@10play/expo-air 0.12.1 → 0.12.2
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/cli/dist/server/promptServer.d.ts.map +1 -1
- package/cli/dist/server/promptServer.js +2 -0
- package/cli/dist/server/promptServer.js.map +1 -1
- package/ios/widget.jsbundle +11 -5
- package/package.json +1 -1
- package/widget/BubbleContent.tsx +50 -755
- package/widget/components/FormattedText.tsx +320 -0
- package/widget/components/Header.tsx +225 -0
- package/widget/components/MessageItems.tsx +308 -0
- package/widget/components/ResponseArea.tsx +3 -754
- package/widget/components/TabBar.tsx +161 -0
|
@@ -1,26 +1,7 @@
|
|
|
1
1
|
import React, { useRef, useEffect, useState, useCallback } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ScrollView,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
TouchableOpacity,
|
|
8
|
-
NativeScrollEvent,
|
|
9
|
-
Keyboard,
|
|
10
|
-
Platform,
|
|
11
|
-
Image,
|
|
12
|
-
Linking,
|
|
13
|
-
} from "react-native";
|
|
14
|
-
import type {
|
|
15
|
-
ServerMessage,
|
|
16
|
-
ToolMessage,
|
|
17
|
-
ResultMessage,
|
|
18
|
-
UserPromptMessage,
|
|
19
|
-
HistoryResultMessage,
|
|
20
|
-
SystemDisplayMessage,
|
|
21
|
-
AssistantPart,
|
|
22
|
-
AssistantPartsMessage,
|
|
23
|
-
} from "../services/websocket";
|
|
2
|
+
import { View, Text, ScrollView, StyleSheet, TouchableOpacity, NativeScrollEvent, Keyboard, Platform } from "react-native";
|
|
3
|
+
import type { ServerMessage, AssistantPart } from "../services/websocket";
|
|
4
|
+
import { MessageItem, PartsRenderer } from "./MessageItems";
|
|
24
5
|
import { SPACING, LAYOUT, COLORS, TYPOGRAPHY } from "../constants/design";
|
|
25
6
|
|
|
26
7
|
interface ResponseAreaProps {
|
|
@@ -146,514 +127,6 @@ export function ResponseArea({ messages, currentParts }: ResponseAreaProps) {
|
|
|
146
127
|
);
|
|
147
128
|
}
|
|
148
129
|
|
|
149
|
-
function MessageItem({ message }: { key?: React.Key; message: ServerMessage }) {
|
|
150
|
-
switch (message.type) {
|
|
151
|
-
case "stream":
|
|
152
|
-
return null; // Handled by currentParts
|
|
153
|
-
|
|
154
|
-
case "tool":
|
|
155
|
-
// Legacy: individual tool messages from history
|
|
156
|
-
return <ToolItem tool={message} />;
|
|
157
|
-
|
|
158
|
-
case "result":
|
|
159
|
-
return <ResultItem result={message} />;
|
|
160
|
-
|
|
161
|
-
case "error":
|
|
162
|
-
return (
|
|
163
|
-
<View style={styles.errorContainer}>
|
|
164
|
-
<Text style={styles.errorText}>{message.message}</Text>
|
|
165
|
-
</View>
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
case "status":
|
|
169
|
-
return null; // Handled by header
|
|
170
|
-
|
|
171
|
-
case "user_prompt":
|
|
172
|
-
return <UserPromptItem message={message} />;
|
|
173
|
-
|
|
174
|
-
case "history_result":
|
|
175
|
-
return <HistoryResultItem message={message} />;
|
|
176
|
-
|
|
177
|
-
case "assistant_parts":
|
|
178
|
-
return <AssistantPartsItem message={message} />;
|
|
179
|
-
|
|
180
|
-
case "system_message":
|
|
181
|
-
return <SystemMessageItem message={message} />;
|
|
182
|
-
|
|
183
|
-
default:
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function UserPromptItem({ message }: { message: UserPromptMessage }) {
|
|
189
|
-
return (
|
|
190
|
-
<View style={styles.userPromptContainer}>
|
|
191
|
-
{message.images && message.images.length > 0 && (
|
|
192
|
-
<View style={styles.userImages}>
|
|
193
|
-
{message.images.map((img, i) => (
|
|
194
|
-
<Image
|
|
195
|
-
key={i}
|
|
196
|
-
source={{ uri: img.uri }}
|
|
197
|
-
style={styles.userImageThumb}
|
|
198
|
-
/>
|
|
199
|
-
))}
|
|
200
|
-
</View>
|
|
201
|
-
)}
|
|
202
|
-
{message.content ? (
|
|
203
|
-
<Text style={styles.userPromptText} selectable>
|
|
204
|
-
{message.content}
|
|
205
|
-
</Text>
|
|
206
|
-
) : null}
|
|
207
|
-
</View>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function HistoryResultItem({ message }: { message: HistoryResultMessage }) {
|
|
212
|
-
return (
|
|
213
|
-
<View style={styles.resultContainer}>
|
|
214
|
-
<FormattedText content={message.content} />
|
|
215
|
-
</View>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function SystemMessageItem({ message }: { message: SystemDisplayMessage }) {
|
|
220
|
-
// Use error styling for errors, muted styling for other system messages
|
|
221
|
-
if (message.messageType === "error") {
|
|
222
|
-
return (
|
|
223
|
-
<View style={styles.errorContainer}>
|
|
224
|
-
<Text style={styles.errorText}>{message.content}</Text>
|
|
225
|
-
</View>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
// Stopped and info messages use muted styling
|
|
229
|
-
return (
|
|
230
|
-
<View style={styles.systemContainer}>
|
|
231
|
-
<Text style={styles.systemText}>{message.content}</Text>
|
|
232
|
-
</View>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Renders formatted text with markdown-style lists, code, and emphasis
|
|
237
|
-
function FormattedText({
|
|
238
|
-
content,
|
|
239
|
-
isStreaming,
|
|
240
|
-
}: {
|
|
241
|
-
content: string;
|
|
242
|
-
isStreaming?: boolean;
|
|
243
|
-
}) {
|
|
244
|
-
const lines = content.split("\n");
|
|
245
|
-
const elements: React.ReactNode[] = [];
|
|
246
|
-
let i = 0;
|
|
247
|
-
|
|
248
|
-
while (i < lines.length) {
|
|
249
|
-
const line = lines[i];
|
|
250
|
-
const trimmed = line.trim();
|
|
251
|
-
|
|
252
|
-
// Code block start (check first to avoid matching content inside code blocks)
|
|
253
|
-
if (trimmed.startsWith("```")) {
|
|
254
|
-
const codeLines: string[] = [];
|
|
255
|
-
const lang = trimmed.slice(3).trim();
|
|
256
|
-
i++;
|
|
257
|
-
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
|
258
|
-
codeLines.push(lines[i]);
|
|
259
|
-
i++;
|
|
260
|
-
}
|
|
261
|
-
elements.push(
|
|
262
|
-
<View key={`code-${i}`} style={styles.codeBlock}>
|
|
263
|
-
{lang && <Text style={styles.codeLang}>{lang}</Text>}
|
|
264
|
-
<Text style={styles.codeText} selectable>
|
|
265
|
-
{codeLines.join("\n")}
|
|
266
|
-
</Text>
|
|
267
|
-
</View>,
|
|
268
|
-
);
|
|
269
|
-
i++; // skip closing ```
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Heading (# ## ### etc.)
|
|
274
|
-
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
275
|
-
if (headingMatch) {
|
|
276
|
-
const level = headingMatch[1].length;
|
|
277
|
-
const headingStyle =
|
|
278
|
-
level <= 2
|
|
279
|
-
? styles.heading1
|
|
280
|
-
: level <= 4
|
|
281
|
-
? styles.heading2
|
|
282
|
-
: styles.heading3;
|
|
283
|
-
elements.push(
|
|
284
|
-
<Text key={i} style={headingStyle} selectable>
|
|
285
|
-
{formatInlineText(headingMatch[2])}
|
|
286
|
-
</Text>,
|
|
287
|
-
);
|
|
288
|
-
i++;
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Blockquote (> text) - collect consecutive > lines
|
|
293
|
-
if (trimmed.startsWith("> ") || trimmed === ">") {
|
|
294
|
-
const quoteLines: string[] = [];
|
|
295
|
-
while (
|
|
296
|
-
i < lines.length &&
|
|
297
|
-
(lines[i].trim().startsWith("> ") || lines[i].trim() === ">")
|
|
298
|
-
) {
|
|
299
|
-
const qContent = lines[i].trim().startsWith("> ")
|
|
300
|
-
? lines[i].trim().slice(2)
|
|
301
|
-
: "";
|
|
302
|
-
quoteLines.push(qContent);
|
|
303
|
-
i++;
|
|
304
|
-
}
|
|
305
|
-
elements.push(
|
|
306
|
-
<View key={`quote-${i}`} style={styles.blockquote}>
|
|
307
|
-
<Text style={styles.blockquoteText} selectable>
|
|
308
|
-
{formatInlineText(quoteLines.join("\n"))}
|
|
309
|
-
</Text>
|
|
310
|
-
</View>,
|
|
311
|
-
);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Task list item (- [ ] or - [x])
|
|
316
|
-
const taskMatch = trimmed.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
|
|
317
|
-
if (taskMatch) {
|
|
318
|
-
const checked = taskMatch[1] !== " ";
|
|
319
|
-
elements.push(
|
|
320
|
-
<View key={i} style={styles.listItem}>
|
|
321
|
-
<Text style={styles.listBullet}>{checked ? "☑" : "☐"}</Text>
|
|
322
|
-
<Text style={styles.listText} selectable>
|
|
323
|
-
{formatInlineText(taskMatch[2])}
|
|
324
|
-
</Text>
|
|
325
|
-
</View>,
|
|
326
|
-
);
|
|
327
|
-
i++;
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Numbered list item (1. 2. 3.) - with nesting via indentation
|
|
332
|
-
const numberedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
|
|
333
|
-
if (numberedMatch) {
|
|
334
|
-
const indent = Math.floor(numberedMatch[1].length / 2);
|
|
335
|
-
const [, , num, text] = numberedMatch;
|
|
336
|
-
elements.push(
|
|
337
|
-
<View
|
|
338
|
-
key={i}
|
|
339
|
-
style={[
|
|
340
|
-
styles.listItem,
|
|
341
|
-
indent > 0 && { paddingLeft: SPACING.SM + indent * SPACING.LG },
|
|
342
|
-
]}
|
|
343
|
-
>
|
|
344
|
-
<Text style={styles.listNumber}>{num}.</Text>
|
|
345
|
-
<Text style={styles.listText} selectable>
|
|
346
|
-
{formatInlineText(text)}
|
|
347
|
-
</Text>
|
|
348
|
-
</View>,
|
|
349
|
-
);
|
|
350
|
-
i++;
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Bullet list item (- or *) - with nesting via indentation
|
|
355
|
-
const bulletMatch = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
356
|
-
if (bulletMatch) {
|
|
357
|
-
const indent = Math.floor(bulletMatch[1].length / 2);
|
|
358
|
-
const bulletChar = indent === 0 ? "•" : indent === 1 ? "◦" : "▪";
|
|
359
|
-
elements.push(
|
|
360
|
-
<View
|
|
361
|
-
key={i}
|
|
362
|
-
style={[
|
|
363
|
-
styles.listItem,
|
|
364
|
-
indent > 0 && { paddingLeft: SPACING.SM + indent * SPACING.LG },
|
|
365
|
-
]}
|
|
366
|
-
>
|
|
367
|
-
<Text style={styles.listBullet}>{bulletChar}</Text>
|
|
368
|
-
<Text style={styles.listText} selectable>
|
|
369
|
-
{formatInlineText(bulletMatch[2])}
|
|
370
|
-
</Text>
|
|
371
|
-
</View>,
|
|
372
|
-
);
|
|
373
|
-
i++;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Horizontal rule (---, ***, ___)
|
|
378
|
-
if (/^[-*_]{3,}$/.test(trimmed)) {
|
|
379
|
-
elements.push(<View key={i} style={styles.horizontalRule} />);
|
|
380
|
-
i++;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Empty line = paragraph break
|
|
385
|
-
if (!trimmed) {
|
|
386
|
-
elements.push(<View key={i} style={styles.paragraphBreak} />);
|
|
387
|
-
i++;
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Regular text
|
|
392
|
-
elements.push(
|
|
393
|
-
<Text key={i} style={styles.responseText} selectable>
|
|
394
|
-
{formatInlineText(line)}
|
|
395
|
-
</Text>,
|
|
396
|
-
);
|
|
397
|
-
i++;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<View style={styles.formattedContainer}>
|
|
402
|
-
{elements}
|
|
403
|
-
{isStreaming && <View style={styles.cursor} />}
|
|
404
|
-
</View>
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Format inline elements: code spans first, then links, then emphasis
|
|
409
|
-
function formatInlineText(text: string): React.ReactNode {
|
|
410
|
-
// Split on inline code first to avoid processing markdown inside code spans
|
|
411
|
-
const codeParts = text.split(/(`[^`]+`)/g);
|
|
412
|
-
if (codeParts.length === 1) {
|
|
413
|
-
return formatLinks(text);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return codeParts.map((part, i) => {
|
|
417
|
-
if (part.startsWith("`") && part.endsWith("`")) {
|
|
418
|
-
return (
|
|
419
|
-
<Text key={i} style={styles.inlineCode}>
|
|
420
|
-
{part.slice(1, -1)}
|
|
421
|
-
</Text>
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
return <React.Fragment key={i}>{formatLinks(part)}</React.Fragment>;
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Format markdown links [text](url)
|
|
429
|
-
function formatLinks(text: string): React.ReactNode {
|
|
430
|
-
const parts = text.split(/(\[[^\]]+\]\([^)]+\))/g);
|
|
431
|
-
if (parts.length === 1) return formatEmphasis(text);
|
|
432
|
-
|
|
433
|
-
return parts.map((part, i) => {
|
|
434
|
-
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
435
|
-
if (linkMatch) {
|
|
436
|
-
return (
|
|
437
|
-
<Text
|
|
438
|
-
key={i}
|
|
439
|
-
style={styles.linkText}
|
|
440
|
-
onPress={() => Linking.openURL(linkMatch[2])}
|
|
441
|
-
>
|
|
442
|
-
{linkMatch[1]}
|
|
443
|
-
</Text>
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
return <React.Fragment key={i}>{formatEmphasis(part)}</React.Fragment>;
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Format bold, italic, and strikethrough emphasis
|
|
451
|
-
function formatEmphasis(text: string): React.ReactNode {
|
|
452
|
-
// Match **bold**, ~~strikethrough~~, *italic*, and plain text segments
|
|
453
|
-
const parts = text.split(/(\*\*[^*]+\*\*|~~[^~]+~~|\*[^*]+\*)/g);
|
|
454
|
-
if (parts.length === 1) return text;
|
|
455
|
-
|
|
456
|
-
return parts.map((part, i) => {
|
|
457
|
-
if (part.startsWith("**") && part.endsWith("**")) {
|
|
458
|
-
return (
|
|
459
|
-
<Text key={i} style={styles.boldText}>
|
|
460
|
-
{part.slice(2, -2)}
|
|
461
|
-
</Text>
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
if (part.startsWith("~~") && part.endsWith("~~")) {
|
|
465
|
-
return (
|
|
466
|
-
<Text key={i} style={styles.strikethroughText}>
|
|
467
|
-
{part.slice(2, -2)}
|
|
468
|
-
</Text>
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
if (part.startsWith("*") && part.endsWith("*")) {
|
|
472
|
-
return (
|
|
473
|
-
<Text key={i} style={styles.italicText}>
|
|
474
|
-
{part.slice(1, -1)}
|
|
475
|
-
</Text>
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
return part;
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Renders interleaved text and tool parts in order
|
|
483
|
-
function PartsRenderer({
|
|
484
|
-
parts,
|
|
485
|
-
isStreaming,
|
|
486
|
-
}: {
|
|
487
|
-
parts: AssistantPart[];
|
|
488
|
-
isStreaming: boolean;
|
|
489
|
-
}) {
|
|
490
|
-
return (
|
|
491
|
-
<View style={styles.partsContainer}>
|
|
492
|
-
{parts.map((part, index) => {
|
|
493
|
-
if (part.type === "text") {
|
|
494
|
-
const isLastPart = index === parts.length - 1;
|
|
495
|
-
return (
|
|
496
|
-
<View key={part.id} style={styles.messageContainer}>
|
|
497
|
-
<FormattedText
|
|
498
|
-
content={part.content}
|
|
499
|
-
isStreaming={isStreaming && isLastPart}
|
|
500
|
-
/>
|
|
501
|
-
</View>
|
|
502
|
-
);
|
|
503
|
-
} else if (part.type === "tool") {
|
|
504
|
-
return <ToolPartItem key={part.id} part={part} />;
|
|
505
|
-
}
|
|
506
|
-
return null;
|
|
507
|
-
})}
|
|
508
|
-
</View>
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Renders a completed assistant response with parts
|
|
513
|
-
function AssistantPartsItem({ message }: { message: AssistantPartsMessage }) {
|
|
514
|
-
return (
|
|
515
|
-
<View style={styles.resultContainer}>
|
|
516
|
-
<PartsRenderer parts={message.parts} isStreaming={false} />
|
|
517
|
-
{!message.isComplete && (
|
|
518
|
-
<Text style={styles.interruptedText}>(interrupted)</Text>
|
|
519
|
-
)}
|
|
520
|
-
</View>
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Shared helper for tool display info
|
|
525
|
-
function getToolDisplayInfo(
|
|
526
|
-
toolName: string,
|
|
527
|
-
input: Record<string, unknown> | undefined,
|
|
528
|
-
): { label: string; value: string } {
|
|
529
|
-
const getFileName = (path: string): string => path.split("/").pop() || path;
|
|
530
|
-
|
|
531
|
-
switch (toolName) {
|
|
532
|
-
case "Read":
|
|
533
|
-
return {
|
|
534
|
-
label: "read",
|
|
535
|
-
value: getFileName((input?.file_path as string) || "file"),
|
|
536
|
-
};
|
|
537
|
-
case "Edit":
|
|
538
|
-
return {
|
|
539
|
-
label: "edit",
|
|
540
|
-
value: getFileName((input?.file_path as string) || "file"),
|
|
541
|
-
};
|
|
542
|
-
case "Write":
|
|
543
|
-
return {
|
|
544
|
-
label: "write",
|
|
545
|
-
value: getFileName((input?.file_path as string) || "file"),
|
|
546
|
-
};
|
|
547
|
-
case "Bash": {
|
|
548
|
-
const cmd = (input?.command as string) || "";
|
|
549
|
-
return {
|
|
550
|
-
label: "$",
|
|
551
|
-
value: cmd.length > 45 ? cmd.slice(0, 45) + "…" : cmd,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
case "Glob":
|
|
555
|
-
return { label: "glob", value: (input?.pattern as string) || "*" };
|
|
556
|
-
case "Grep":
|
|
557
|
-
return { label: "grep", value: (input?.pattern as string) || "search" };
|
|
558
|
-
case "Task":
|
|
559
|
-
return {
|
|
560
|
-
label: "agent",
|
|
561
|
-
value: (input?.description as string) || "task",
|
|
562
|
-
};
|
|
563
|
-
default:
|
|
564
|
-
return { label: toolName.toLowerCase(), value: "" };
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Renders a tool display line (shared between ToolPartItem and ToolItem)
|
|
569
|
-
function ToolDisplay({
|
|
570
|
-
toolName,
|
|
571
|
-
input,
|
|
572
|
-
isFailed,
|
|
573
|
-
}: {
|
|
574
|
-
toolName: string;
|
|
575
|
-
input?: unknown;
|
|
576
|
-
isFailed: boolean;
|
|
577
|
-
}) {
|
|
578
|
-
const { label, value } = getToolDisplayInfo(
|
|
579
|
-
toolName,
|
|
580
|
-
input as Record<string, unknown> | undefined,
|
|
581
|
-
);
|
|
582
|
-
|
|
583
|
-
return (
|
|
584
|
-
<View style={styles.toolLine}>
|
|
585
|
-
<Text style={isFailed ? styles.toolLabelFailed : styles.toolLabel}>
|
|
586
|
-
{label}
|
|
587
|
-
</Text>
|
|
588
|
-
<Text
|
|
589
|
-
style={isFailed ? styles.toolValueFailed : styles.toolValue}
|
|
590
|
-
numberOfLines={1}
|
|
591
|
-
>
|
|
592
|
-
{value}
|
|
593
|
-
</Text>
|
|
594
|
-
{isFailed && <Text style={styles.toolLabelFailed}> ✕</Text>}
|
|
595
|
-
</View>
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Tool part renderer (for parts in AssistantPartsMessage)
|
|
600
|
-
function ToolPartItem({
|
|
601
|
-
part,
|
|
602
|
-
}: {
|
|
603
|
-
key?: React.Key;
|
|
604
|
-
part: AssistantPart & { type: "tool" };
|
|
605
|
-
}) {
|
|
606
|
-
if (part.status === "started") return null;
|
|
607
|
-
return (
|
|
608
|
-
<ToolDisplay
|
|
609
|
-
toolName={part.toolName}
|
|
610
|
-
input={part.input}
|
|
611
|
-
isFailed={part.status === "failed"}
|
|
612
|
-
/>
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Tool item renderer (for legacy ToolMessage from history)
|
|
617
|
-
function ToolItem({ tool }: { tool: ToolMessage }) {
|
|
618
|
-
if (tool.status === "started") return null;
|
|
619
|
-
return (
|
|
620
|
-
<ToolDisplay
|
|
621
|
-
toolName={tool.toolName}
|
|
622
|
-
input={tool.input}
|
|
623
|
-
isFailed={tool.status === "failed"}
|
|
624
|
-
/>
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
function ResultItem({ result }: { result: ResultMessage }) {
|
|
629
|
-
if (!result.success && result.error) {
|
|
630
|
-
return (
|
|
631
|
-
<View style={styles.errorContainer}>
|
|
632
|
-
<Text style={styles.errorText}>{result.error}</Text>
|
|
633
|
-
</View>
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return (
|
|
638
|
-
<View style={styles.resultContainer}>
|
|
639
|
-
{result.result && (
|
|
640
|
-
<Text style={styles.responseText} selectable>
|
|
641
|
-
{result.result}
|
|
642
|
-
</Text>
|
|
643
|
-
)}
|
|
644
|
-
{(result.costUsd !== undefined || result.durationMs !== undefined) && (
|
|
645
|
-
<Text style={styles.metaText}>
|
|
646
|
-
{result.durationMs !== undefined && `${result.durationMs}ms`}
|
|
647
|
-
{result.costUsd !== undefined &&
|
|
648
|
-
result.durationMs !== undefined &&
|
|
649
|
-
" • "}
|
|
650
|
-
{result.costUsd !== undefined && `$${result.costUsd.toFixed(4)}`}
|
|
651
|
-
</Text>
|
|
652
|
-
)}
|
|
653
|
-
</View>
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
130
|
const styles = StyleSheet.create({
|
|
658
131
|
wrapper: {
|
|
659
132
|
flex: 1,
|
|
@@ -679,230 +152,6 @@ const styles = StyleSheet.create({
|
|
|
679
152
|
textAlign: "center",
|
|
680
153
|
lineHeight: 22,
|
|
681
154
|
},
|
|
682
|
-
messageContainer: {
|
|
683
|
-
flexDirection: "row",
|
|
684
|
-
flexWrap: "wrap",
|
|
685
|
-
},
|
|
686
|
-
responseText: {
|
|
687
|
-
color: "rgba(255,255,255,0.95)",
|
|
688
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
689
|
-
lineHeight: 22,
|
|
690
|
-
},
|
|
691
|
-
cursor: {
|
|
692
|
-
width: 2,
|
|
693
|
-
height: 18,
|
|
694
|
-
backgroundColor: COLORS.TEXT_PRIMARY,
|
|
695
|
-
marginLeft: 2,
|
|
696
|
-
opacity: 0.7,
|
|
697
|
-
},
|
|
698
|
-
resultContainer: {
|
|
699
|
-
marginTop: SPACING.SM,
|
|
700
|
-
},
|
|
701
|
-
partsContainer: {
|
|
702
|
-
// Container for interleaved parts
|
|
703
|
-
},
|
|
704
|
-
formattedContainer: {
|
|
705
|
-
flexDirection: "column",
|
|
706
|
-
},
|
|
707
|
-
listItem: {
|
|
708
|
-
flexDirection: "row",
|
|
709
|
-
marginVertical: SPACING.XS / 2,
|
|
710
|
-
paddingLeft: SPACING.SM,
|
|
711
|
-
},
|
|
712
|
-
listNumber: {
|
|
713
|
-
color: COLORS.TEXT_MUTED,
|
|
714
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
715
|
-
lineHeight: 22,
|
|
716
|
-
width: 24,
|
|
717
|
-
fontWeight: TYPOGRAPHY.WEIGHT_MEDIUM,
|
|
718
|
-
},
|
|
719
|
-
listBullet: {
|
|
720
|
-
color: COLORS.TEXT_MUTED,
|
|
721
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
722
|
-
lineHeight: 22,
|
|
723
|
-
width: 18,
|
|
724
|
-
},
|
|
725
|
-
listText: {
|
|
726
|
-
flex: 1,
|
|
727
|
-
color: "rgba(255,255,255,0.95)",
|
|
728
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
729
|
-
lineHeight: 22,
|
|
730
|
-
},
|
|
731
|
-
codeBlock: {
|
|
732
|
-
backgroundColor: "rgba(255,255,255,0.06)",
|
|
733
|
-
borderRadius: SPACING.SM,
|
|
734
|
-
padding: SPACING.MD,
|
|
735
|
-
marginVertical: SPACING.SM,
|
|
736
|
-
},
|
|
737
|
-
codeLang: {
|
|
738
|
-
color: COLORS.TEXT_MUTED,
|
|
739
|
-
fontSize: TYPOGRAPHY.SIZE_XS,
|
|
740
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
741
|
-
marginBottom: SPACING.XS,
|
|
742
|
-
textTransform: "uppercase",
|
|
743
|
-
},
|
|
744
|
-
codeText: {
|
|
745
|
-
color: "rgba(255,255,255,0.85)",
|
|
746
|
-
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
747
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
748
|
-
lineHeight: 18,
|
|
749
|
-
},
|
|
750
|
-
inlineCode: {
|
|
751
|
-
backgroundColor: "rgba(255,255,255,0.1)",
|
|
752
|
-
color: "rgba(255,255,255,0.9)",
|
|
753
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
754
|
-
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
755
|
-
paddingHorizontal: 4,
|
|
756
|
-
borderRadius: 3,
|
|
757
|
-
},
|
|
758
|
-
heading1: {
|
|
759
|
-
color: COLORS.TEXT_PRIMARY,
|
|
760
|
-
fontSize: TYPOGRAPHY.SIZE_XL,
|
|
761
|
-
fontWeight: TYPOGRAPHY.WEIGHT_SEMIBOLD,
|
|
762
|
-
lineHeight: 24,
|
|
763
|
-
marginTop: SPACING.MD,
|
|
764
|
-
marginBottom: SPACING.XS,
|
|
765
|
-
},
|
|
766
|
-
heading2: {
|
|
767
|
-
color: "rgba(255,255,255,0.9)",
|
|
768
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
769
|
-
fontWeight: TYPOGRAPHY.WEIGHT_SEMIBOLD,
|
|
770
|
-
lineHeight: 22,
|
|
771
|
-
marginTop: SPACING.SM,
|
|
772
|
-
marginBottom: SPACING.XS,
|
|
773
|
-
},
|
|
774
|
-
heading3: {
|
|
775
|
-
color: "rgba(255,255,255,0.85)",
|
|
776
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
777
|
-
fontWeight: TYPOGRAPHY.WEIGHT_MEDIUM,
|
|
778
|
-
lineHeight: 22,
|
|
779
|
-
marginTop: SPACING.SM,
|
|
780
|
-
marginBottom: SPACING.XS,
|
|
781
|
-
},
|
|
782
|
-
boldText: {
|
|
783
|
-
fontWeight: TYPOGRAPHY.WEIGHT_SEMIBOLD,
|
|
784
|
-
color: COLORS.TEXT_PRIMARY,
|
|
785
|
-
},
|
|
786
|
-
italicText: {
|
|
787
|
-
fontStyle: "italic",
|
|
788
|
-
color: "rgba(255,255,255,0.85)",
|
|
789
|
-
},
|
|
790
|
-
strikethroughText: {
|
|
791
|
-
textDecorationLine: "line-through",
|
|
792
|
-
color: COLORS.TEXT_MUTED,
|
|
793
|
-
},
|
|
794
|
-
linkText: {
|
|
795
|
-
color: COLORS.STATUS_INFO,
|
|
796
|
-
textDecorationLine: "underline",
|
|
797
|
-
},
|
|
798
|
-
blockquote: {
|
|
799
|
-
borderLeftWidth: 3,
|
|
800
|
-
borderLeftColor: "rgba(255,255,255,0.15)",
|
|
801
|
-
paddingLeft: SPACING.MD,
|
|
802
|
-
marginVertical: SPACING.XS,
|
|
803
|
-
},
|
|
804
|
-
blockquoteText: {
|
|
805
|
-
color: COLORS.TEXT_SECONDARY,
|
|
806
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
807
|
-
lineHeight: 22,
|
|
808
|
-
fontStyle: "italic",
|
|
809
|
-
},
|
|
810
|
-
horizontalRule: {
|
|
811
|
-
height: 1,
|
|
812
|
-
backgroundColor: COLORS.BORDER,
|
|
813
|
-
marginVertical: SPACING.MD,
|
|
814
|
-
},
|
|
815
|
-
paragraphBreak: {
|
|
816
|
-
height: SPACING.SM,
|
|
817
|
-
},
|
|
818
|
-
interruptedText: {
|
|
819
|
-
color: COLORS.TEXT_MUTED,
|
|
820
|
-
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
821
|
-
fontStyle: "italic",
|
|
822
|
-
marginTop: SPACING.XS,
|
|
823
|
-
},
|
|
824
|
-
metaText: {
|
|
825
|
-
color: COLORS.TEXT_MUTED,
|
|
826
|
-
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
827
|
-
marginTop: SPACING.SM + 2, // 10px
|
|
828
|
-
},
|
|
829
|
-
errorContainer: {
|
|
830
|
-
backgroundColor: "rgba(255,59,48,0.15)",
|
|
831
|
-
borderRadius: SPACING.MD,
|
|
832
|
-
padding: SPACING.MD,
|
|
833
|
-
marginVertical: SPACING.SM - 2, // 6px
|
|
834
|
-
},
|
|
835
|
-
errorText: {
|
|
836
|
-
color: "#FF6B6B",
|
|
837
|
-
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
838
|
-
},
|
|
839
|
-
systemContainer: {
|
|
840
|
-
backgroundColor: "rgba(255,255,255,0.05)",
|
|
841
|
-
borderRadius: SPACING.MD,
|
|
842
|
-
padding: SPACING.MD,
|
|
843
|
-
marginVertical: SPACING.SM - 2, // 6px
|
|
844
|
-
},
|
|
845
|
-
systemText: {
|
|
846
|
-
color: COLORS.TEXT_TERTIARY,
|
|
847
|
-
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
848
|
-
fontStyle: "italic",
|
|
849
|
-
},
|
|
850
|
-
toolLine: {
|
|
851
|
-
flexDirection: "row",
|
|
852
|
-
alignItems: "center",
|
|
853
|
-
marginVertical: SPACING.XS,
|
|
854
|
-
},
|
|
855
|
-
toolLabel: {
|
|
856
|
-
color: COLORS.TEXT_MUTED,
|
|
857
|
-
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
858
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
859
|
-
marginRight: SPACING.SM,
|
|
860
|
-
minWidth: 36,
|
|
861
|
-
},
|
|
862
|
-
toolLabelFailed: {
|
|
863
|
-
color: "rgba(255,100,100,0.6)",
|
|
864
|
-
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
865
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
866
|
-
marginRight: SPACING.SM,
|
|
867
|
-
minWidth: 36,
|
|
868
|
-
},
|
|
869
|
-
toolValue: {
|
|
870
|
-
color: "rgba(255,255,255,0.7)",
|
|
871
|
-
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
872
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
873
|
-
flexShrink: 1,
|
|
874
|
-
},
|
|
875
|
-
toolValueFailed: {
|
|
876
|
-
color: "rgba(255,100,100,0.7)",
|
|
877
|
-
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
878
|
-
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
879
|
-
flexShrink: 1,
|
|
880
|
-
},
|
|
881
|
-
userPromptContainer: {
|
|
882
|
-
backgroundColor: "rgba(0,122,255,0.15)",
|
|
883
|
-
borderRadius: LAYOUT.BORDER_RADIUS_SM + 2, // 16px
|
|
884
|
-
padding: SPACING.MD,
|
|
885
|
-
marginVertical: SPACING.SM,
|
|
886
|
-
alignSelf: "flex-end",
|
|
887
|
-
maxWidth: "85%",
|
|
888
|
-
},
|
|
889
|
-
userPromptText: {
|
|
890
|
-
color: COLORS.TEXT_PRIMARY,
|
|
891
|
-
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
892
|
-
lineHeight: 20,
|
|
893
|
-
},
|
|
894
|
-
userImages: {
|
|
895
|
-
flexDirection: "row",
|
|
896
|
-
flexWrap: "wrap",
|
|
897
|
-
gap: SPACING.SM,
|
|
898
|
-
marginBottom: SPACING.SM,
|
|
899
|
-
},
|
|
900
|
-
userImageThumb: {
|
|
901
|
-
width: 80,
|
|
902
|
-
height: 80,
|
|
903
|
-
borderRadius: SPACING.SM,
|
|
904
|
-
backgroundColor: "rgba(255,255,255,0.1)",
|
|
905
|
-
},
|
|
906
155
|
scrollButton: {
|
|
907
156
|
position: "absolute",
|
|
908
157
|
bottom: LAYOUT.CONTENT_PADDING_H,
|