feat(prompts): add atlascloud logo SVG and update prompt source handling

This commit is contained in:
HouYunFei
2026-07-25 11:55:00 +08:00
parent 49851f2b5f
commit 0179724467
13 changed files with 80 additions and 346 deletions
+15 -55
View File
@@ -9,12 +9,11 @@ import { exportCanvasNodes } from "@/lib/canvas/canvas-export";
import { getNodeDefinition } from "@/lib/canvas/node-registry";
import { cn } from "@/lib/utils";
import { PromptDetailDialog } from "@/pages/prompts/components/prompt-detail-dialog";
import { fetchSourcePrompts, personalPromptToPrompt, type Prompt } from "@/services/api/prompts";
import { fetchSourcePrompts, type Prompt } from "@/services/api/prompts";
import { uploadMediaFile } from "@/services/file-storage";
import { uploadImage } from "@/services/image-storage";
import { useAssetStore, type Asset, type AssetKind } from "@/stores/use-asset-store";
import { usePromptSourceStore } from "@/stores/use-prompt-source-store";
import { usePromptStore } from "@/stores/use-prompt-store";
import { CANVAS_SIDE_PANEL_MAX_WIDTH, CANVAS_SIDE_PANEL_MIN_WIDTH, CANVAS_SIDE_PANEL_MOTION_MS, useCanvasSidePanelStore } from "@/stores/use-canvas-side-panel-store";
import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
@@ -444,10 +443,9 @@ function AssetCover({ asset }: { asset: Asset }) {
const CanvasPromptsTab = memo(function CanvasPromptsTab({ onInsert, theme }: { onInsert: (payload: InsertAssetPayload) => void; theme: CanvasTheme }) {
const { message } = App.useApp();
const sources = usePromptSourceStore((state) => state.sources);
const personalPrompts = usePromptStore((state) => state.prompts);
const enabledSources = useMemo(() => sources.filter((source) => source.enabled), [sources]);
const [keyword, setKeyword] = useState("");
const [expanded, setExpanded] = useState<Record<string, boolean>>({ personal: true });
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const [detail, setDetail] = useState<Prompt | null>(null);
const copyPrompt = async (prompt: string) => {
@@ -466,32 +464,19 @@ const CanvasPromptsTab = memo(function CanvasPromptsTab({ onInsert, theme }: { o
</div>
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-3">
<div className="space-y-1">
<PersonalPromptGroup
items={personalPrompts.map(personalPromptToPrompt)}
keyword={keyword}
open={!!expanded.personal}
theme={theme}
onToggle={() => setExpanded((prev) => ({ ...prev, personal: !prev.personal }))}
onInsert={onInsert}
onView={setDetail}
/>
{enabledSources.length ? (
<>
{enabledSources.map((source) => (
<PromptSourceGroup
key={source.id}
sourceId={source.id}
sourceName={source.name}
keyword={keyword}
open={!!expanded[source.id]}
theme={theme}
onToggle={() => setExpanded((prev) => ({ ...prev, [source.id]: !prev[source.id] }))}
onInsert={onInsert}
onView={setDetail}
/>
))}
</>
) : personalPrompts.length === 0 ? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无提示词" className="pt-12" /> : null}
{enabledSources.length ? enabledSources.map((source) => (
<PromptSourceGroup
key={source.id}
sourceId={source.id}
sourceName={source.name}
keyword={keyword}
open={!!expanded[source.id]}
theme={theme}
onToggle={() => setExpanded((prev) => ({ ...prev, [source.id]: !prev[source.id] }))}
onInsert={onInsert}
onView={setDetail}
/>
)) : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无提示词" className="pt-12" />}
</div>
</div>
<PromptDetailDialog prompt={detail} onClose={() => setDetail(null)} onCopy={(prompt) => void copyPrompt(prompt)} />
@@ -499,31 +484,6 @@ const CanvasPromptsTab = memo(function CanvasPromptsTab({ onInsert, theme }: { o
);
});
function PersonalPromptGroup({ items, keyword, open, theme, onToggle, onInsert, onView }: { items: Prompt[]; keyword: string; open: boolean; theme: CanvasTheme; onToggle: () => void; onInsert: (payload: InsertAssetPayload) => void; onView: (prompt: Prompt) => void }) {
const showResults = open || !!keyword.trim();
const filtered = useMemo(() => {
const query = keyword.trim().toLowerCase();
return query ? items.filter((item) => [item.title, item.prompt, item.description, ...item.tags].join(" ").toLowerCase().includes(query)) : items;
}, [items, keyword]);
return (
<div>
<button type="button" onClick={onToggle} className="flex w-full items-center gap-1.5 rounded-md px-1.5 py-1.5 text-left text-xs font-semibold opacity-75 transition hover:opacity-100">
<ChevronRight className={cn("size-3.5 transition-transform", showResults && "rotate-90")} />
<BookOpen className="size-3.5" />
<span className="min-w-0 flex-1 truncate"></span>
<span className="opacity-50">{filtered.length}</span>
</button>
{showResults ? (
<div className="space-y-1.5 px-1 pb-2 pt-1">
{filtered.map((item) => <PromptRow key={item.id} item={item} theme={theme} onInsert={() => onInsert({ kind: "text", content: item.prompt, title: item.title })} onView={() => onView(item)} />)}
{!filtered.length ? <div className="py-4 text-center text-xs opacity-40">{keyword.trim() ? "无匹配提示词" : "还没有保存提示词"}</div> : null}
</div>
) : null}
</div>
);
}
function PromptSourceGroup({
sourceId,
sourceName,