zerg-ztc 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +63 -2
- package/dist/App.js.map +1 -1
- package/dist/agent/commands/dictation.d.ts +3 -0
- package/dist/agent/commands/dictation.d.ts.map +1 -0
- package/dist/agent/commands/dictation.js +10 -0
- package/dist/agent/commands/dictation.js.map +1 -0
- package/dist/agent/commands/index.d.ts.map +1 -1
- package/dist/agent/commands/index.js +2 -1
- package/dist/agent/commands/index.js.map +1 -1
- package/dist/agent/commands/types.d.ts +7 -0
- package/dist/agent/commands/types.d.ts.map +1 -1
- package/dist/components/InputArea.d.ts +1 -0
- package/dist/components/InputArea.d.ts.map +1 -1
- package/dist/components/InputArea.js +591 -43
- package/dist/components/InputArea.js.map +1 -1
- package/dist/components/SingleMessage.d.ts.map +1 -1
- package/dist/components/SingleMessage.js +157 -7
- package/dist/components/SingleMessage.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/ui/views/status_bar.js +2 -2
- package/dist/ui/views/status_bar.js.map +1 -1
- package/dist/utils/dictation.d.ts +46 -0
- package/dist/utils/dictation.d.ts.map +1 -0
- package/dist/utils/dictation.js +409 -0
- package/dist/utils/dictation.js.map +1 -0
- package/dist/utils/dictation_native.d.ts +51 -0
- package/dist/utils/dictation_native.d.ts.map +1 -0
- package/dist/utils/dictation_native.js +216 -0
- package/dist/utils/dictation_native.js.map +1 -0
- package/dist/utils/path_format.d.ts +20 -0
- package/dist/utils/path_format.d.ts.map +1 -0
- package/dist/utils/path_format.js +90 -0
- package/dist/utils/path_format.js.map +1 -0
- package/dist/utils/table.d.ts +38 -0
- package/dist/utils/table.d.ts.map +1 -0
- package/dist/utils/table.js +133 -0
- package/dist/utils/table.js.map +1 -0
- package/dist/utils/tool_trace.d.ts +7 -2
- package/dist/utils/tool_trace.d.ts.map +1 -1
- package/dist/utils/tool_trace.js +156 -51
- package/dist/utils/tool_trace.js.map +1 -1
- package/package.json +4 -1
- package/packages/ztc-dictation/Cargo.toml +43 -0
- package/packages/ztc-dictation/README.md +65 -0
- package/packages/ztc-dictation/bin/.gitkeep +0 -0
- package/packages/ztc-dictation/index.d.ts +16 -0
- package/packages/ztc-dictation/index.js +74 -0
- package/packages/ztc-dictation/package.json +41 -0
- package/packages/ztc-dictation/src/main.rs +430 -0
- package/src/App.tsx +98 -1
- package/src/agent/commands/dictation.ts +11 -0
- package/src/agent/commands/index.ts +2 -0
- package/src/agent/commands/types.ts +8 -0
- package/src/components/InputArea.tsx +606 -42
- package/src/components/SingleMessage.tsx +248 -9
- package/src/config/types.ts +7 -0
- package/src/ui/views/status_bar.ts +2 -2
- package/src/utils/dictation.ts +467 -0
- package/src/utils/dictation_native.ts +258 -0
- package/src/utils/path_format.ts +99 -0
- package/src/utils/table.ts +171 -0
- package/src/utils/tool_trace.ts +184 -54
|
@@ -2,6 +2,111 @@ import React from 'react';
|
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Message } from '../types.js';
|
|
4
4
|
|
|
5
|
+
// Box-drawing characters for tables
|
|
6
|
+
const BOX = {
|
|
7
|
+
topLeft: '╭',
|
|
8
|
+
topRight: '╮',
|
|
9
|
+
bottomLeft: '╰',
|
|
10
|
+
bottomRight: '╯',
|
|
11
|
+
horizontal: '─',
|
|
12
|
+
vertical: '│',
|
|
13
|
+
leftT: '├',
|
|
14
|
+
rightT: '┤',
|
|
15
|
+
topT: '┬',
|
|
16
|
+
bottomT: '┴',
|
|
17
|
+
cross: '┼'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface ParsedTable {
|
|
21
|
+
headers: string[];
|
|
22
|
+
rows: string[][];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseMarkdownTable(lines: string[]): ParsedTable | null {
|
|
26
|
+
// Need at least header row and separator row
|
|
27
|
+
if (lines.length < 2) return null;
|
|
28
|
+
|
|
29
|
+
const headerLine = lines[0];
|
|
30
|
+
const separatorLine = lines[1];
|
|
31
|
+
|
|
32
|
+
// Check if it looks like a markdown table
|
|
33
|
+
if (!headerLine.includes('|') || !separatorLine.match(/^\|?[\s\-:|]+\|?$/)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parseRow = (line: string): string[] => {
|
|
38
|
+
return line
|
|
39
|
+
.split('|')
|
|
40
|
+
.map(cell => cell.trim())
|
|
41
|
+
.filter((_, i, arr) => i > 0 && i < arr.length - 1 || (arr.length === 2 && i === 0));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const headers = parseRow(headerLine);
|
|
45
|
+
if (headers.length === 0) return null;
|
|
46
|
+
|
|
47
|
+
const rows: string[][] = [];
|
|
48
|
+
for (let i = 2; i < lines.length; i++) {
|
|
49
|
+
if (!lines[i].includes('|')) break;
|
|
50
|
+
rows.push(parseRow(lines[i]));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { headers, rows };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderBoxTable(table: ParsedTable): string[] {
|
|
57
|
+
const { headers, rows } = table;
|
|
58
|
+
|
|
59
|
+
// Calculate column widths
|
|
60
|
+
const widths = headers.map((h, i) => {
|
|
61
|
+
const dataMax = rows.reduce((max, row) => {
|
|
62
|
+
const cell = row[i] || '';
|
|
63
|
+
return Math.max(max, cell.length);
|
|
64
|
+
}, 0);
|
|
65
|
+
return Math.max(h.length, dataMax);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const output: string[] = [];
|
|
69
|
+
|
|
70
|
+
// Top border
|
|
71
|
+
output.push(
|
|
72
|
+
BOX.topLeft +
|
|
73
|
+
widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.topT) +
|
|
74
|
+
BOX.topRight
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Header row
|
|
78
|
+
output.push(
|
|
79
|
+
BOX.vertical +
|
|
80
|
+
headers.map((h, i) => ' ' + h.padEnd(widths[i]) + ' ').join(BOX.vertical) +
|
|
81
|
+
BOX.vertical
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Header separator
|
|
85
|
+
output.push(
|
|
86
|
+
BOX.leftT +
|
|
87
|
+
widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.cross) +
|
|
88
|
+
BOX.rightT
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Data rows
|
|
92
|
+
for (const row of rows) {
|
|
93
|
+
output.push(
|
|
94
|
+
BOX.vertical +
|
|
95
|
+
widths.map((w, i) => ' ' + (row[i] || '').padEnd(w) + ' ').join(BOX.vertical) +
|
|
96
|
+
BOX.vertical
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Bottom border
|
|
101
|
+
output.push(
|
|
102
|
+
BOX.bottomLeft +
|
|
103
|
+
widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.bottomT) +
|
|
104
|
+
BOX.bottomRight
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
109
|
+
|
|
5
110
|
interface SingleMessageProps {
|
|
6
111
|
message: Message;
|
|
7
112
|
expandToolOutputs?: boolean;
|
|
@@ -28,17 +133,149 @@ function formatTime(date: Date): string {
|
|
|
28
133
|
});
|
|
29
134
|
}
|
|
30
135
|
|
|
136
|
+
interface DiffLine {
|
|
137
|
+
type: 'add' | 'remove' | 'context';
|
|
138
|
+
text: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface ToolOutput {
|
|
142
|
+
preview: string;
|
|
143
|
+
full: string;
|
|
144
|
+
truncated: boolean;
|
|
145
|
+
diffLines?: DiffLine[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Render a line with diff coloring
|
|
149
|
+
const DiffLineComponent: React.FC<{ line: DiffLine; lineNum?: number }> = ({ line, lineNum }) => {
|
|
150
|
+
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
|
|
151
|
+
const color = line.type === 'add' ? 'green' : line.type === 'remove' ? 'red' : undefined;
|
|
152
|
+
const lineNumStr = lineNum !== undefined ? `${String(lineNum).padStart(4)} ` : '';
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Text color={color}>
|
|
156
|
+
{' '}{lineNumStr}{prefix} {line.text}
|
|
157
|
+
</Text>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Render content with diff awareness and markdown table support
|
|
162
|
+
const ContentRenderer: React.FC<{
|
|
163
|
+
content: string;
|
|
164
|
+
diffLines?: DiffLine[];
|
|
165
|
+
isToolTrace: boolean;
|
|
166
|
+
}> = ({ content, diffLines, isToolTrace }) => {
|
|
167
|
+
// If we have structured diff lines, render them with colors
|
|
168
|
+
if (diffLines && diffLines.length > 0 && isToolTrace) {
|
|
169
|
+
const lines = content.split('\n');
|
|
170
|
+
const summaryLine = lines[0] || '';
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<Box flexDirection="column">
|
|
174
|
+
<Text>{summaryLine}</Text>
|
|
175
|
+
{diffLines.map((line, i) => (
|
|
176
|
+
<DiffLineComponent key={i} line={line} />
|
|
177
|
+
))}
|
|
178
|
+
</Box>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Parse content into segments (text and tables)
|
|
183
|
+
const lines = content.split('\n');
|
|
184
|
+
const segments: Array<{ type: 'text' | 'table'; lines: string[] }> = [];
|
|
185
|
+
let i = 0;
|
|
186
|
+
|
|
187
|
+
while (i < lines.length) {
|
|
188
|
+
const line = lines[i];
|
|
189
|
+
|
|
190
|
+
// Check if this looks like the start of a markdown table
|
|
191
|
+
if (line.includes('|') && i + 1 < lines.length && lines[i + 1].match(/^\|?[\s\-:|]+\|?$/)) {
|
|
192
|
+
// Collect all table lines
|
|
193
|
+
const tableLines: string[] = [line];
|
|
194
|
+
let j = i + 1;
|
|
195
|
+
while (j < lines.length && lines[j].includes('|')) {
|
|
196
|
+
tableLines.push(lines[j]);
|
|
197
|
+
j++;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const parsed = parseMarkdownTable(tableLines);
|
|
201
|
+
if (parsed && parsed.rows.length > 0) {
|
|
202
|
+
const boxLines = renderBoxTable(parsed);
|
|
203
|
+
segments.push({ type: 'table', lines: boxLines });
|
|
204
|
+
i = j;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Regular text line
|
|
210
|
+
if (segments.length === 0 || segments[segments.length - 1].type !== 'text') {
|
|
211
|
+
segments.push({ type: 'text', lines: [] });
|
|
212
|
+
}
|
|
213
|
+
segments[segments.length - 1].lines.push(line);
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Box flexDirection="column">
|
|
219
|
+
{segments.map((segment, segIdx) => (
|
|
220
|
+
<Box key={segIdx} flexDirection="column">
|
|
221
|
+
{segment.lines.map((line, lineIdx) => {
|
|
222
|
+
// Color diff lines in output
|
|
223
|
+
if (isToolTrace) {
|
|
224
|
+
const trimmed = line.trimStart();
|
|
225
|
+
if (trimmed.startsWith('+ ') && !trimmed.startsWith('+ …')) {
|
|
226
|
+
return <Text key={lineIdx} color="green">{line}</Text>;
|
|
227
|
+
}
|
|
228
|
+
if (trimmed.startsWith('- ')) {
|
|
229
|
+
return <Text key={lineIdx} color="red">{line}</Text>;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Table lines get a subtle color
|
|
233
|
+
if (segment.type === 'table') {
|
|
234
|
+
return <Text key={lineIdx} color="cyan">{line}</Text>;
|
|
235
|
+
}
|
|
236
|
+
return <Text key={lineIdx}>{line}</Text>;
|
|
237
|
+
})}
|
|
238
|
+
</Box>
|
|
239
|
+
))}
|
|
240
|
+
</Box>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
31
244
|
export const SingleMessage: React.FC<SingleMessageProps> = ({
|
|
32
245
|
message,
|
|
33
246
|
expandToolOutputs = false
|
|
34
247
|
}) => {
|
|
35
248
|
const config = roleConfigs[message.role] || roleConfigs.system;
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
249
|
+
const metadata = message.metadata as Record<string, unknown> | undefined;
|
|
250
|
+
const toolOutput = metadata?.toolOutput as ToolOutput | undefined;
|
|
251
|
+
|
|
252
|
+
// Determine content to show
|
|
253
|
+
let content: string;
|
|
254
|
+
let diffLines: DiffLine[] | undefined;
|
|
255
|
+
|
|
256
|
+
if (toolOutput) {
|
|
257
|
+
content = expandToolOutputs && toolOutput.full ? toolOutput.full : toolOutput.preview;
|
|
258
|
+
diffLines = toolOutput.diffLines;
|
|
259
|
+
} else {
|
|
260
|
+
content = message.content;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const isToolTrace = message.role === 'tool';
|
|
264
|
+
|
|
265
|
+
// For tool traces, use more compact display
|
|
266
|
+
if (isToolTrace) {
|
|
267
|
+
return (
|
|
268
|
+
<Box flexDirection="column" marginY={0} paddingX={1}>
|
|
269
|
+
<Box flexDirection="column" marginLeft={1}>
|
|
270
|
+
<ContentRenderer
|
|
271
|
+
content={content}
|
|
272
|
+
diffLines={diffLines}
|
|
273
|
+
isToolTrace={true}
|
|
274
|
+
/>
|
|
275
|
+
</Box>
|
|
276
|
+
</Box>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
42
279
|
|
|
43
280
|
return (
|
|
44
281
|
<Box flexDirection="column" marginY={1} paddingX={1}>
|
|
@@ -48,9 +285,11 @@ export const SingleMessage: React.FC<SingleMessageProps> = ({
|
|
|
48
285
|
{message.isStreaming && <Text color="yellow" bold> ▌</Text>}
|
|
49
286
|
</Box>
|
|
50
287
|
<Box flexDirection="column" marginLeft={2}>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
288
|
+
<ContentRenderer
|
|
289
|
+
content={content}
|
|
290
|
+
diffLines={diffLines}
|
|
291
|
+
isToolTrace={false}
|
|
292
|
+
/>
|
|
54
293
|
</Box>
|
|
55
294
|
</Box>
|
|
56
295
|
);
|
package/src/config/types.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface DictationSettings {
|
|
2
|
+
provider?: 'openai' | 'local' | 'macos';
|
|
3
|
+
language?: string; // e.g., 'en', 'es', 'fr'
|
|
4
|
+
whisperModel?: string; // For local whisper: 'tiny', 'base', 'small', 'medium', 'large'
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
export interface ZTCConfig {
|
|
2
8
|
apiKey?: string;
|
|
3
9
|
apiKeys?: Record<string, string>;
|
|
@@ -9,4 +15,5 @@ export interface ZTCConfig {
|
|
|
9
15
|
emulationId?: string;
|
|
10
16
|
spinnerVerbs?: string[];
|
|
11
17
|
toolPermissions?: Partial<Record<'file_read' | 'file_write' | 'shell_exec' | 'network', boolean>>;
|
|
18
|
+
dictation?: DictationSettings;
|
|
12
19
|
}
|
|
@@ -67,8 +67,8 @@ export function buildStatusBarView({
|
|
|
67
67
|
|
|
68
68
|
return box([
|
|
69
69
|
box([
|
|
70
|
-
text(isActive ? '⠋
|
|
71
|
-
text(label
|
|
70
|
+
text(isActive ? '⠋' : config.icon, { color: config.color }),
|
|
71
|
+
text(` ${label}`, { color: config.color }),
|
|
72
72
|
state.error ? text(` - ${state.error}`, { color: 'red' }) : text('', {}),
|
|
73
73
|
toastLabel ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
74
74
|
toastLabel ? text(toastLabel, { color: 'yellow' }) : text('', {})
|