返回 ViMax
lineMapping.ts
根目录 / ui / src / lineMapping.ts
1 import type {MappingState, StreamEvent, WorkspaceLine} from './types.js';
2
3 export function createMappingState(): MappingState {
4 return {assistantStreaming: false};
5 }
6
7 export function appendUserLine(lines: WorkspaceLine[], text: string): {lines: WorkspaceLine[]; state: MappingState} {
8 return {lines: [...lines, {kind: 'user', text}], state: createMappingState()};
9 }
10
11 export function applyStreamEvent(lines: WorkspaceLine[], state: MappingState, event: StreamEvent): {lines: WorkspaceLine[]; state: MappingState} {
12 switch (event.type) {
13 case 'turn':
14 case 'status':
15 return {lines, state};
16 case 'token':
17 return appendAssistantToken(lines, state, event.delta ?? '');
18 case 'tool_start':
19 return append(lines, state, {kind: 'tool', status: 'running', text: `tool ${event.tool?.name ?? 'unknown'} started`});
20 case 'tool_progress':
21 return append(lines, state, {
22 kind: 'tool',
23 status: 'running',
24 text: compactJoin([`tool ${event.tool?.name ?? 'unknown'}`, event.progress?.stage, event.progress?.message]),
25 });
26 case 'tool_result': {
27 const result = event.tool_result ?? {};
28 const ok = result.ok !== false;
29 const name = result.name ?? 'unknown';
30 const detail = ok ? '' : cleanToolError(result.content);
31 return append(lines, state, {kind: 'tool', status: ok ? 'done' : 'error', text: detail ? `tool ${name} error: ${detail}` : `tool ${name} ${ok ? 'done' : 'error'}`});
32 }
33 case 'terminal':
34 return append(lines, state, {kind: 'terminal', text: compactJoin([event.stream ? `[${event.stream}]` : '', event.line])});
35 case 'session':
36 return {lines, state};
37 case 'done':
38 return {lines, state: createMappingState()};
39 case 'error':
40 return append(lines, state, {kind: 'error', text: event.message ?? 'Unknown error'});
41 default:
42 return {lines, state};
43 }
44 }
45
46 function append(lines: WorkspaceLine[], state: MappingState, line: WorkspaceLine): {lines: WorkspaceLine[]; state: MappingState} {
47 return {lines: [...lines, line], state: {...state, assistantStreaming: false}};
48 }
49
50 function appendAssistantToken(lines: WorkspaceLine[], state: MappingState, delta: string): {lines: WorkspaceLine[]; state: MappingState} {
51 if (!delta) return {lines, state: {...state, assistantStreaming: true}};
52 const next = [...lines];
53 const last = next[next.length - 1];
54 if (state.assistantStreaming && last?.kind === 'assistant') {
55 next[next.length - 1] = {...last, text: `${last.text}${delta}`};
56 } else {
57 next.push({kind: 'assistant', text: delta});
58 }
59 return {lines: next, state: {assistantStreaming: true}};
60 }
61
62
63 function compactJoin(parts: Array<string | undefined | null>): string {
64 return parts.map((part) => String(part ?? '').trim()).filter(Boolean).join(': ');
65 }
66
67
68 function cleanToolError(content: string | undefined): string {
69 return String(content ?? '').replace(/\s+/g, ' ').trim();
70 }
71
71 lines TYPESCRIPT