@ridit/lens 0.1.7 → 0.1.8
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/index.mjs +1073 -103
- package/hello.py +51 -0
- package/package.json +3 -3
- package/src/colors.ts +9 -0
- package/src/components/chat/ChatMessage.tsx +567 -66
- package/src/components/chat/ChatRunner.tsx +44 -8
- package/src/utils/chat.ts +89 -29
- package/skills.json +0 -7
|
@@ -1,29 +1,415 @@
|
|
|
1
|
+
// StaticMessage.tsx
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { Box, Text } from "ink";
|
|
3
|
-
import {
|
|
4
|
+
import { tokenize } from "sugar-high";
|
|
5
|
+
import {
|
|
6
|
+
ACCENT,
|
|
7
|
+
TOKEN_KEYWORD,
|
|
8
|
+
TOKEN_STRING,
|
|
9
|
+
TOKEN_NUMBER,
|
|
10
|
+
TOKEN_PROPERTY,
|
|
11
|
+
TOKEN_ENTITY,
|
|
12
|
+
TOKEN_TEXT,
|
|
13
|
+
TOKEN_MUTED,
|
|
14
|
+
TOKEN_COMMENT,
|
|
15
|
+
} from "../../colors";
|
|
4
16
|
import type { Message } from "../../types/chat";
|
|
5
17
|
|
|
18
|
+
const T_IDENTIFIER = 0;
|
|
19
|
+
const T_KEYWORD = 1;
|
|
20
|
+
const T_STRING = 2;
|
|
21
|
+
const T_CLS_NUMBER = 3;
|
|
22
|
+
const T_PROPERTY = 4;
|
|
23
|
+
const T_ENTITY = 5;
|
|
24
|
+
const T_JSX_LITERAL = 6;
|
|
25
|
+
const T_SIGN = 7;
|
|
26
|
+
const T_COMMENT = 8;
|
|
27
|
+
const T_BREAK = 9;
|
|
28
|
+
const T_SPACE = 10;
|
|
29
|
+
|
|
30
|
+
const JS_LANGS = new Set([
|
|
31
|
+
"js",
|
|
32
|
+
"javascript",
|
|
33
|
+
"jsx",
|
|
34
|
+
"ts",
|
|
35
|
+
"typescript",
|
|
36
|
+
"tsx",
|
|
37
|
+
"mjs",
|
|
38
|
+
"cjs",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
function tokenColor(type: number): string {
|
|
42
|
+
switch (type) {
|
|
43
|
+
case T_KEYWORD:
|
|
44
|
+
return TOKEN_KEYWORD;
|
|
45
|
+
case T_STRING:
|
|
46
|
+
return TOKEN_STRING;
|
|
47
|
+
case T_CLS_NUMBER:
|
|
48
|
+
return TOKEN_NUMBER;
|
|
49
|
+
case T_PROPERTY:
|
|
50
|
+
return TOKEN_PROPERTY;
|
|
51
|
+
case T_ENTITY:
|
|
52
|
+
return TOKEN_ENTITY;
|
|
53
|
+
case T_JSX_LITERAL:
|
|
54
|
+
return TOKEN_TEXT;
|
|
55
|
+
case T_SIGN:
|
|
56
|
+
return TOKEN_MUTED;
|
|
57
|
+
case T_COMMENT:
|
|
58
|
+
return TOKEN_COMMENT;
|
|
59
|
+
case T_IDENTIFIER:
|
|
60
|
+
return TOKEN_TEXT;
|
|
61
|
+
default:
|
|
62
|
+
return TOKEN_TEXT;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type SimpleToken = { color: string; text: string };
|
|
67
|
+
|
|
68
|
+
const PYTHON_KW = new Set([
|
|
69
|
+
"def",
|
|
70
|
+
"class",
|
|
71
|
+
"import",
|
|
72
|
+
"from",
|
|
73
|
+
"return",
|
|
74
|
+
"if",
|
|
75
|
+
"elif",
|
|
76
|
+
"else",
|
|
77
|
+
"for",
|
|
78
|
+
"while",
|
|
79
|
+
"in",
|
|
80
|
+
"not",
|
|
81
|
+
"and",
|
|
82
|
+
"or",
|
|
83
|
+
"is",
|
|
84
|
+
"None",
|
|
85
|
+
"True",
|
|
86
|
+
"False",
|
|
87
|
+
"try",
|
|
88
|
+
"except",
|
|
89
|
+
"finally",
|
|
90
|
+
"with",
|
|
91
|
+
"as",
|
|
92
|
+
"pass",
|
|
93
|
+
"break",
|
|
94
|
+
"continue",
|
|
95
|
+
"raise",
|
|
96
|
+
"yield",
|
|
97
|
+
"lambda",
|
|
98
|
+
"async",
|
|
99
|
+
"await",
|
|
100
|
+
"del",
|
|
101
|
+
"global",
|
|
102
|
+
"nonlocal",
|
|
103
|
+
"assert",
|
|
104
|
+
]);
|
|
105
|
+
const RUST_KW = new Set([
|
|
106
|
+
"fn",
|
|
107
|
+
"let",
|
|
108
|
+
"mut",
|
|
109
|
+
"const",
|
|
110
|
+
"struct",
|
|
111
|
+
"enum",
|
|
112
|
+
"impl",
|
|
113
|
+
"trait",
|
|
114
|
+
"pub",
|
|
115
|
+
"use",
|
|
116
|
+
"mod",
|
|
117
|
+
"match",
|
|
118
|
+
"if",
|
|
119
|
+
"else",
|
|
120
|
+
"loop",
|
|
121
|
+
"while",
|
|
122
|
+
"for",
|
|
123
|
+
"in",
|
|
124
|
+
"return",
|
|
125
|
+
"self",
|
|
126
|
+
"Self",
|
|
127
|
+
"super",
|
|
128
|
+
"where",
|
|
129
|
+
"type",
|
|
130
|
+
"as",
|
|
131
|
+
"ref",
|
|
132
|
+
"move",
|
|
133
|
+
"unsafe",
|
|
134
|
+
"extern",
|
|
135
|
+
"dyn",
|
|
136
|
+
"async",
|
|
137
|
+
"await",
|
|
138
|
+
"true",
|
|
139
|
+
"false",
|
|
140
|
+
"Some",
|
|
141
|
+
"None",
|
|
142
|
+
"Ok",
|
|
143
|
+
"Err",
|
|
144
|
+
]);
|
|
145
|
+
const GO_KW = new Set([
|
|
146
|
+
"func",
|
|
147
|
+
"var",
|
|
148
|
+
"const",
|
|
149
|
+
"type",
|
|
150
|
+
"struct",
|
|
151
|
+
"interface",
|
|
152
|
+
"package",
|
|
153
|
+
"import",
|
|
154
|
+
"return",
|
|
155
|
+
"if",
|
|
156
|
+
"else",
|
|
157
|
+
"for",
|
|
158
|
+
"range",
|
|
159
|
+
"switch",
|
|
160
|
+
"case",
|
|
161
|
+
"default",
|
|
162
|
+
"break",
|
|
163
|
+
"continue",
|
|
164
|
+
"goto",
|
|
165
|
+
"defer",
|
|
166
|
+
"go",
|
|
167
|
+
"chan",
|
|
168
|
+
"map",
|
|
169
|
+
"make",
|
|
170
|
+
"new",
|
|
171
|
+
"nil",
|
|
172
|
+
"true",
|
|
173
|
+
"false",
|
|
174
|
+
"error",
|
|
175
|
+
]);
|
|
176
|
+
const SHELL_KW = new Set([
|
|
177
|
+
"if",
|
|
178
|
+
"then",
|
|
179
|
+
"else",
|
|
180
|
+
"elif",
|
|
181
|
+
"fi",
|
|
182
|
+
"for",
|
|
183
|
+
"do",
|
|
184
|
+
"done",
|
|
185
|
+
"while",
|
|
186
|
+
"case",
|
|
187
|
+
"esac",
|
|
188
|
+
"in",
|
|
189
|
+
"function",
|
|
190
|
+
"return",
|
|
191
|
+
"echo",
|
|
192
|
+
"export",
|
|
193
|
+
"local",
|
|
194
|
+
"source",
|
|
195
|
+
"exit",
|
|
196
|
+
]);
|
|
197
|
+
const CSS_AT = /^@[\w-]+/;
|
|
198
|
+
const CSS_PROP = /^[\w-]+(?=\s*:)/;
|
|
199
|
+
|
|
200
|
+
function tokenizeGeneric(code: string, lang: string): SimpleToken[][] {
|
|
201
|
+
const keywords =
|
|
202
|
+
lang === "python" || lang === "py"
|
|
203
|
+
? PYTHON_KW
|
|
204
|
+
: lang === "rust" || lang === "rs"
|
|
205
|
+
? RUST_KW
|
|
206
|
+
: lang === "go"
|
|
207
|
+
? GO_KW
|
|
208
|
+
: lang === "bash" ||
|
|
209
|
+
lang === "sh" ||
|
|
210
|
+
lang === "shell" ||
|
|
211
|
+
lang === "zsh"
|
|
212
|
+
? SHELL_KW
|
|
213
|
+
: new Set<string>();
|
|
214
|
+
|
|
215
|
+
const lines = code.split("\n");
|
|
216
|
+
return lines.map((line) => {
|
|
217
|
+
const tokens: SimpleToken[] = [];
|
|
218
|
+
let i = 0;
|
|
219
|
+
|
|
220
|
+
const push = (color: string, text: string) => {
|
|
221
|
+
if (text) tokens.push({ color, text });
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
while (i < line.length) {
|
|
225
|
+
const rest = line.slice(i);
|
|
226
|
+
|
|
227
|
+
const commentPrefixes =
|
|
228
|
+
lang === "python" || lang === "py"
|
|
229
|
+
? ["#"]
|
|
230
|
+
: lang === "bash" ||
|
|
231
|
+
lang === "sh" ||
|
|
232
|
+
lang === "shell" ||
|
|
233
|
+
lang === "zsh"
|
|
234
|
+
? ["#"]
|
|
235
|
+
: lang === "css" || lang === "scss"
|
|
236
|
+
? ["//", "/*"]
|
|
237
|
+
: lang === "html" || lang === "xml"
|
|
238
|
+
? ["<!--"]
|
|
239
|
+
: lang === "sql"
|
|
240
|
+
? ["--", "#"]
|
|
241
|
+
: ["//", "#"];
|
|
242
|
+
|
|
243
|
+
let matchedComment = false;
|
|
244
|
+
for (const prefix of commentPrefixes) {
|
|
245
|
+
if (rest.startsWith(prefix)) {
|
|
246
|
+
push(TOKEN_COMMENT, line.slice(i));
|
|
247
|
+
i = line.length;
|
|
248
|
+
matchedComment = true;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (matchedComment) continue;
|
|
253
|
+
|
|
254
|
+
if (line[i] === '"' || line[i] === "'" || line[i] === "`") {
|
|
255
|
+
const quote = line[i]!;
|
|
256
|
+
let j = i + 1;
|
|
257
|
+
while (j < line.length) {
|
|
258
|
+
if (line[j] === "\\") {
|
|
259
|
+
j += 2;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (line[j] === quote) {
|
|
263
|
+
j++;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
j++;
|
|
267
|
+
}
|
|
268
|
+
push(TOKEN_STRING, line.slice(i, j));
|
|
269
|
+
i = j;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const numMatch = rest.match(/^-?\d+\.?\d*/);
|
|
274
|
+
if (numMatch && (i === 0 || !/\w/.test(line[i - 1] ?? ""))) {
|
|
275
|
+
push(TOKEN_NUMBER, numMatch[0]);
|
|
276
|
+
i += numMatch[0].length;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (lang === "css" || lang === "scss") {
|
|
281
|
+
const atMatch = rest.match(CSS_AT);
|
|
282
|
+
if (atMatch) {
|
|
283
|
+
push(TOKEN_KEYWORD, atMatch[0]);
|
|
284
|
+
i += atMatch[0].length;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const propMatch = rest.match(CSS_PROP);
|
|
288
|
+
if (propMatch) {
|
|
289
|
+
push(TOKEN_PROPERTY, propMatch[0]);
|
|
290
|
+
i += propMatch[0].length;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if ((lang === "html" || lang === "xml") && line[i] === "<") {
|
|
296
|
+
const tagMatch = rest.match(/^<\/?[\w:-]+/);
|
|
297
|
+
if (tagMatch) {
|
|
298
|
+
push(TOKEN_ENTITY, tagMatch[0]);
|
|
299
|
+
i += tagMatch[0].length;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const wordMatch = rest.match(/^[a-zA-Z_$][\w$]*/);
|
|
305
|
+
if (wordMatch) {
|
|
306
|
+
const word = wordMatch[0];
|
|
307
|
+
push(keywords.has(word) ? TOKEN_KEYWORD : TOKEN_TEXT, word);
|
|
308
|
+
i += word.length;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const opMatch = rest.match(/^[^\w\s"'`]+/);
|
|
313
|
+
if (opMatch) {
|
|
314
|
+
push(TOKEN_MUTED, opMatch[0]);
|
|
315
|
+
i += opMatch[0].length;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
push(TOKEN_TEXT, line[i]!);
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return tokens;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function HighlightedLine({ tokens }: { tokens: SimpleToken[] }) {
|
|
328
|
+
return (
|
|
329
|
+
<Text>
|
|
330
|
+
{" "}
|
|
331
|
+
{tokens.map((t, i) => (
|
|
332
|
+
<Text key={i} color={t.color as any}>
|
|
333
|
+
{t.text}
|
|
334
|
+
</Text>
|
|
335
|
+
))}
|
|
336
|
+
</Text>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function CodeBlock({ lang, code }: { lang: string; code: string }) {
|
|
341
|
+
const normalizedLang = lang.toLowerCase().trim();
|
|
342
|
+
|
|
343
|
+
let lines: SimpleToken[][];
|
|
344
|
+
|
|
345
|
+
if (JS_LANGS.has(normalizedLang)) {
|
|
346
|
+
const tokens = tokenize(code);
|
|
347
|
+
const lineAccum: SimpleToken[][] = [[]];
|
|
348
|
+
|
|
349
|
+
for (const [type, value] of tokens) {
|
|
350
|
+
if (type === T_BREAK) {
|
|
351
|
+
lineAccum.push([]);
|
|
352
|
+
} else if (type !== T_SPACE) {
|
|
353
|
+
lineAccum[lineAccum.length - 1]!.push({
|
|
354
|
+
color: tokenColor(type),
|
|
355
|
+
text: value,
|
|
356
|
+
});
|
|
357
|
+
} else {
|
|
358
|
+
lineAccum[lineAccum.length - 1]!.push({
|
|
359
|
+
color: TOKEN_TEXT,
|
|
360
|
+
text: value,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
lines = lineAccum;
|
|
365
|
+
} else if (normalizedLang) {
|
|
366
|
+
lines = tokenizeGeneric(code, normalizedLang);
|
|
367
|
+
} else {
|
|
368
|
+
lines = code.split("\n").map((l) => [{ color: TOKEN_TEXT, text: l }]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<Box flexDirection="column" marginY={1} marginLeft={2}>
|
|
373
|
+
{normalizedLang ? (
|
|
374
|
+
<Text color={TOKEN_MUTED} dimColor>
|
|
375
|
+
{normalizedLang}
|
|
376
|
+
</Text>
|
|
377
|
+
) : null}
|
|
378
|
+
{lines.map((lineTokens, i) => (
|
|
379
|
+
<HighlightedLine key={i} tokens={lineTokens} />
|
|
380
|
+
))}
|
|
381
|
+
</Box>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
6
385
|
function InlineText({ text }: { text: string }) {
|
|
7
|
-
const parts = text.split(/(
|
|
386
|
+
const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g);
|
|
8
387
|
return (
|
|
9
388
|
<>
|
|
10
389
|
{parts.map((part, i) => {
|
|
11
|
-
if (part.startsWith("
|
|
390
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
12
391
|
return (
|
|
13
|
-
<Text key={i} color={
|
|
392
|
+
<Text key={i} bold color={TOKEN_TEXT}>
|
|
393
|
+
{part.slice(2, -2)}
|
|
394
|
+
</Text>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (part.startsWith("*") && part.endsWith("*") && part.length > 2) {
|
|
398
|
+
return (
|
|
399
|
+
<Text key={i} italic color={TOKEN_TEXT}>
|
|
14
400
|
{part.slice(1, -1)}
|
|
15
401
|
</Text>
|
|
16
402
|
);
|
|
17
403
|
}
|
|
18
|
-
if (part.startsWith("
|
|
404
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
19
405
|
return (
|
|
20
|
-
<Text key={i}
|
|
21
|
-
{part.slice(
|
|
406
|
+
<Text key={i} color={ACCENT}>
|
|
407
|
+
{part.slice(1, -1)}
|
|
22
408
|
</Text>
|
|
23
409
|
);
|
|
24
410
|
}
|
|
25
411
|
return (
|
|
26
|
-
<Text key={i} color=
|
|
412
|
+
<Text key={i} color={TOKEN_TEXT}>
|
|
27
413
|
{part}
|
|
28
414
|
</Text>
|
|
29
415
|
);
|
|
@@ -32,68 +418,177 @@ function InlineText({ text }: { text: string }) {
|
|
|
32
418
|
);
|
|
33
419
|
}
|
|
34
420
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{" "}
|
|
42
|
-
{line}
|
|
421
|
+
function Heading({ level, text }: { level: 1 | 2 | 3; text: string }) {
|
|
422
|
+
if (level === 1) {
|
|
423
|
+
return (
|
|
424
|
+
<Box marginTop={1}>
|
|
425
|
+
<Text color={ACCENT} bold underline>
|
|
426
|
+
{text}
|
|
43
427
|
</Text>
|
|
44
|
-
|
|
428
|
+
</Box>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (level === 2) {
|
|
432
|
+
return (
|
|
433
|
+
<Box marginTop={1}>
|
|
434
|
+
<Text color={ACCENT} bold>
|
|
435
|
+
{text}
|
|
436
|
+
</Text>
|
|
437
|
+
</Box>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
return (
|
|
441
|
+
<Box marginTop={1}>
|
|
442
|
+
<Text color={TOKEN_TEXT} bold>
|
|
443
|
+
{text}
|
|
444
|
+
</Text>
|
|
45
445
|
</Box>
|
|
46
446
|
);
|
|
47
447
|
}
|
|
48
448
|
|
|
49
|
-
function
|
|
449
|
+
function BulletItem({ text }: { text: string }) {
|
|
450
|
+
return (
|
|
451
|
+
<Box gap={1}>
|
|
452
|
+
<Text color={ACCENT}>{"*"}</Text>
|
|
453
|
+
<Box flexShrink={1}>
|
|
454
|
+
<InlineText text={text} />
|
|
455
|
+
</Box>
|
|
456
|
+
</Box>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function NumberedItem({ num, text }: { num: string; text: string }) {
|
|
461
|
+
return (
|
|
462
|
+
<Box gap={1}>
|
|
463
|
+
<Text color={TOKEN_MUTED}>{num}.</Text>
|
|
464
|
+
<Box flexShrink={1}>
|
|
465
|
+
<InlineText text={text} />
|
|
466
|
+
</Box>
|
|
467
|
+
</Box>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function BlockQuote({ text }: { text: string }) {
|
|
472
|
+
return (
|
|
473
|
+
<Box gap={1} marginLeft={1}>
|
|
474
|
+
<Text color={TOKEN_MUTED}>{"│"}</Text>
|
|
475
|
+
<Text color={TOKEN_MUTED} dimColor>
|
|
476
|
+
{text}
|
|
477
|
+
</Text>
|
|
478
|
+
</Box>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
type Block =
|
|
483
|
+
| { type: "heading"; level: 1 | 2 | 3; text: string }
|
|
484
|
+
| { type: "code"; lang: string; code: string }
|
|
485
|
+
| { type: "bullet"; text: string }
|
|
486
|
+
| { type: "numbered"; num: string; text: string }
|
|
487
|
+
| { type: "blockquote"; text: string }
|
|
488
|
+
| { type: "hr" }
|
|
489
|
+
| { type: "paragraph"; text: string };
|
|
490
|
+
|
|
491
|
+
function parseBlocks(content: string): Block[] {
|
|
492
|
+
const blocks: Block[] = [];
|
|
50
493
|
const segments = content.split(/(```[\s\S]*?```)/g);
|
|
51
494
|
|
|
495
|
+
for (const seg of segments) {
|
|
496
|
+
if (seg.startsWith("```")) {
|
|
497
|
+
const lines = seg.slice(3).split("\n");
|
|
498
|
+
const lang = lines[0]?.trim() ?? "";
|
|
499
|
+
const code = lines
|
|
500
|
+
.slice(1)
|
|
501
|
+
.join("\n")
|
|
502
|
+
.replace(/```\s*$/, "")
|
|
503
|
+
.trimEnd();
|
|
504
|
+
blocks.push({ type: "code", lang, code });
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
for (const line of seg.split("\n")) {
|
|
509
|
+
const trimmed = line.trim();
|
|
510
|
+
if (!trimmed) continue;
|
|
511
|
+
|
|
512
|
+
const h3 = trimmed.match(/^### (.+)$/);
|
|
513
|
+
const h2 = trimmed.match(/^## (.+)$/);
|
|
514
|
+
const h1 = trimmed.match(/^# (.+)$/);
|
|
515
|
+
if (h3) {
|
|
516
|
+
blocks.push({ type: "heading", level: 3, text: h3[1]! });
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (h2) {
|
|
520
|
+
blocks.push({ type: "heading", level: 2, text: h2[1]! });
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (h1) {
|
|
524
|
+
blocks.push({ type: "heading", level: 1, text: h1[1]! });
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (/^[-*_]{3,}$/.test(trimmed)) {
|
|
529
|
+
blocks.push({ type: "hr" });
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (trimmed.startsWith("> ")) {
|
|
534
|
+
blocks.push({ type: "blockquote", text: trimmed.slice(2).trim() });
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (/^[-*•]\s/.test(trimmed)) {
|
|
539
|
+
blocks.push({ type: "bullet", text: trimmed.slice(2).trim() });
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const numMatch = trimmed.match(/^(\d+)\.\s(.+)/);
|
|
544
|
+
if (numMatch) {
|
|
545
|
+
blocks.push({
|
|
546
|
+
type: "numbered",
|
|
547
|
+
num: numMatch[1]!,
|
|
548
|
+
text: numMatch[2]!,
|
|
549
|
+
});
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
blocks.push({ type: "paragraph", text: trimmed });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return blocks;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function MessageBody({ content }: { content: string }) {
|
|
561
|
+
const blocks = parseBlocks(content);
|
|
562
|
+
|
|
52
563
|
return (
|
|
53
564
|
<Box flexDirection="column">
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
565
|
+
{blocks.map((block, i) => {
|
|
566
|
+
switch (block.type) {
|
|
567
|
+
case "heading":
|
|
568
|
+
return <Heading key={i} level={block.level} text={block.text} />;
|
|
569
|
+
case "code":
|
|
570
|
+
return <CodeBlock key={i} lang={block.lang} code={block.code} />;
|
|
571
|
+
case "bullet":
|
|
572
|
+
return <BulletItem key={i} text={block.text} />;
|
|
573
|
+
case "numbered":
|
|
574
|
+
return <NumberedItem key={i} num={block.num} text={block.text} />;
|
|
575
|
+
case "blockquote":
|
|
576
|
+
return <BlockQuote key={i} text={block.text} />;
|
|
577
|
+
case "hr":
|
|
578
|
+
return (
|
|
579
|
+
<Box key={i} marginY={1}>
|
|
580
|
+
<Text color={TOKEN_MUTED} dimColor>
|
|
581
|
+
{"─".repeat(40)}
|
|
582
|
+
</Text>
|
|
583
|
+
</Box>
|
|
584
|
+
);
|
|
585
|
+
case "paragraph":
|
|
586
|
+
return (
|
|
587
|
+
<Box key={i}>
|
|
588
|
+
<InlineText text={block.text} />
|
|
589
|
+
</Box>
|
|
590
|
+
);
|
|
64
591
|
}
|
|
65
|
-
|
|
66
|
-
const lines = seg.split("\n").filter((l) => l.trim() !== "");
|
|
67
|
-
return (
|
|
68
|
-
<Box key={si} flexDirection="column">
|
|
69
|
-
{lines.map((line, li) => {
|
|
70
|
-
if (line.match(/^[-*•]\s/)) {
|
|
71
|
-
return (
|
|
72
|
-
<Box key={li} gap={1}>
|
|
73
|
-
<Text color={ACCENT}>*</Text>
|
|
74
|
-
<InlineText text={line.slice(2).trim()} />
|
|
75
|
-
</Box>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (line.match(/^\d+\.\s/)) {
|
|
80
|
-
const num = line.match(/^(\d+)\.\s/)![1];
|
|
81
|
-
return (
|
|
82
|
-
<Box key={li} gap={1}>
|
|
83
|
-
<Text color="gray">{num}.</Text>
|
|
84
|
-
<InlineText text={line.replace(/^\d+\.\s/, "").trim()} />
|
|
85
|
-
</Box>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<Box key={li}>
|
|
91
|
-
<InlineText text={line} />
|
|
92
|
-
</Box>
|
|
93
|
-
);
|
|
94
|
-
})}
|
|
95
|
-
</Box>
|
|
96
|
-
);
|
|
97
592
|
})}
|
|
98
593
|
</Box>
|
|
99
594
|
);
|
|
@@ -103,8 +598,8 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
103
598
|
if (msg.role === "user") {
|
|
104
599
|
return (
|
|
105
600
|
<Box marginBottom={1} gap={1}>
|
|
106
|
-
<Text color=
|
|
107
|
-
<Text color=
|
|
601
|
+
<Text color={TOKEN_MUTED}>{">"}</Text>
|
|
602
|
+
<Text color={TOKEN_TEXT} bold>
|
|
108
603
|
{msg.content}
|
|
109
604
|
</Text>
|
|
110
605
|
</Box>
|
|
@@ -137,14 +632,17 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
137
632
|
<Box flexDirection="column" marginBottom={1}>
|
|
138
633
|
<Box gap={1}>
|
|
139
634
|
<Text color={msg.approved ? ACCENT : "red"}>{icon}</Text>
|
|
140
|
-
<Text
|
|
635
|
+
<Text
|
|
636
|
+
color={msg.approved ? TOKEN_MUTED : "red"}
|
|
637
|
+
dimColor={!msg.approved}
|
|
638
|
+
>
|
|
141
639
|
{label}
|
|
142
640
|
</Text>
|
|
143
641
|
{!msg.approved && <Text color="red">denied</Text>}
|
|
144
642
|
</Box>
|
|
145
643
|
{msg.approved && msg.result && (
|
|
146
644
|
<Box marginLeft={2}>
|
|
147
|
-
<Text color=
|
|
645
|
+
<Text color={TOKEN_MUTED}>
|
|
148
646
|
{msg.result.split("\n")[0]?.slice(0, 120)}
|
|
149
647
|
{(msg.result.split("\n")[0]?.length ?? 0) > 120 ? "…" : ""}
|
|
150
648
|
</Text>
|
|
@@ -162,10 +660,13 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
162
660
|
<MessageBody content={msg.content} />
|
|
163
661
|
</Box>
|
|
164
662
|
<Box marginLeft={2} gap={1}>
|
|
165
|
-
<Text color={msg.applied ? "green" :
|
|
663
|
+
<Text color={msg.applied ? "green" : TOKEN_MUTED}>
|
|
166
664
|
{msg.applied ? "✓" : "·"}
|
|
167
665
|
</Text>
|
|
168
|
-
<Text
|
|
666
|
+
<Text
|
|
667
|
+
color={msg.applied ? "green" : TOKEN_MUTED}
|
|
668
|
+
dimColor={!msg.applied}
|
|
669
|
+
>
|
|
169
670
|
{msg.applied ? "changes applied" : "changes skipped"}
|
|
170
671
|
</Text>
|
|
171
672
|
</Box>
|