feat(desktop): DeerFlow visual redesign + stream hang fix + intelligence client
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

DeerFlow frontend visual overhaul:
- Card-style input box (white rounded card, textarea top, actions bottom)
- Dropdown mode selector (闪速/思考/Pro/Ultra with icons+descriptions)
- Colored quick-action chips (小惊喜/写作/研究/收集/学习)
- Minimal top bar (title + token count + export)
- Warm gray color system (#faf9f6 bg, #f5f4f1 sidebar, #e8e6e1 border)
- DeerFlow-style sidebar (新对话/对话/智能体 nav)
- Reasoning block, tool call chain, task progress visualization
- Streaming text, model selector, suggestion chips components
- Resizable artifact panel with drag handle
- Virtualized message list for 100+ messages

Bug fixes:
- Stream hang: GatewayClient onclose code 1000 now calls onComplete
- WebView2 textarea border: CSS !important override for UA styles
- Gateway stream event handling (response/phase/tool_call types)

Intelligence client:
- Unified client with fallback drivers (compactor/heartbeat/identity/memory/reflection)
- Gateway API types and type conversions
This commit is contained in:
iven
2026-04-01 22:03:07 +08:00
parent e3b93ff96d
commit 73ff5e8c5e
43 changed files with 4817 additions and 905 deletions

View File

@@ -15,3 +15,5 @@ export type {
UseAutomationEventsOptions,
} from './useAutomationEvents';
export { useOptimisticMessages } from './useOptimisticMessages';

View File

@@ -0,0 +1,102 @@
import { useCallback, useRef } from 'react';
import { useChatStore, type Message } from '../store/chatStore';
import { createLogger } from '../lib/logger';
const log = createLogger('OptimisticMessages');
/**
* Represents a file attached to an optimistic message,
* tracking its upload lifecycle. Extends MessageFile with a status field.
*/
interface OptimisticFile {
name: string;
size: number;
status: 'uploading' | 'uploaded' | 'error';
url?: string;
}
/**
* 3-phase optimistic message merging hook (inspired by DeerFlow useThreadStream).
*
* Phase 1: Instant local echo -- creates a synthetic user message with `optimistic: true`
* Phase 2: Server confirmation -- removes optimistic message when real message arrives
* Phase 3: File status transition -- updates file status from uploading -> uploaded | error
*
* This hook provides standalone utilities for components that need fine-grained
* control over optimistic rendering outside the main chat flow.
*/
export function useOptimisticMessages() {
const optimisticIdCounter = useRef(0);
const generateOptimisticId = useCallback(() => {
optimisticIdCounter.current += 1;
return `opt-user-${Date.now()}-${optimisticIdCounter.current}`;
}, []);
/**
* Phase 1: Create and insert an optimistic user message into the store.
* Returns the optimistic ID for later correlation.
*/
const addOptimistic = useCallback((content: string, files?: File[]) => {
const id = generateOptimisticId();
const optimisticFiles: OptimisticFile[] | undefined = files?.map(f => ({
name: f.name,
size: f.size,
status: 'uploading' as const,
}));
const optimisticMessage: Message = {
id,
role: 'user',
content,
timestamp: new Date(),
optimistic: true,
// Cast through unknown because OptimisticFile extends MessageFile with status
files: optimisticFiles as Message['files'],
};
log.debug('Adding optimistic message', { id, content: content.slice(0, 50) });
useChatStore.setState(state => ({
messages: [...state.messages, optimisticMessage],
}));
return id;
}, [generateOptimisticId]);
/**
* Phase 2: Remove an optimistic message when the server confirms
* by sending back the real message.
*/
const clearOnConfirm = useCallback((optimisticId: string) => {
log.debug('Clearing optimistic message on confirm', { optimisticId });
useChatStore.setState(state => ({
messages: state.messages.filter(m => m.id !== optimisticId),
}));
}, []);
/**
* Phase 3: Transition file attachment status for an optimistic message.
*/
const updateFileStatus = useCallback((optimisticId: string, status: 'uploaded' | 'error') => {
log.debug('Updating file status', { optimisticId, status });
useChatStore.setState(state => ({
messages: state.messages.map(m => {
if (m.id === optimisticId && m.files) {
return {
...m,
files: m.files.map(f => ({
...f,
status,
})),
};
}
return m;
}),
}));
}, []);
return { addOptimistic, clearOnConfirm, updateFileStatus };
}