kit初版,模型引入,agent优化
This commit is contained in:
289
art-agent/frontend/src/lib/app-context.tsx
Normal file
289
art-agent/frontend/src/lib/app-context.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import type { Session, Tag, ImageAsset, ChatMessage } from "./types";
|
||||
import {
|
||||
loadSessions,
|
||||
saveSessions,
|
||||
createSession as storeCreateSession,
|
||||
updateSession as storeUpdateSession,
|
||||
deleteSession as storeDeleteSession,
|
||||
loadTags,
|
||||
saveTags,
|
||||
addTag as storeAddTag,
|
||||
deleteTag as storeDeleteTag,
|
||||
loadAssets,
|
||||
addAsset as storeAddAsset,
|
||||
updateAsset as storeUpdateAsset,
|
||||
deleteAsset as storeDeleteAsset,
|
||||
toggleFavorite as storeToggleFavorite,
|
||||
generateId,
|
||||
} from "./store";
|
||||
|
||||
interface AppContextValue {
|
||||
// 会话
|
||||
sessions: Session[];
|
||||
activeSessionId: string | null;
|
||||
activeSession: Session | null;
|
||||
createSession: () => Session;
|
||||
switchSession: (id: string) => void;
|
||||
deleteSession: (id: string) => void;
|
||||
renameSession: (id: string, title: string) => void;
|
||||
updateSessionTags: (id: string, tags: string[]) => void;
|
||||
appendMessage: (sessionId: string, message: ChatMessage) => void;
|
||||
updateSessionThumbnail: (sessionId: string, url: string) => void;
|
||||
|
||||
// 标签
|
||||
tags: Tag[];
|
||||
addTag: (name: string, color: string) => Tag;
|
||||
deleteTag: (id: string) => void;
|
||||
selectedTagIds: string[];
|
||||
setSelectedTagIds: (ids: string[]) => void;
|
||||
|
||||
// 图片资源
|
||||
assets: ImageAsset[];
|
||||
addAsset: (asset: ImageAsset) => void;
|
||||
updateAsset: (id: string, patch: Partial<ImageAsset>) => void;
|
||||
deleteAssetById: (id: string) => void;
|
||||
toggleFavorite: (id: string) => boolean;
|
||||
|
||||
// 右侧面板
|
||||
detailImage: ImageAsset | null;
|
||||
setDetailImage: (asset: ImageAsset | null) => void;
|
||||
|
||||
// 左栏折叠
|
||||
sidebarCollapsed: boolean;
|
||||
setSidebarCollapsed: (v: boolean) => void;
|
||||
}
|
||||
|
||||
const AppContext = createContext<AppContextValue | null>(null);
|
||||
|
||||
export function useApp(): AppContextValue {
|
||||
const ctx = useContext(AppContext);
|
||||
if (!ctx) throw new Error("useApp must be used within AppProvider");
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function AppProvider({ children }: { children: ReactNode }) {
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [assets, setAssets] = useState<ImageAsset[]>([]);
|
||||
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
|
||||
const [detailImage, setDetailImage] = useState<ImageAsset | null>(null);
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
||||
if (typeof window !== "undefined") return window.innerWidth < 768;
|
||||
return false;
|
||||
});
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// 初始化:从 localStorage 加载
|
||||
useEffect(() => {
|
||||
setSessions(loadSessions());
|
||||
setTags(loadTags());
|
||||
setAssets(loadAssets());
|
||||
setInitialized(true);
|
||||
}, []);
|
||||
|
||||
// 初始化后,如果没有会话则自动创建一个
|
||||
useEffect(() => {
|
||||
if (!initialized) return;
|
||||
if (sessions.length === 0) {
|
||||
const s = storeCreateSession();
|
||||
setSessions([s]);
|
||||
setActiveSessionId(s.id);
|
||||
} else if (!activeSessionId) {
|
||||
setActiveSessionId(sessions[0].id);
|
||||
}
|
||||
}, [initialized, sessions.length, activeSessionId]);
|
||||
|
||||
const activeSession = useMemo(
|
||||
() => sessions.find((s) => s.id === activeSessionId) ?? null,
|
||||
[sessions, activeSessionId]
|
||||
);
|
||||
|
||||
// --- 会话操作 ---
|
||||
|
||||
const createSession = useCallback(() => {
|
||||
const s = storeCreateSession();
|
||||
setSessions((prev) => [s, ...prev]);
|
||||
setActiveSessionId(s.id);
|
||||
setDetailImage(null);
|
||||
return s;
|
||||
}, []);
|
||||
|
||||
const switchSession = useCallback((id: string) => {
|
||||
setActiveSessionId(id);
|
||||
setDetailImage(null);
|
||||
if (typeof window !== "undefined" && window.innerWidth < 768) {
|
||||
setSidebarCollapsed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const deleteSessionCb = useCallback(
|
||||
(id: string) => {
|
||||
storeDeleteSession(id);
|
||||
setSessions((prev) => {
|
||||
const next = prev.filter((s) => s.id !== id);
|
||||
if (activeSessionId === id) {
|
||||
if (next.length > 0) {
|
||||
setActiveSessionId(next[0].id);
|
||||
} else {
|
||||
const s = storeCreateSession();
|
||||
next.push(s);
|
||||
setActiveSessionId(s.id);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setAssets((prev) => prev.filter((a) => a.sessionId !== id));
|
||||
setDetailImage(null);
|
||||
},
|
||||
[activeSessionId]
|
||||
);
|
||||
|
||||
const renameSession = useCallback((id: string, title: string) => {
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== id) return s;
|
||||
const updated = { ...s, title, updatedAt: Date.now() };
|
||||
storeUpdateSession(updated);
|
||||
return updated;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
const updateSessionTags = useCallback((id: string, tagIds: string[]) => {
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== id) return s;
|
||||
const updated = { ...s, tags: tagIds, updatedAt: Date.now() };
|
||||
storeUpdateSession(updated);
|
||||
return updated;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
const appendMessage = useCallback((sessionId: string, message: ChatMessage) => {
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== sessionId) return s;
|
||||
const updated = {
|
||||
...s,
|
||||
messages: [...s.messages, message],
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
// 用首条用户消息作为自动标题
|
||||
if (message.role === "user" && s.messages.length === 0) {
|
||||
updated.title = message.content.slice(0, 30) + (message.content.length > 30 ? "…" : "");
|
||||
}
|
||||
storeUpdateSession(updated);
|
||||
return updated;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
const updateSessionThumbnail = useCallback((sessionId: string, url: string) => {
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== sessionId) return s;
|
||||
const updated = { ...s, thumbnail: url, updatedAt: Date.now() };
|
||||
storeUpdateSession(updated);
|
||||
return updated;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
// --- 标签操作 ---
|
||||
|
||||
const addTag = useCallback((name: string, color: string) => {
|
||||
const t = storeAddTag(name, color);
|
||||
setTags(loadTags());
|
||||
return t;
|
||||
}, []);
|
||||
|
||||
const deleteTagCb = useCallback((id: string) => {
|
||||
storeDeleteTag(id);
|
||||
setTags(loadTags());
|
||||
setSelectedTagIds((prev) => prev.filter((tid) => tid !== id));
|
||||
}, []);
|
||||
|
||||
// --- 资源操作 ---
|
||||
|
||||
const addAssetCb = useCallback((asset: ImageAsset) => {
|
||||
storeAddAsset(asset);
|
||||
setAssets((prev) => [asset, ...prev]);
|
||||
}, []);
|
||||
|
||||
const updateAssetCb = useCallback((id: string, patch: Partial<ImageAsset>) => {
|
||||
storeUpdateAsset(id, patch);
|
||||
setAssets((prev) => prev.map((a) => (a.id === id ? { ...a, ...patch } : a)));
|
||||
setDetailImage((prev) => (prev && prev.id === id ? { ...prev, ...patch } : prev));
|
||||
}, []);
|
||||
|
||||
const deleteAssetCb = useCallback((id: string) => {
|
||||
storeDeleteAsset(id);
|
||||
setAssets((prev) => prev.filter((a) => a.id !== id));
|
||||
setDetailImage((prev) => (prev && prev.id === id ? null : prev));
|
||||
}, []);
|
||||
|
||||
const toggleFavoriteCb = useCallback((id: string) => {
|
||||
const result = storeToggleFavorite(id);
|
||||
setAssets((prev) =>
|
||||
prev.map((a) => (a.id === id ? { ...a, favorited: result } : a))
|
||||
);
|
||||
setDetailImage((prev) =>
|
||||
prev && prev.id === id ? { ...prev, favorited: result } : prev
|
||||
);
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
const value = useMemo<AppContextValue>(
|
||||
() => ({
|
||||
sessions,
|
||||
activeSessionId,
|
||||
activeSession,
|
||||
createSession,
|
||||
switchSession,
|
||||
deleteSession: deleteSessionCb,
|
||||
renameSession,
|
||||
updateSessionTags,
|
||||
appendMessage,
|
||||
updateSessionThumbnail,
|
||||
tags,
|
||||
addTag,
|
||||
deleteTag: deleteTagCb,
|
||||
selectedTagIds,
|
||||
setSelectedTagIds,
|
||||
assets,
|
||||
addAsset: addAssetCb,
|
||||
updateAsset: updateAssetCb,
|
||||
deleteAssetById: deleteAssetCb,
|
||||
toggleFavorite: toggleFavoriteCb,
|
||||
detailImage,
|
||||
setDetailImage,
|
||||
sidebarCollapsed,
|
||||
setSidebarCollapsed,
|
||||
}),
|
||||
[
|
||||
sessions, activeSessionId, activeSession,
|
||||
createSession, switchSession, deleteSessionCb,
|
||||
renameSession, updateSessionTags, appendMessage, updateSessionThumbnail,
|
||||
tags, addTag, deleteTagCb, selectedTagIds,
|
||||
assets, addAssetCb, updateAssetCb, deleteAssetCb, toggleFavoriteCb,
|
||||
detailImage, sidebarCollapsed,
|
||||
]
|
||||
);
|
||||
|
||||
if (!initialized) return null;
|
||||
|
||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||
}
|
||||
Reference in New Issue
Block a user