@vvedantb/native-ui 0.17.2 → 0.18.0
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/AlertDialog.jsx +123 -0
- package/dist/AmbientBackground.jsx +73 -0
- package/dist/ArtifactList.jsx +59 -0
- package/dist/Avatar.jsx +285 -0
- package/dist/Badge.jsx +154 -0
- package/dist/BottomSheetContainer.jsx +208 -0
- package/dist/BulkActionBar.jsx +717 -0
- package/dist/Button.jsx +344 -0
- package/dist/CaptureButton.jsx +104 -0
- package/dist/Card.jsx +371 -0
- package/dist/Checkbox.jsx +177 -0
- package/dist/Chip.jsx +79 -0
- package/dist/ColorSwatches.jsx +159 -0
- package/dist/ConfirmDialog.jsx +157 -0
- package/dist/DateTimeField.jsx +204 -0
- package/dist/Dialog.jsx +614 -0
- package/dist/DividedList.jsx +120 -0
- package/dist/DropdownMenu.jsx +849 -0
- package/dist/EmptyState.jsx +434 -0
- package/dist/ExpoWebRoot.jsx +134 -0
- package/dist/FloatingFAB.jsx +95 -0
- package/dist/FloatingSurface.jsx +226 -0
- package/dist/FloatingTabBar.jsx +403 -0
- package/dist/HeaderIconButton.jsx +53 -0
- package/dist/IconButton.jsx +157 -0
- package/dist/Input.jsx +96 -0
- package/dist/Label.jsx +96 -0
- package/dist/LabeledSwitchRow.jsx +170 -0
- package/dist/LiquidGlass.jsx +2 -0
- package/dist/ListRow.jsx +281 -0
- package/dist/LoadingState.jsx +205 -0
- package/dist/PageIndicator.jsx +128 -0
- package/dist/PageTitle.jsx +205 -0
- package/dist/Pager.jsx +105 -0
- package/dist/PasswordInput.jsx +120 -0
- package/dist/PillTabs.jsx +300 -0
- package/dist/PinnedBotTile.jsx +212 -0
- package/dist/Progress.jsx +147 -0
- package/dist/SafeAreaContainer.jsx +38 -0
- package/dist/ScreenContainer.jsx +117 -0
- package/dist/SearchInput.jsx +150 -0
- package/dist/SectionLabel.jsx +128 -0
- package/dist/Select.jsx +223 -0
- package/dist/Separator.jsx +95 -0
- package/dist/Sheet.jsx +343 -0
- package/dist/SheetRow.jsx +198 -0
- package/dist/Skeleton.jsx +311 -0
- package/dist/Spinner.jsx +195 -0
- package/dist/StackPageHeader.jsx +160 -0
- package/dist/StatGrid.jsx +74 -0
- package/dist/StatusDot.jsx +129 -0
- package/dist/SurfaceRow.jsx +143 -0
- package/dist/SwipeableRow.jsx +50 -0
- package/dist/Switch.jsx +130 -0
- package/dist/Tabs.jsx +385 -0
- package/dist/Textarea.jsx +103 -0
- package/dist/ThemeToggle.jsx +78 -0
- package/dist/Toast.jsx +396 -0
- package/dist/ai/activity-step-detail.jsx +179 -0
- package/dist/ai/activity-step-label.js +816 -0
- package/dist/ai/activity-step-types.js +14 -0
- package/dist/ai/activity-step.jsx +98 -0
- package/dist/ai/activity-tasks-utils.js +172 -0
- package/dist/ai/activity-timeline.js +47 -0
- package/dist/ai/attachments.jsx +258 -0
- package/dist/ai/card-button.jsx +188 -0
- package/dist/ai/chain-of-thought.jsx +459 -0
- package/dist/ai/chat-jump-rail.jsx +57 -0
- package/dist/ai/chat-text.js +44 -0
- package/dist/ai/chat-thread.jsx +135 -0
- package/dist/ai/choice-question.jsx +925 -0
- package/dist/ai/code-block.jsx +574 -0
- package/dist/ai/collapsible.jsx +275 -0
- package/dist/ai/conversation.jsx +723 -0
- package/dist/ai/file-chip.jsx +198 -0
- package/dist/ai/icon-button.jsx +179 -0
- package/dist/ai/index.js +37 -0
- package/dist/ai/message-action-sheet.jsx +408 -0
- package/dist/ai/message-quote.jsx +241 -0
- package/dist/ai/message.jsx +739 -0
- package/dist/ai/prompt-input-menu.jsx +365 -0
- package/dist/ai/prompt-input.jsx +626 -0
- package/dist/ai/reasoning.jsx +446 -0
- package/dist/ai/reply-quote-chip.jsx +278 -0
- package/dist/ai/response-text.jsx +130 -0
- package/dist/ai/shimmer.jsx +149 -0
- package/dist/ai/sources.jsx +260 -0
- package/dist/ai/subagent-cards.jsx +645 -0
- package/dist/ai/suggestion.jsx +186 -0
- package/dist/ai/tool-approval-card.jsx +359 -0
- package/dist/ai/trace.jsx +728 -0
- package/dist/ai/unified-diff.jsx +280 -0
- package/dist/ai/use-streaming-disclosure.js +70 -0
- package/dist/auth/AuthProviders.jsx +27 -0
- package/dist/auth/AuthScreen.jsx +292 -0
- package/dist/auth/GoogleSignInButton.jsx +188 -0
- package/dist/auth/SecondFactorScreen.jsx +133 -0
- package/dist/auth/SignInScreen.jsx +482 -0
- package/dist/auth/authErrors.js +131 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/totp.js +378 -0
- package/dist/blur.js +36 -0
- package/dist/brand/AppIcons.jsx +118 -0
- package/dist/brand/AppMark.jsx +128 -0
- package/dist/brand/AppWordmark.jsx +138 -0
- package/dist/brand/GoogleMark.jsx +71 -0
- package/dist/brand/ProviderIcon.jsx +192 -0
- package/dist/brand/familyApps.js +33 -0
- package/dist/brand/index.js +11 -0
- package/dist/brand/svg.js +252 -0
- package/dist/brand/vProAppBenefits.js +19 -0
- package/dist/cn.js +5 -0
- package/dist/colors.js +15 -0
- package/dist/configureTheme.js +72 -0
- package/dist/controlStyles.js +520 -0
- package/dist/definiteSize.js +12 -0
- package/dist/dialogLayout.js +18 -0
- package/dist/entries/ai.js +4 -0
- package/dist/entries/ambient.js +3 -0
- package/dist/entries/auth.js +5 -0
- package/dist/entries/bottom-sheet.js +2 -0
- package/dist/entries/brand.js +2 -0
- package/dist/entries/button.js +2 -0
- package/dist/entries/date-time.js +3 -0
- package/dist/entries/expo-router.js +4 -0
- package/dist/entries/expo-web-root.js +3 -0
- package/dist/entries/floating.js +7 -0
- package/dist/entries/gesture-handler.js +4 -0
- package/dist/entries/keyboard-controller.js +4 -0
- package/dist/entries/layout.js +16 -0
- package/dist/entries/markdown.js +4 -0
- package/dist/entries/marketing.js +3 -0
- package/dist/entries/page-title.js +7 -0
- package/dist/entries/password-input.js +3 -0
- package/dist/entries/primitives.js +57 -0
- package/dist/entries/search-input.js +3 -0
- package/dist/entries/tab-bar.js +4 -0
- package/dist/entries/theme.js +7 -0
- package/dist/entries/toast.js +5 -0
- package/dist/entries/video.js +3 -0
- package/dist/entries/webview.js +4 -0
- package/dist/examples/AiChatGallery.jsx +509 -0
- package/dist/examples/ParityGallery.jsx +1307 -0
- package/dist/floating.js +62 -0
- package/dist/glassBlur.jsx +145 -0
- package/dist/gradient.js +17 -0
- package/dist/haptics.js +44 -0
- package/dist/highlight.js +382 -0
- package/dist/index.js +85 -0
- package/dist/initials.js +4 -0
- package/dist/loadingGrid.js +81 -0
- package/dist/markdown/Markdown.jsx +515 -0
- package/dist/markdown/MarkdownTable.jsx +443 -0
- package/dist/markdown/tableText.js +68 -0
- package/dist/marketing/PricingModal.jsx +608 -0
- package/dist/marketing/UpgradeToProCard.jsx +164 -0
- package/dist/marketing/pricing.js +34 -0
- package/dist/media/ImageGallery.jsx +580 -0
- package/dist/media/MediaCard.jsx +162 -0
- package/dist/media/imageGrid.js +54 -0
- package/dist/motion.js +33 -0
- package/dist/pagerSwipe.js +104 -0
- package/dist/palette.js +104 -0
- package/dist/primitives/button.jsx +127 -0
- package/dist/primitives/card.jsx +249 -0
- package/dist/primitives/icon.jsx +37 -0
- package/dist/primitives/input.jsx +56 -0
- package/dist/primitives/keyboard-controller.jsx +207 -0
- package/dist/primitives/keyboard-parts.jsx +75 -0
- package/dist/primitives/native-only-animated-view.jsx +68 -0
- package/dist/primitives/overlay.jsx +391 -0
- package/dist/primitives/pager-gesture.jsx +131 -0
- package/dist/primitives/select-host.jsx +103 -0
- package/dist/primitives/select.jsx +555 -0
- package/dist/primitives/sheet-drag.jsx +420 -0
- package/dist/primitives/sheet-gesture.jsx +219 -0
- package/dist/primitives/swipe-row-gesture.jsx +341 -0
- package/dist/primitives/switch.jsx +105 -0
- package/dist/primitives/text.jsx +145 -0
- package/dist/primitives/toast-drag-gesture.jsx +94 -0
- package/dist/primitives/use-controllable-state.js +40 -0
- package/dist/primitives/web-click-guard.js +69 -0
- package/dist/push.js +166 -0
- package/dist/relativeTime.js +70 -0
- package/dist/router/RouterStackPageHeader.jsx +53 -0
- package/dist/selectA11y.js +32 -0
- package/dist/settings/MoreFromProjectV.jsx +168 -0
- package/dist/settings/SettingsEmptyState.jsx +102 -0
- package/dist/settings/SettingsField.jsx +49 -0
- package/dist/settings/SettingsSection.jsx +101 -0
- package/dist/settings/SettingsStack.jsx +17 -0
- package/dist/settings/SettingsToggleRow.jsx +163 -0
- package/dist/settings/projectVMobileApps.js +126 -0
- package/dist/settings/relaxedSizeStyle.js +11 -0
- package/dist/sheetDrag.js +76 -0
- package/dist/svgA11y.js +46 -0
- package/dist/swipeRow.js +161 -0
- package/dist/tabBarRoutes.js +41 -0
- package/dist/tabIndicator.js +139 -0
- package/dist/tailwind-preset.cjs +255 -0
- package/dist/tailwind-preset.d.cts +55 -0
- package/dist/theme.jsx +62 -0
- package/dist/toastChrome.js +110 -0
- package/dist/toastDrag.js +51 -0
- package/dist/toastStore.js +54 -0
- package/dist/tokens.js +70 -0
- package/dist/typography.js +40 -0
- package/dist/useReduceMotion.js +32 -0
- package/dist/useReduceTransparency.js +27 -0
- package/dist/useScheme.js +15 -0
- package/dist/video/InlineVideo.jsx +115 -0
- package/dist/webview/HtmlView.jsx +169 -0
- package/dist/webview/HtmlView.web.jsx +63 -0
- package/dist/webview/htmlView.js +43 -0
- package/package.json +70 -33
- package/src/DropdownMenu.tsx +28 -16
- package/src/FloatingTabBar.tsx +78 -41
- package/src/ai/conversation.tsx +12 -8
- package/src/glassBlur.tsx +47 -22
- package/src/primitives/swipe-row-gesture.tsx +11 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Pure step types for the activity timeline: no React, so `@vvedantb/native-ui`
|
|
2
|
+
// keeps a byte-identical copy (`src/ai/activity-step-types.ts`), with the step
|
|
3
|
+
// labels and grouping that read them.
|
|
4
|
+
|
|
5
|
+
/** One item in a todo checklist step (type "todos"). */
|
|
6
|
+
|
|
7
|
+
/** One choice offered by an AskUserQuestion prompt. */
|
|
8
|
+
|
|
9
|
+
/** One question of an AskUserQuestion prompt, with the choices shown. */
|
|
10
|
+
|
|
11
|
+
/** True when the step has expandable rich detail to show. */
|
|
12
|
+
export function activityStepHasRichDetail(step) {
|
|
13
|
+
return Boolean(step.command || step.output || step.edits && step.edits.length > 0 || step.files && step.files.length > 0 || step.contentPreview || step.hasHiddenDetail);
|
|
14
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { IconAlertTriangle, IconCheck } from "@tabler/icons-react-native";
|
|
3
|
+
import { Spinner } from "../Spinner";
|
|
4
|
+
import { useLargerText } from "../theme";
|
|
5
|
+
import { AgentTraceDescription, AgentTraceIcon, AgentTraceLine, AgentTraceTitle } from "./trace";
|
|
6
|
+
/**
|
|
7
|
+
* One tool or activity step as a single trace line: icon, label, status and
|
|
8
|
+
* an optional description. A light stand-in for the web `ActivityTasks`, built
|
|
9
|
+
* on the `AgentTrace` parts. Its accessibility label reads the label, the
|
|
10
|
+
* status and the description.
|
|
11
|
+
*/
|
|
12
|
+
export function ActivityStep(t0) {
|
|
13
|
+
const $ = _c(26);
|
|
14
|
+
const {
|
|
15
|
+
label,
|
|
16
|
+
icon,
|
|
17
|
+
status: t1,
|
|
18
|
+
description,
|
|
19
|
+
testID
|
|
20
|
+
} = t0;
|
|
21
|
+
const status = t1 === undefined ? "complete" : t1;
|
|
22
|
+
const largerText = useLargerText();
|
|
23
|
+
const running = status === "running";
|
|
24
|
+
const state = running ? "in progress" : status === "error" ? "failed" : "done";
|
|
25
|
+
const t2 = status === "error" ? "error" : undefined;
|
|
26
|
+
let t3;
|
|
27
|
+
if ($[0] !== description || $[1] !== label || $[2] !== state) {
|
|
28
|
+
t3 = [label, state, description].filter(Boolean);
|
|
29
|
+
$[0] = description;
|
|
30
|
+
$[1] = label;
|
|
31
|
+
$[2] = state;
|
|
32
|
+
$[3] = t3;
|
|
33
|
+
} else {
|
|
34
|
+
t3 = $[3];
|
|
35
|
+
}
|
|
36
|
+
const t4 = t3.join(", ");
|
|
37
|
+
let t5;
|
|
38
|
+
if ($[4] !== running) {
|
|
39
|
+
t5 = {
|
|
40
|
+
busy: running
|
|
41
|
+
};
|
|
42
|
+
$[4] = running;
|
|
43
|
+
$[5] = t5;
|
|
44
|
+
} else {
|
|
45
|
+
t5 = $[5];
|
|
46
|
+
}
|
|
47
|
+
let t6;
|
|
48
|
+
if ($[6] !== icon) {
|
|
49
|
+
t6 = icon ? <AgentTraceIcon icon={icon} /> : null;
|
|
50
|
+
$[6] = icon;
|
|
51
|
+
$[7] = t6;
|
|
52
|
+
} else {
|
|
53
|
+
t6 = $[7];
|
|
54
|
+
}
|
|
55
|
+
let t7;
|
|
56
|
+
if ($[8] !== label || $[9] !== running) {
|
|
57
|
+
t7 = <AgentTraceTitle shimmer={running}>{label}</AgentTraceTitle>;
|
|
58
|
+
$[8] = label;
|
|
59
|
+
$[9] = running;
|
|
60
|
+
$[10] = t7;
|
|
61
|
+
} else {
|
|
62
|
+
t7 = $[10];
|
|
63
|
+
}
|
|
64
|
+
let t8;
|
|
65
|
+
if ($[11] !== description) {
|
|
66
|
+
t8 = description ? <AgentTraceDescription>{description}</AgentTraceDescription> : null;
|
|
67
|
+
$[11] = description;
|
|
68
|
+
$[12] = t8;
|
|
69
|
+
} else {
|
|
70
|
+
t8 = $[12];
|
|
71
|
+
}
|
|
72
|
+
let t9;
|
|
73
|
+
if ($[13] !== largerText || $[14] !== running || $[15] !== status) {
|
|
74
|
+
t9 = running ? <AgentTraceIcon><Spinner size={largerText ? "md" : "sm"} decorative={true} /></AgentTraceIcon> : <AgentTraceIcon icon={status === "error" ? IconAlertTriangle : IconCheck} />;
|
|
75
|
+
$[13] = largerText;
|
|
76
|
+
$[14] = running;
|
|
77
|
+
$[15] = status;
|
|
78
|
+
$[16] = t9;
|
|
79
|
+
} else {
|
|
80
|
+
t9 = $[16];
|
|
81
|
+
}
|
|
82
|
+
let t10;
|
|
83
|
+
if ($[17] !== t2 || $[18] !== t4 || $[19] !== t5 || $[20] !== t6 || $[21] !== t7 || $[22] !== t8 || $[23] !== t9 || $[24] !== testID) {
|
|
84
|
+
t10 = <AgentTraceLine testID={testID} status={t2} accessible={true} accessibilityLabel={t4} accessibilityState={t5}>{t6}{t7}{t8}{t9}</AgentTraceLine>;
|
|
85
|
+
$[17] = t2;
|
|
86
|
+
$[18] = t4;
|
|
87
|
+
$[19] = t5;
|
|
88
|
+
$[20] = t6;
|
|
89
|
+
$[21] = t7;
|
|
90
|
+
$[22] = t8;
|
|
91
|
+
$[23] = t9;
|
|
92
|
+
$[24] = testID;
|
|
93
|
+
$[25] = t10;
|
|
94
|
+
} else {
|
|
95
|
+
t10 = $[25];
|
|
96
|
+
}
|
|
97
|
+
return t10;
|
|
98
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One row in the activity timeline. Subtask rows may nest child rows that ran
|
|
3
|
+
* inside that agent (keyed by parentToolUseId → toolUseId).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Status-filler thinking labels emitted by older callback versions (and by
|
|
8
|
+
* prod until the current callback deploys). The response/reasoning text is
|
|
9
|
+
* shown directly, so these narration rows are pure noise — hide them.
|
|
10
|
+
*/
|
|
11
|
+
const LEGACY_NOISE_LABELS = new Set(["Generating response...", "Generated response", "Streaming response...", "Streamed response", "Finalizing response..."]);
|
|
12
|
+
|
|
13
|
+
/** Thinking labels that mark model reasoning rather than status narration. */
|
|
14
|
+
const REASONING_LABELS = new Set(["Thinking...", "Thought"]);
|
|
15
|
+
|
|
16
|
+
/** Placeholder details older callbacks attached to reasoning-label steps. */
|
|
17
|
+
const FILLER_DETAILS = new Set(["Claude is reasoning...", "Codex is reasoning...", "Opencode is reasoning...", "Cursor is reasoning...", "Receiving reply..."]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Older callbacks stored reasoning as thinking steps labelled
|
|
21
|
+
* "Thinking..."/"Thought". Remap those with real text to reasoning blocks;
|
|
22
|
+
* drop the bare placeholder ones (nothing to show).
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeStep(step) {
|
|
25
|
+
if (step.type !== "thinking") return step;
|
|
26
|
+
if (LEGACY_NOISE_LABELS.has(step.label)) return null;
|
|
27
|
+
if (REASONING_LABELS.has(step.label)) {
|
|
28
|
+
if (step.detail && !FILLER_DETAILS.has(step.detail)) {
|
|
29
|
+
return {
|
|
30
|
+
...step,
|
|
31
|
+
type: "reasoning"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return step;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Builds one row per normalized step (no consecutive-type merging). */
|
|
40
|
+
function stepsToRows(steps) {
|
|
41
|
+
const rows = [];
|
|
42
|
+
for (const raw of steps) {
|
|
43
|
+
const step = normalizeStep(raw);
|
|
44
|
+
if (!step) continue;
|
|
45
|
+
rows.push({
|
|
46
|
+
step
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return rows;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Row types that read as narration rather than work: they already summarise
|
|
54
|
+
* themselves, so folding them into an action group would hide their only text.
|
|
55
|
+
*/
|
|
56
|
+
const STANDALONE_TYPES = new Set(["todos", "notice", "status", "question", "steer"]);
|
|
57
|
+
|
|
58
|
+
/** One block in the activity timeline: prose, a lone row, or a folded run. */
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Folds rows into the read-then-act rhythm of the turn: each reasoning block
|
|
62
|
+
* stays prose, and the run of tool calls it produced collapses behind a single
|
|
63
|
+
* summary line. Without this the whole turn is one undifferentiated wall of
|
|
64
|
+
* per-call rows and the thinking that motivated them is lost in it.
|
|
65
|
+
*/
|
|
66
|
+
export function groupActivityRows(rows) {
|
|
67
|
+
const segments = [];
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
if (row.step.type === "reasoning") {
|
|
70
|
+
segments.push({
|
|
71
|
+
kind: "reasoning",
|
|
72
|
+
step: row.step
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Mid-turn reply text: it ends the tool run before it, like reasoning.
|
|
77
|
+
if (row.step.type === "response") {
|
|
78
|
+
segments.push({
|
|
79
|
+
kind: "response",
|
|
80
|
+
step: row.step
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (STANDALONE_TYPES.has(row.step.type)) {
|
|
85
|
+
segments.push({
|
|
86
|
+
kind: "row",
|
|
87
|
+
row
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const last = segments.at(-1);
|
|
92
|
+
if (last?.kind === "actions") {
|
|
93
|
+
last.rows.push(row);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
segments.push({
|
|
97
|
+
kind: "actions",
|
|
98
|
+
rows: [row]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return segments;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* React key for one activity step. `label` and `status` are omitted so a
|
|
106
|
+
* streaming update (Running → Ran, error → recovered) keeps the same instance.
|
|
107
|
+
* `toolUseId` is the identity the callback already stamps; the fallback is
|
|
108
|
+
* type + path/command + occurrence in the list being keyed (never the visible
|
|
109
|
+
* overflow slice — callers must pass the full-list index).
|
|
110
|
+
*/
|
|
111
|
+
export function activityStepKey(step, occurrence) {
|
|
112
|
+
const id = step.toolUseId;
|
|
113
|
+
if (id !== undefined && id.length > 0) {
|
|
114
|
+
return `id:${id}`;
|
|
115
|
+
}
|
|
116
|
+
const stable = step.path ?? step.command ?? "";
|
|
117
|
+
return `fb:${step.type}:${stable}:${occurrence}`;
|
|
118
|
+
}
|
|
119
|
+
export function activityRowKey(row, occurrence) {
|
|
120
|
+
return activityStepKey(row.step, occurrence);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** React key for a timeline segment. Action groups key off the first row. */
|
|
124
|
+
export function activitySegmentKey(segment, occurrence) {
|
|
125
|
+
if (segment.kind === "reasoning" || segment.kind === "response") {
|
|
126
|
+
return `${segment.kind}:${activityStepKey(segment.step, occurrence)}`;
|
|
127
|
+
}
|
|
128
|
+
if (segment.kind === "row") {
|
|
129
|
+
return `row:${activityRowKey(segment.row, occurrence)}`;
|
|
130
|
+
}
|
|
131
|
+
const first = segment.rows[0];
|
|
132
|
+
if (first === undefined) {
|
|
133
|
+
return `actions:${occurrence}`;
|
|
134
|
+
}
|
|
135
|
+
return `actions:${activityRowKey(first, occurrence)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Builds per-call activity rows. Steps with `parentToolUseId` nest under the
|
|
140
|
+
* matching subtask `toolUseId`. Orphans whose parent was never found append
|
|
141
|
+
* at the top level (never drop).
|
|
142
|
+
*/
|
|
143
|
+
export function buildActivityRows(steps) {
|
|
144
|
+
const childrenByParent = new Map();
|
|
145
|
+
const topLevel = [];
|
|
146
|
+
for (const step of steps) {
|
|
147
|
+
const parentId = step.parentToolUseId;
|
|
148
|
+
if (parentId) {
|
|
149
|
+
const existing = childrenByParent.get(parentId);
|
|
150
|
+
if (existing) existing.push(step);else childrenByParent.set(parentId, [step]);
|
|
151
|
+
} else {
|
|
152
|
+
topLevel.push(step);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const rows = stepsToRows(topLevel);
|
|
156
|
+
const attached = new Set();
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
if (row.step.type !== "subtask") continue;
|
|
159
|
+
const toolUseId = row.step.toolUseId;
|
|
160
|
+
if (!toolUseId) continue;
|
|
161
|
+
const kids = childrenByParent.get(toolUseId);
|
|
162
|
+
if (kids && kids.length > 0) {
|
|
163
|
+
row.children = stepsToRows(kids);
|
|
164
|
+
attached.add(toolUseId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const [parentId, kids] of childrenByParent) {
|
|
168
|
+
if (attached.has(parentId)) continue;
|
|
169
|
+
rows.push(...stepsToRows(kids));
|
|
170
|
+
}
|
|
171
|
+
return rows;
|
|
172
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The folded (Codex-style) rules of the web `ActivityTasks`
|
|
2
|
+
// (`@vvedantb/ui` `activity-tasks.tsx`), as pure logic for an app that draws
|
|
3
|
+
// the rows with the `AgentTrace` parts. The row text, the grouping and the
|
|
4
|
+
// step types are byte-identical copies of the web files
|
|
5
|
+
// (`activity-step-label.ts`, `activity-tasks-utils.ts`, `activity-step-types.ts`).
|
|
6
|
+
|
|
7
|
+
import { buildActivityRows, groupActivityRows } from "./activity-tasks-utils";
|
|
8
|
+
/** Blocks a settled timeline shows before "Show N more" (web `MAX_VISIBLE_ROWS`). */
|
|
9
|
+
export const ACTIVITY_MAX_VISIBLE_BLOCKS = 8;
|
|
10
|
+
|
|
11
|
+
/** Child rows a subagent step shows before "+N more" (web `MAX_VISIBLE_CHILDREN`). */
|
|
12
|
+
export const ACTIVITY_MAX_VISIBLE_CHILDREN = 8;
|
|
13
|
+
|
|
14
|
+
/** Step types the folded layout hides: the reply text and the status thinking lines. */
|
|
15
|
+
const HIDDEN_TYPES = new Set(["thinking", "response"]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A turn's steps as the blocks of the folded timeline: each reasoning step as
|
|
19
|
+
* prose, each run of tool calls behind one summary line, narration (to-dos,
|
|
20
|
+
* notices, questions) as lone rows, and the steps a subagent ran nested under
|
|
21
|
+
* its row. Reply text and status thinking lines are dropped, as on the web.
|
|
22
|
+
*/
|
|
23
|
+
export function foldedActivitySegments(steps) {
|
|
24
|
+
return groupActivityRows(buildActivityRows(steps).filter(row => !HIDDEN_TYPES.has(row.step.type)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The child rows a subagent row shows (its reply text belongs to its own
|
|
29
|
+
* thread, so it is dropped), capped at `ACTIVITY_MAX_VISIBLE_CHILDREN`, and
|
|
30
|
+
* how many are left for "+N more".
|
|
31
|
+
*/
|
|
32
|
+
export function activityChildRows(row) {
|
|
33
|
+
const children = (row.children ?? []).filter(child => !HIDDEN_TYPES.has(child.step.type));
|
|
34
|
+
const overflow = Math.max(0, children.length - ACTIVITY_MAX_VISIBLE_CHILDREN);
|
|
35
|
+
return {
|
|
36
|
+
shown: children.slice(0, ACTIVITY_MAX_VISIBLE_CHILDREN),
|
|
37
|
+
total: children.length,
|
|
38
|
+
overflow
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whole seconds a finished step took, for the faded "4s" after its row; undefined when unknown or under 1s. */
|
|
43
|
+
export function activityStepSeconds(step) {
|
|
44
|
+
if (step.status !== "complete" || !step.durationMs) return undefined;
|
|
45
|
+
const seconds = Math.round(step.durationMs / 1000);
|
|
46
|
+
return seconds >= 1 ? seconds : undefined;
|
|
47
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { Image, Pressable, View } from "react-native";
|
|
4
|
+
import { IconFileText, IconMusic, IconPaperclip, IconPhoto, IconVideo, IconWorld, IconX } from "@tabler/icons-react-native";
|
|
5
|
+
import { cn } from "../cn";
|
|
6
|
+
import { touchSlop } from "../controlStyles";
|
|
7
|
+
import { useLargerText } from "../theme";
|
|
8
|
+
import { tokens } from "../tokens";
|
|
9
|
+
import { useScheme } from "../useScheme";
|
|
10
|
+
import { ResponseText } from "./response-text";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A file or source attached to a prompt or message. The fields the web reads
|
|
14
|
+
* from the `ai` SDK `FileUIPart` and `SourceDocumentUIPart`, without the SDK.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const MEDIA_ICONS = {
|
|
18
|
+
audio: IconMusic,
|
|
19
|
+
document: IconFileText,
|
|
20
|
+
image: IconPhoto,
|
|
21
|
+
source: IconWorld,
|
|
22
|
+
unknown: IconPaperclip,
|
|
23
|
+
video: IconVideo
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** The kind of attachment, from its type and MIME type, as on the web. */
|
|
27
|
+
export function getMediaCategory(data) {
|
|
28
|
+
if (data.type === "source-document") return "source";
|
|
29
|
+
const mediaType = data.mediaType ?? "";
|
|
30
|
+
if (mediaType.startsWith("image/")) return "image";
|
|
31
|
+
if (mediaType.startsWith("video/")) return "video";
|
|
32
|
+
if (mediaType.startsWith("audio/")) return "audio";
|
|
33
|
+
if (mediaType.startsWith("application/") || mediaType.startsWith("text/")) return "document";
|
|
34
|
+
return "unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The name to show and read, as on the web: the title or file name, else "Source", "Image" or "Attachment". */
|
|
38
|
+
export function getAttachmentLabel(data) {
|
|
39
|
+
if (data.type === "source-document") return data.title || data.filename || "Source";
|
|
40
|
+
return data.filename || (getMediaCategory(data) === "image" ? "Image" : "Attachment");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `inline` (default) wraps chips in rows; `list` stacks them full width. The web `grid` previews are not ported. */
|
|
44
|
+
|
|
45
|
+
const AttachmentsContext = createContext("inline");
|
|
46
|
+
/** The attachments of a prompt or message. */
|
|
47
|
+
export function Attachments(t0) {
|
|
48
|
+
const $ = _c(13);
|
|
49
|
+
let className;
|
|
50
|
+
let props;
|
|
51
|
+
let t1;
|
|
52
|
+
if ($[0] !== t0) {
|
|
53
|
+
({
|
|
54
|
+
variant: t1,
|
|
55
|
+
className,
|
|
56
|
+
...props
|
|
57
|
+
} = t0);
|
|
58
|
+
$[0] = t0;
|
|
59
|
+
$[1] = className;
|
|
60
|
+
$[2] = props;
|
|
61
|
+
$[3] = t1;
|
|
62
|
+
} else {
|
|
63
|
+
className = $[1];
|
|
64
|
+
props = $[2];
|
|
65
|
+
t1 = $[3];
|
|
66
|
+
}
|
|
67
|
+
const variant = t1 === undefined ? "inline" : t1;
|
|
68
|
+
const t2 = variant === "list" ? "gap-2" : "flex-row flex-wrap items-start gap-2";
|
|
69
|
+
let t3;
|
|
70
|
+
if ($[4] !== className || $[5] !== t2) {
|
|
71
|
+
t3 = cn(t2, className);
|
|
72
|
+
$[4] = className;
|
|
73
|
+
$[5] = t2;
|
|
74
|
+
$[6] = t3;
|
|
75
|
+
} else {
|
|
76
|
+
t3 = $[6];
|
|
77
|
+
}
|
|
78
|
+
let t4;
|
|
79
|
+
if ($[7] !== props || $[8] !== t3) {
|
|
80
|
+
t4 = <View className={t3} {...props} />;
|
|
81
|
+
$[7] = props;
|
|
82
|
+
$[8] = t3;
|
|
83
|
+
$[9] = t4;
|
|
84
|
+
} else {
|
|
85
|
+
t4 = $[9];
|
|
86
|
+
}
|
|
87
|
+
let t5;
|
|
88
|
+
if ($[10] !== t4 || $[11] !== variant) {
|
|
89
|
+
t5 = <AttachmentsContext.Provider value={variant}>{t4}</AttachmentsContext.Provider>;
|
|
90
|
+
$[10] = t4;
|
|
91
|
+
$[11] = variant;
|
|
92
|
+
$[12] = t5;
|
|
93
|
+
} else {
|
|
94
|
+
t5 = $[12];
|
|
95
|
+
}
|
|
96
|
+
return t5;
|
|
97
|
+
}
|
|
98
|
+
/** The hit target of a pressable chip, in points, on each axis. */
|
|
99
|
+
export const ATTACHMENT_PRESS_TARGET = 48;
|
|
100
|
+
|
|
101
|
+
/** A remove cross of 20 in a 32 row: the slop brings it to 44. */
|
|
102
|
+
const REMOVE_SIZE = 20;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* One attachment as a chip: a 20 thumbnail for an image with a `url`, else
|
|
106
|
+
* its media icon, then the name and an optional remove button. Flat, on the
|
|
107
|
+
* web `flatPanelSubtle` fill (`bg-surface-secondary`). No hover card. With
|
|
108
|
+
* `onPress` the thumbnail or icon and the name are one button with a 48 point
|
|
109
|
+
* hit target (the chip stays 32, or 44 with larger text, and `hitSlop` does the
|
|
110
|
+
* rest); the remove button is a separate target that never calls `onPress`.
|
|
111
|
+
*/
|
|
112
|
+
export function Attachment(t0) {
|
|
113
|
+
const $ = _c(40);
|
|
114
|
+
const {
|
|
115
|
+
data,
|
|
116
|
+
onRemove,
|
|
117
|
+
onPress,
|
|
118
|
+
accessibilityLabel,
|
|
119
|
+
testID
|
|
120
|
+
} = t0;
|
|
121
|
+
const variant = useContext(AttachmentsContext);
|
|
122
|
+
const scheme = useScheme();
|
|
123
|
+
const largerText = useLargerText();
|
|
124
|
+
let t1;
|
|
125
|
+
if ($[0] !== data) {
|
|
126
|
+
t1 = getMediaCategory(data);
|
|
127
|
+
$[0] = data;
|
|
128
|
+
$[1] = t1;
|
|
129
|
+
} else {
|
|
130
|
+
t1 = $[1];
|
|
131
|
+
}
|
|
132
|
+
const category = t1;
|
|
133
|
+
let t2;
|
|
134
|
+
if ($[2] !== data) {
|
|
135
|
+
t2 = getAttachmentLabel(data);
|
|
136
|
+
$[2] = data;
|
|
137
|
+
$[3] = t2;
|
|
138
|
+
} else {
|
|
139
|
+
t2 = $[3];
|
|
140
|
+
}
|
|
141
|
+
const label = t2;
|
|
142
|
+
const Icon = MEDIA_ICONS[category];
|
|
143
|
+
const iconSize = largerText ? 20 : 16;
|
|
144
|
+
const muted = tokens.color.muted[scheme];
|
|
145
|
+
let t3;
|
|
146
|
+
if ($[4] !== Icon || $[5] !== category || $[6] !== data || $[7] !== iconSize || $[8] !== muted) {
|
|
147
|
+
t3 = category === "image" && data.url ? <Image source={{
|
|
148
|
+
uri: data.url
|
|
149
|
+
}} resizeMode="cover" style={{
|
|
150
|
+
width: 20,
|
|
151
|
+
height: 20,
|
|
152
|
+
borderRadius: 4
|
|
153
|
+
}} /> : <Icon size={iconSize} color={muted} />;
|
|
154
|
+
$[4] = Icon;
|
|
155
|
+
$[5] = category;
|
|
156
|
+
$[6] = data;
|
|
157
|
+
$[7] = iconSize;
|
|
158
|
+
$[8] = muted;
|
|
159
|
+
$[9] = t3;
|
|
160
|
+
} else {
|
|
161
|
+
t3 = $[9];
|
|
162
|
+
}
|
|
163
|
+
const t4 = variant === "list" ? "flex-1" : "shrink";
|
|
164
|
+
let t5;
|
|
165
|
+
if ($[10] !== label || $[11] !== t4) {
|
|
166
|
+
t5 = <ResponseText size="small" selectable={false} numberOfLines={1} ellipsizeMode="middle" className={t4}>{label}</ResponseText>;
|
|
167
|
+
$[10] = label;
|
|
168
|
+
$[11] = t4;
|
|
169
|
+
$[12] = t5;
|
|
170
|
+
} else {
|
|
171
|
+
t5 = $[12];
|
|
172
|
+
}
|
|
173
|
+
let t6;
|
|
174
|
+
if ($[13] !== t3 || $[14] !== t5) {
|
|
175
|
+
t6 = <>{t3}{t5}</>;
|
|
176
|
+
$[13] = t3;
|
|
177
|
+
$[14] = t5;
|
|
178
|
+
$[15] = t6;
|
|
179
|
+
} else {
|
|
180
|
+
t6 = $[15];
|
|
181
|
+
}
|
|
182
|
+
const content = t6;
|
|
183
|
+
const chipHeight = largerText ? 44 : 32;
|
|
184
|
+
const slop = Math.max(0, Math.ceil((ATTACHMENT_PRESS_TARGET - chipHeight) / 2));
|
|
185
|
+
const t7 = largerText ? "min-h-11" : "min-h-8";
|
|
186
|
+
const t8 = variant === "list" ? "w-full" : "max-w-full";
|
|
187
|
+
let t9;
|
|
188
|
+
if ($[16] !== t7 || $[17] !== t8) {
|
|
189
|
+
t9 = cn("min-w-0 flex-row items-center gap-1.5 px-2", t7, t8);
|
|
190
|
+
$[16] = t7;
|
|
191
|
+
$[17] = t8;
|
|
192
|
+
$[18] = t9;
|
|
193
|
+
} else {
|
|
194
|
+
t9 = $[18];
|
|
195
|
+
}
|
|
196
|
+
const t10 = tokens.color.surfaceSecondary[scheme];
|
|
197
|
+
let t11;
|
|
198
|
+
if ($[19] !== t10) {
|
|
199
|
+
t11 = {
|
|
200
|
+
borderRadius: tokens.radius.sm,
|
|
201
|
+
backgroundColor: t10
|
|
202
|
+
};
|
|
203
|
+
$[19] = t10;
|
|
204
|
+
$[20] = t11;
|
|
205
|
+
} else {
|
|
206
|
+
t11 = $[20];
|
|
207
|
+
}
|
|
208
|
+
let t12;
|
|
209
|
+
if ($[21] !== accessibilityLabel || $[22] !== content || $[23] !== label || $[24] !== onPress || $[25] !== slop || $[26] !== variant) {
|
|
210
|
+
t12 = onPress ? <Pressable accessibilityRole="button" accessibilityLabel={accessibilityLabel ?? label} hitSlop={{
|
|
211
|
+
top: slop,
|
|
212
|
+
bottom: slop,
|
|
213
|
+
left: 0,
|
|
214
|
+
right: 0
|
|
215
|
+
}} onPress={onPress} className={cn("min-w-0 flex-row items-center gap-1.5 self-stretch active:opacity-70", variant === "list" ? "flex-1" : "shrink")}>{content}</Pressable> : content;
|
|
216
|
+
$[21] = accessibilityLabel;
|
|
217
|
+
$[22] = content;
|
|
218
|
+
$[23] = label;
|
|
219
|
+
$[24] = onPress;
|
|
220
|
+
$[25] = slop;
|
|
221
|
+
$[26] = variant;
|
|
222
|
+
$[27] = t12;
|
|
223
|
+
} else {
|
|
224
|
+
t12 = $[27];
|
|
225
|
+
}
|
|
226
|
+
let t13;
|
|
227
|
+
if ($[28] !== label || $[29] !== largerText || $[30] !== muted || $[31] !== onPress || $[32] !== onRemove) {
|
|
228
|
+
t13 = onRemove ? <Pressable accessibilityRole="button" accessibilityLabel={`Remove ${label}`} hitSlop={{
|
|
229
|
+
...touchSlop(REMOVE_SIZE),
|
|
230
|
+
left: onPress ? 3 : touchSlop(REMOVE_SIZE).left,
|
|
231
|
+
right: 8
|
|
232
|
+
}} onPress={onRemove} className="items-center justify-center rounded-full active:opacity-70" style={{
|
|
233
|
+
width: REMOVE_SIZE,
|
|
234
|
+
height: REMOVE_SIZE
|
|
235
|
+
}}><IconX size={largerText ? 16 : 14} color={muted} /></Pressable> : null;
|
|
236
|
+
$[28] = label;
|
|
237
|
+
$[29] = largerText;
|
|
238
|
+
$[30] = muted;
|
|
239
|
+
$[31] = onPress;
|
|
240
|
+
$[32] = onRemove;
|
|
241
|
+
$[33] = t13;
|
|
242
|
+
} else {
|
|
243
|
+
t13 = $[33];
|
|
244
|
+
}
|
|
245
|
+
let t14;
|
|
246
|
+
if ($[34] !== t11 || $[35] !== t12 || $[36] !== t13 || $[37] !== t9 || $[38] !== testID) {
|
|
247
|
+
t14 = <View testID={testID} className={t9} style={t11}>{t12}{t13}</View>;
|
|
248
|
+
$[34] = t11;
|
|
249
|
+
$[35] = t12;
|
|
250
|
+
$[36] = t13;
|
|
251
|
+
$[37] = t9;
|
|
252
|
+
$[38] = testID;
|
|
253
|
+
$[39] = t14;
|
|
254
|
+
} else {
|
|
255
|
+
t14 = $[39];
|
|
256
|
+
}
|
|
257
|
+
return t14;
|
|
258
|
+
}
|