aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/models/assistant-store.ts.bk
diff options
context:
space:
mode:
author苏向夜 <46275354+fu050409@users.noreply.github.com>2026-03-29 21:35:34 +0800
committerGitHub <noreply@github.com>2026-03-29 21:35:34 +0800
commit70348cefb7de8c1e044800296a99177309c5a81e (patch)
treeeb0fdfbcc880574e9b386a3f2fc9b3a89489e5b5 /packages/ui/src/models/assistant-store.ts.bk
parentf2f5383a1b615a7493316d558dc55271198e772a (diff)
parent1c115141cc7b676e6a07786594155c3ac293fe34 (diff)
downloadDropOut-70348cefb7de8c1e044800296a99177309c5a81e.tar.gz
DropOut-70348cefb7de8c1e044800296a99177309c5a81e.zip
refactor(ui): full rewrite instance and code struct (#129)
## Summary by Sourcery Refactor the UI to modernize effect handling, routing, and legacy APIs while adding a reusable alert dialog component and cleaning up obsolete stores. New Features: - Introduce a shared SaturnEffect context via ParticleBackground so pages can access the effect without relying on global window APIs. - Add a Base UI–powered alert dialog component for consistent confirmation and warning flows across the app. - Define a central router configuration module with instance routes to standardize page wiring. Bug Fixes: - Ensure SaturnEffect nullish checks are handled safely when forwarding pointer and touch events from the home view. Enhancements: - Rewrite ParticleBackground to manage its own SaturnEffect lifecycle via React state and context instead of global accessors. - Update the home view to use the SaturnEffect hook, simplify pointer/touch handlers, and remove legacy game and release store usage. - Refine layout and accessibility attributes for various form field and label components, including field grouping and error rendering keys. - Simplify sidebar navigation and adjust the user dropdown trigger to work with the updated dropdown menu API. - Wrap the root outlet for the home route with ParticleBackground only on the index path to limit the effect to the intended view. - Clean up imports and code style in radio group and other UI primitives for consistency. Chores: - Remove deprecated UI stores and utility modules that are no longer used with the new architecture. - Add changeset entries documenting the Saturn effect refactor, ParticleBackground rewrite, and removal of legacy store code.
Diffstat (limited to 'packages/ui/src/models/assistant-store.ts.bk')
-rw-r--r--packages/ui/src/models/assistant-store.ts.bk201
1 files changed, 201 insertions, 0 deletions
diff --git a/packages/ui/src/models/assistant-store.ts.bk b/packages/ui/src/models/assistant-store.ts.bk
new file mode 100644
index 0000000..180031b
--- /dev/null
+++ b/packages/ui/src/models/assistant-store.ts.bk
@@ -0,0 +1,201 @@
+import { invoke } from "@tauri-apps/api/core";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
+import { create } from "zustand";
+import type { GenerationStats, StreamChunk } from "@/types/bindings/assistant";
+
+export interface Message {
+ role: "user" | "assistant" | "system";
+ content: string;
+ stats?: GenerationStats;
+}
+
+interface AssistantState {
+ // State
+ messages: Message[];
+ isProcessing: boolean;
+ isProviderHealthy: boolean | undefined;
+ streamingContent: string;
+ initialized: boolean;
+ streamUnlisten: UnlistenFn | null;
+
+ // Actions
+ init: () => Promise<void>;
+ checkHealth: () => Promise<void>;
+ sendMessage: (
+ content: string,
+ isEnabled: boolean,
+ provider: string,
+ endpoint: string,
+ ) => Promise<void>;
+ finishStreaming: () => void;
+ clearHistory: () => void;
+ setMessages: (messages: Message[]) => void;
+ setIsProcessing: (isProcessing: boolean) => void;
+ setIsProviderHealthy: (isProviderHealthy: boolean | undefined) => void;
+ setStreamingContent: (streamingContent: string) => void;
+}
+
+export const useAssistantStore = create<AssistantState>((set, get) => ({
+ // Initial state
+ messages: [],
+ isProcessing: false,
+ isProviderHealthy: false,
+ streamingContent: "",
+ initialized: false,
+ streamUnlisten: null,
+
+ // Actions
+ init: async () => {
+ const { initialized } = get();
+ if (initialized) return;
+ set({ initialized: true });
+ await get().checkHealth();
+ },
+
+ checkHealth: async () => {
+ try {
+ const isHealthy = await invoke<boolean>("assistant_check_health");
+ set({ isProviderHealthy: isHealthy });
+ } catch (e) {
+ console.error("Failed to check provider health:", e);
+ set({ isProviderHealthy: false });
+ }
+ },
+
+ finishStreaming: () => {
+ const { streamUnlisten } = get();
+ set({ isProcessing: false, streamingContent: "" });
+
+ if (streamUnlisten) {
+ streamUnlisten();
+ set({ streamUnlisten: null });
+ }
+ },
+
+ sendMessage: async (content, isEnabled, provider, endpoint) => {
+ if (!content.trim()) return;
+
+ const { messages } = get();
+
+ if (!isEnabled) {
+ const newMessage: Message = {
+ role: "assistant",
+ content: "Assistant is disabled. Enable it in Settings > AI Assistant.",
+ };
+ set({ messages: [...messages, { role: "user", content }, newMessage] });
+ return;
+ }
+
+ // Add user message
+ const userMessage: Message = { role: "user", content };
+ const updatedMessages = [...messages, userMessage];
+ set({
+ messages: updatedMessages,
+ isProcessing: true,
+ streamingContent: "",
+ });
+
+ // Add empty assistant message for streaming
+ const assistantMessage: Message = { role: "assistant", content: "" };
+ const withAssistantMessage = [...updatedMessages, assistantMessage];
+ set({ messages: withAssistantMessage });
+
+ try {
+ // Set up stream listener
+ const unlisten = await listen<StreamChunk>(
+ "assistant-stream",
+ (event) => {
+ const chunk = event.payload;
+ const currentState = get();
+
+ if (chunk.content) {
+ const newStreamingContent =
+ currentState.streamingContent + chunk.content;
+ const currentMessages = [...currentState.messages];
+ const lastIdx = currentMessages.length - 1;
+
+ if (lastIdx >= 0 && currentMessages[lastIdx].role === "assistant") {
+ currentMessages[lastIdx] = {
+ ...currentMessages[lastIdx],
+ content: newStreamingContent,
+ };
+ set({
+ streamingContent: newStreamingContent,
+ messages: currentMessages,
+ });
+ }
+ }
+
+ if (chunk.done) {
+ const finalMessages = [...currentState.messages];
+ const lastIdx = finalMessages.length - 1;
+
+ if (
+ chunk.stats &&
+ lastIdx >= 0 &&
+ finalMessages[lastIdx].role === "assistant"
+ ) {
+ finalMessages[lastIdx] = {
+ ...finalMessages[lastIdx],
+ stats: chunk.stats,
+ };
+ set({ messages: finalMessages });
+ }
+
+ get().finishStreaming();
+ }
+ },
+ );
+
+ set({ streamUnlisten: unlisten });
+
+ // Start streaming chat
+ await invoke<string>("assistant_chat_stream", {
+ messages: withAssistantMessage.slice(0, -1), // Exclude the empty assistant message
+ });
+ } catch (e) {
+ console.error("Failed to send message:", e);
+ const errorMessage = e instanceof Error ? e.message : String(e);
+
+ let helpText = "";
+ if (provider === "ollama") {
+ helpText = `\n\nPlease ensure Ollama is running at ${endpoint}.`;
+ } else if (provider === "openai") {
+ helpText = "\n\nPlease check your OpenAI API key in Settings.";
+ }
+
+ // Update the last message with error
+ const currentMessages = [...get().messages];
+ const lastIdx = currentMessages.length - 1;
+ if (lastIdx >= 0 && currentMessages[lastIdx].role === "assistant") {
+ currentMessages[lastIdx] = {
+ role: "assistant",
+ content: `Error: ${errorMessage}${helpText}`,
+ };
+ set({ messages: currentMessages });
+ }
+
+ get().finishStreaming();
+ }
+ },
+
+ clearHistory: () => {
+ set({ messages: [], streamingContent: "" });
+ },
+
+ setMessages: (messages) => {
+ set({ messages });
+ },
+
+ setIsProcessing: (isProcessing) => {
+ set({ isProcessing });
+ },
+
+ setIsProviderHealthy: (isProviderHealthy) => {
+ set({ isProviderHealthy });
+ },
+
+ setStreamingContent: (streamingContent) => {
+ set({ streamingContent });
+ },
+}));