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

@@ -473,6 +473,9 @@ export class GatewayClient {
opts?: {
sessionKey?: string;
agentId?: string;
thinking_enabled?: boolean;
reasoning_effort?: string;
plan_mode?: boolean;
}
): Promise<{ runId: string }> {
const agentId = opts?.agentId || this.defaultAgentId;
@@ -482,11 +485,16 @@ export class GatewayClient {
// If no agent ID, try to fetch from ZCLAW status (async, but we'll handle it in connectZclawStream)
if (!agentId) {
// Try to get default agent asynchronously
const chatModeOpts = {
thinking_enabled: opts?.thinking_enabled,
reasoning_effort: opts?.reasoning_effort,
plan_mode: opts?.plan_mode,
};
this.fetchDefaultAgentId().then(() => {
const resolvedAgentId = this.defaultAgentId;
if (resolvedAgentId) {
this.streamCallbacks.set(runId, callbacks);
this.connectZclawStream(resolvedAgentId, runId, sessionId, message);
this.connectZclawStream(resolvedAgentId, runId, sessionId, message, chatModeOpts);
} else {
callbacks.onError('No agent available. Please ensure ZCLAW has at least one agent.');
callbacks.onComplete();
@@ -502,7 +510,11 @@ export class GatewayClient {
this.streamCallbacks.set(runId, callbacks);
// Connect to ZCLAW WebSocket if not connected
this.connectZclawStream(agentId, runId, sessionId, message);
this.connectZclawStream(agentId, runId, sessionId, message, {
thinking_enabled: opts?.thinking_enabled,
reasoning_effort: opts?.reasoning_effort,
plan_mode: opts?.plan_mode,
});
return { runId };
}
@@ -512,7 +524,12 @@ export class GatewayClient {
agentId: string,
runId: string,
sessionId: string,
message: string
message: string,
chatModeOpts?: {
thinking_enabled?: boolean;
reasoning_effort?: string;
plan_mode?: boolean;
}
): void {
// Close existing connection if any
if (this.zclawWs && this.zclawWs.readyState !== WebSocket.CLOSED) {
@@ -539,11 +556,20 @@ export class GatewayClient {
this.zclawWs.onopen = () => {
this.log('info', 'ZCLAW WebSocket connected');
// Send chat message using ZCLAW actual protocol
const chatRequest = {
const chatRequest: Record<string, unknown> = {
type: 'message',
content: message,
session_id: sessionId,
};
if (chatModeOpts?.thinking_enabled !== undefined) {
chatRequest.thinking_enabled = chatModeOpts.thinking_enabled;
}
if (chatModeOpts?.reasoning_effort !== undefined) {
chatRequest.reasoning_effort = chatModeOpts.reasoning_effort;
}
if (chatModeOpts?.plan_mode !== undefined) {
chatRequest.plan_mode = chatModeOpts.plan_mode;
}
this.zclawWs?.send(JSON.stringify(chatRequest));
};
@@ -569,8 +595,13 @@ export class GatewayClient {
this.zclawWs.onclose = (event) => {
this.log('info', `ZCLAW WebSocket closed: ${event.code} ${event.reason}`);
const callbacks = this.streamCallbacks.get(runId);
if (callbacks && event.code !== 1000) {
callbacks.onError(`Connection closed: ${event.reason || 'unknown'}`);
if (callbacks) {
if (event.code !== 1000) {
callbacks.onError(`Connection closed: ${event.reason || 'unknown'}`);
} else {
// Normal closure — ensure stream is completed even if no done event was sent
callbacks.onComplete();
}
}
this.streamCallbacks.delete(runId);
this.zclawWs = null;
@@ -614,8 +645,9 @@ export class GatewayClient {
case 'response':
// Final response with tokens info
if (data.content) {
// If we haven't received any deltas yet, send the full response
// This handles non-streaming responses
// Forward the full response content via onDelta
// This handles non-streaming responses from the server
callbacks.onDelta(data.content);
}
// Mark complete if phase done wasn't sent
callbacks.onComplete();