fix(desktop): DeerFlow UI — ChatArea refactor + ai-elements + dead CSS cleanup
ChatArea retry button uses setInput instead of direct sendToGateway, fix bootstrap spinner stuck for non-logged-in users, remove dead CSS (aurora-title/sidebar-open/quick-action-chips), add ai components (ReasoningBlock/StreamingText/ChatMode/ModelSelector/TaskProgress), add ClassroomPlayer + ResizableChatLayout + artifact panel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
223
desktop/src/store/classroomStore.ts
Normal file
223
desktop/src/store/classroomStore.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Classroom Store
|
||||
*
|
||||
* Zustand store for classroom generation, chat messages,
|
||||
* and active classroom data. Uses Tauri invoke for backend calls.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import type {
|
||||
Classroom,
|
||||
ClassroomChatMessage,
|
||||
} from '../types/classroom';
|
||||
import { createLogger } from '../lib/logger';
|
||||
|
||||
const log = createLogger('ClassroomStore');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface GenerationRequest {
|
||||
topic: string;
|
||||
document?: string;
|
||||
style?: string;
|
||||
level?: string;
|
||||
targetDurationMinutes?: number;
|
||||
sceneCount?: number;
|
||||
customInstructions?: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface GenerationResult {
|
||||
classroomId: string;
|
||||
}
|
||||
|
||||
export interface GenerationProgressEvent {
|
||||
topic: string;
|
||||
stage: string;
|
||||
progress: number;
|
||||
activity: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Store interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ClassroomState {
|
||||
/** Currently generating classroom */
|
||||
generating: boolean;
|
||||
/** Generation progress stage */
|
||||
progressStage: string | null;
|
||||
progressPercent: number;
|
||||
progressActivity: string;
|
||||
/** Topic being generated (used for cancel) */
|
||||
generatingTopic: string | null;
|
||||
/** The active classroom */
|
||||
activeClassroom: Classroom | null;
|
||||
/** Whether the ClassroomPlayer overlay is open */
|
||||
classroomOpen: boolean;
|
||||
/** Chat messages for the active classroom */
|
||||
chatMessages: ClassroomChatMessage[];
|
||||
/** Whether chat is loading */
|
||||
chatLoading: boolean;
|
||||
/** Generation error message */
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface ClassroomActions {
|
||||
startGeneration: (request: GenerationRequest) => Promise<string>;
|
||||
cancelGeneration: () => void;
|
||||
loadClassroom: (id: string) => Promise<void>;
|
||||
setActiveClassroom: (classroom: Classroom) => void;
|
||||
openClassroom: () => void;
|
||||
closeClassroom: () => void;
|
||||
sendChatMessage: (message: string, sceneContext?: string) => Promise<void>;
|
||||
clearError: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export type ClassroomStore = ClassroomState & ClassroomActions;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Store
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const useClassroomStore = create<ClassroomStore>()((set, get) => ({
|
||||
generating: false,
|
||||
progressStage: null,
|
||||
progressPercent: 0,
|
||||
progressActivity: '',
|
||||
generatingTopic: null,
|
||||
activeClassroom: null,
|
||||
classroomOpen: false,
|
||||
chatMessages: [],
|
||||
chatLoading: false,
|
||||
error: null,
|
||||
|
||||
startGeneration: async (request) => {
|
||||
set({
|
||||
generating: true,
|
||||
progressStage: 'agent_profiles',
|
||||
progressPercent: 0,
|
||||
progressActivity: 'Starting generation...',
|
||||
generatingTopic: request.topic,
|
||||
error: null,
|
||||
});
|
||||
|
||||
// Listen for progress events from Rust
|
||||
const unlisten = await listen<GenerationProgressEvent>('classroom:progress', (event) => {
|
||||
const { stage, progress, activity } = event.payload;
|
||||
set({
|
||||
progressStage: stage,
|
||||
progressPercent: progress,
|
||||
progressActivity: activity,
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await invoke<GenerationResult>('classroom_generate', { request });
|
||||
set({ generating: false });
|
||||
await get().loadClassroom(result.classroomId);
|
||||
set({ classroomOpen: true });
|
||||
return result.classroomId;
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
log.error('Generation failed', { error: msg });
|
||||
set({ generating: false, error: msg });
|
||||
throw e;
|
||||
} finally {
|
||||
unlisten();
|
||||
}
|
||||
},
|
||||
|
||||
cancelGeneration: () => {
|
||||
const topic = get().generatingTopic;
|
||||
if (topic) {
|
||||
invoke('classroom_cancel_generation', { topic }).catch(() => {});
|
||||
}
|
||||
set({ generating: false, generatingTopic: null });
|
||||
},
|
||||
|
||||
loadClassroom: async (id) => {
|
||||
try {
|
||||
const classroom = await invoke<Classroom>('classroom_get', { classroomId: id });
|
||||
set({ activeClassroom: classroom, chatMessages: [] });
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
log.error('Failed to load classroom', { error: msg });
|
||||
set({ error: msg });
|
||||
}
|
||||
},
|
||||
|
||||
setActiveClassroom: (classroom) => {
|
||||
set({ activeClassroom: classroom, chatMessages: [], classroomOpen: true });
|
||||
},
|
||||
|
||||
openClassroom: () => {
|
||||
set({ classroomOpen: true });
|
||||
},
|
||||
|
||||
closeClassroom: () => {
|
||||
set({ classroomOpen: false });
|
||||
},
|
||||
|
||||
sendChatMessage: async (message, sceneContext) => {
|
||||
const classroom = get().activeClassroom;
|
||||
if (!classroom) {
|
||||
log.error('No active classroom');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a local user message for display
|
||||
const userMsg: ClassroomChatMessage = {
|
||||
id: `user-${Date.now()}`,
|
||||
agentId: 'user',
|
||||
agentName: '你',
|
||||
agentAvatar: '👤',
|
||||
content: message,
|
||||
timestamp: Date.now(),
|
||||
role: 'user',
|
||||
color: '#3b82f6',
|
||||
};
|
||||
|
||||
set((state) => ({
|
||||
chatMessages: [...state.chatMessages, userMsg],
|
||||
chatLoading: true,
|
||||
}));
|
||||
|
||||
try {
|
||||
const responses = await invoke<ClassroomChatMessage[]>('classroom_chat', {
|
||||
request: {
|
||||
classroomId: classroom.id,
|
||||
userMessage: message,
|
||||
sceneContext: sceneContext ?? null,
|
||||
},
|
||||
});
|
||||
set((state) => ({
|
||||
chatMessages: [...state.chatMessages, ...responses],
|
||||
chatLoading: false,
|
||||
}));
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
log.error('Chat failed', { error: msg });
|
||||
set({ chatLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
|
||||
reset: () => set({
|
||||
generating: false,
|
||||
progressStage: null,
|
||||
progressPercent: 0,
|
||||
progressActivity: '',
|
||||
activeClassroom: null,
|
||||
classroomOpen: false,
|
||||
chatMessages: [],
|
||||
chatLoading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
Reference in New Issue
Block a user