aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/stores/assistant-store.ts
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/stores/assistant-store.ts
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/stores/assistant-store.ts')
-rw-r--r--packages/ui/src/stores/assistant-store.ts201
1 files changed, 0 insertions, 201 deletions
diff --git a/packages/ui/src/stores/assistant-store.ts b/packages/ui/src/stores/assistant-store.ts
deleted file mode 100644
index 180031b..0000000
--- a/packages/ui/src/stores/assistant-store.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-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 });
- },
-}));