mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-03 15:41:16 +08:00
feat(prompts): add atlascloud logo SVG and update prompt source handling
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -45,7 +45,7 @@ export function ConfigPromptSources() {
|
||||
const handleDelete = (source: PromptSource) => {
|
||||
modal.confirm({
|
||||
title: `删除「${source.name}」?`,
|
||||
content: "来源配置会被移除,已经保存到我的提示词的内容不受影响。",
|
||||
content: "来源配置会被移除,已经加入我的资产的内容不受影响。",
|
||||
okText: "删除",
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: "取消",
|
||||
|
||||
@@ -12,7 +12,7 @@ export function PromptSelectDialog({ open, onOpenChange, onSelect }: { open: boo
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState(ALL_PROMPTS_OPTION);
|
||||
const { query, items, tags: promptTags, categories: promptCategories } = usePromptList({ keyword, tags: selectedTags, category: selectedCategory, enabled: open, includePersonal: true });
|
||||
const { query, items, tags: promptTags, categories: promptCategories } = usePromptList({ keyword, tags: selectedTags, category: selectedCategory, enabled: open });
|
||||
const toggleTag = (tag: string) => {
|
||||
if (tag === ALL_PROMPTS_OPTION) return setSelectedTags([]);
|
||||
setSelectedTags((items) => (items.includes(tag) ? items.filter((item) => item !== tag) : [...items, tag]));
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ALL_PROMPTS_OPTION, fetchPrompts } from "@/services/api/prompts";
|
||||
|
||||
export const PROMPT_PAGE_SIZE = 20;
|
||||
|
||||
export function usePromptList({ keyword, tags, category, enabled = true, includePersonal = false }: { keyword: string; tags: string[]; category: string; enabled?: boolean; includePersonal?: boolean }) {
|
||||
export function usePromptList({ keyword, tags, category, enabled = true }: { keyword: string; tags: string[]; category: string; enabled?: boolean }) {
|
||||
const [debouncedKeyword, setDebouncedKeyword] = useState(keyword);
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedKeyword(keyword), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [keyword]);
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ["prompts", keyword, tags, category, includePersonal],
|
||||
queryFn: ({ pageParam }) => fetchPrompts({ keyword, tag: tags, category, page: pageParam, pageSize: PROMPT_PAGE_SIZE, includePersonal }),
|
||||
queryKey: ["prompts", debouncedKeyword, tags, category],
|
||||
queryFn: ({ pageParam }) => fetchPrompts({ keyword: debouncedKeyword, tag: tags, category, page: pageParam, pageSize: PROMPT_PAGE_SIZE }),
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage, pages) => (pages.reduce((total, page) => total + page.items.length, 0) < lastPage.total ? pages.length + 1 : undefined),
|
||||
enabled,
|
||||
|
||||
Reference in New Issue
Block a user