@vvedantb/ui 0.17.1 → 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/README.md +251 -3
- package/package.json +13 -5
- package/shell/src/SidebarResize.tsx +1 -4
- package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
- package/src/ai-elements/activity-question-utils.ts +26 -0
- package/src/ai-elements/activity-question.tsx +159 -0
- package/src/ai-elements/activity-shared.tsx +459 -0
- package/src/ai-elements/activity-step-detail.tsx +115 -0
- package/src/ai-elements/activity-step-label.ts +981 -0
- package/src/ai-elements/activity-tasks-utils.ts +199 -0
- package/src/ai-elements/activity-tasks.tsx +770 -0
- package/src/ai-elements/artifact.tsx +162 -52
- package/src/ai-elements/attachments.tsx +62 -32
- package/src/ai-elements/code-block.tsx +76 -22
- package/src/ai-elements/context.tsx +370 -0
- package/src/ai-elements/conversation.tsx +186 -62
- package/src/ai-elements/file-chip.tsx +53 -0
- package/src/ai-elements/message.tsx +161 -50
- package/src/ai-elements/model-picker-content.tsx +505 -0
- package/src/ai-elements/model-picker-types.ts +56 -0
- package/src/ai-elements/model-picker.tsx +301 -0
- package/src/ai-elements/model-selector.tsx +102 -21
- package/src/ai-elements/plan.tsx +130 -0
- package/src/ai-elements/prompt-input-settings.tsx +144 -0
- package/src/ai-elements/prompt-input-speech.tsx +189 -0
- package/src/ai-elements/prompt-input-traits.tsx +281 -0
- package/src/ai-elements/prompt-input.tsx +320 -134
- package/src/ai-elements/provider-icon.tsx +133 -0
- package/src/ai-elements/queue.tsx +293 -9
- package/src/ai-elements/shimmer.tsx +72 -1
- package/src/ai-elements/simple-model-ladder.tsx +108 -0
- package/src/ai-elements/test-results.tsx +510 -0
- package/src/ai-elements/trace.tsx +243 -0
- package/src/ai-elements/use-streaming-disclosure.ts +50 -0
- package/src/ai-elements/web-preview.tsx +349 -0
- package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
- package/src/index.ts +66 -12
- package/src/kibo/avatar-stack.tsx +40 -0
- package/src/kibo/contribution-graph.tsx +539 -0
- package/src/kibo/cursor.tsx +72 -0
- package/src/kibo/gantt-features.tsx +439 -0
- package/src/kibo/gantt-header.tsx +181 -0
- package/src/kibo/gantt-provider.tsx +657 -0
- package/src/kibo/gantt-sidebar.tsx +157 -0
- package/src/kibo/gantt-timeline.tsx +154 -0
- package/src/kibo/index.ts +151 -0
- package/src/kibo/kanban.tsx +338 -0
- package/src/kibo/kanbanDropTarget.ts +25 -0
- package/src/kibo/list.tsx +162 -0
- package/src/kibo/theme-switcher.tsx +109 -0
- package/src/kibo/video-player.tsx +237 -0
- package/src/markdown/Markdown.tsx +112 -0
- package/src/markdown/MarkdownBlockquote.tsx +45 -0
- package/src/markdown/MarkdownCodeBlock.tsx +197 -0
- package/src/markdown/MarkdownInlineCode.tsx +40 -0
- package/src/markdown/MarkdownLink.tsx +92 -0
- package/src/markdown/codeFence.ts +57 -0
- package/src/markdown/codeHighlight.ts +138 -0
- package/src/markdown/incrementalBlocks.ts +182 -0
- package/src/markdown/index.ts +3 -0
- package/src/markdown/markdownClipboard.ts +178 -0
- package/src/markdown/mermaid.ts +155 -0
- package/src/markdown/rehypeGithubAlerts.ts +80 -0
- package/src/motion/presets.ts +24 -0
- package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
- package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
- package/src/ui/_menu-parts.tsx +29 -0
- package/src/ui/accordion.alt.tsx +46 -53
- package/src/ui/accordion.tsx +59 -10
- package/src/ui/avatar.tsx +56 -0
- package/src/ui/badge.tsx +45 -21
- package/src/ui/border-beam.tsx +97 -0
- package/src/ui/button-group.tsx +50 -21
- package/src/ui/button.tsx +67 -45
- package/src/ui/calendar.tsx +48 -5
- package/src/ui/card.tsx +77 -8
- package/src/ui/carousel.tsx +60 -0
- package/src/ui/centered-spinner.tsx +30 -0
- package/src/ui/checkbox.tsx +34 -16
- package/src/ui/clear-input.alt.tsx +33 -75
- package/src/ui/clear-input.tsx +140 -15
- package/src/ui/collapsible.tsx +40 -2
- package/src/ui/command.tsx +143 -70
- package/src/ui/context-menu.tsx +159 -93
- package/src/ui/crossfade-icon.tsx +123 -0
- package/src/ui/dialog.tsx +154 -70
- package/src/ui/dropdown-menu.tsx +177 -117
- package/src/ui/hover-card.tsx +59 -17
- package/src/ui/input-group.alt.tsx +83 -83
- package/src/ui/input-group.tsx +129 -58
- package/src/ui/input.tsx +13 -4
- package/src/ui/label.tsx +11 -8
- package/src/ui/list-row.tsx +232 -0
- package/src/ui/loading-state.tsx +215 -0
- package/src/ui/popover.tsx +56 -23
- package/src/ui/progress.tsx +28 -16
- package/src/ui/refresh-spin-icon.tsx +41 -0
- package/src/ui/search-input.tsx +66 -15
- package/src/ui/select.tsx +171 -100
- package/src/ui/separator.tsx +23 -14
- package/src/ui/slider.tsx +21 -7
- package/src/ui/sonner.tsx +68 -30
- package/src/ui/spinner.tsx +229 -11
- package/src/ui/status-dot.tsx +84 -0
- package/src/ui/surface.tsx +49 -0
- package/src/ui/switch.tsx +29 -15
- package/src/ui/table.tsx +38 -16
- package/src/ui/tabs.tsx +123 -35
- package/src/ui/tabsSliding.ts +33 -1
- package/src/ui/textarea.tsx +13 -4
- package/src/ui/tooltip.tsx +50 -14
- package/src/ui/ui-config.tsx +149 -0
- package/src/utils/cat-color.ts +123 -0
- package/src/utils/dayjs.ts +6 -0
- package/src/utils/gesture.ts +59 -0
- package/src/utils/runtimeVisibility.ts +167 -0
- package/src/utils/sharedClock.ts +96 -0
- package/src/utils/surface-radius.ts +16 -0
- package/src/utils/useDragSensors.ts +58 -0
- package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
- package/tokens/examples/tokens-moved/README.md +20 -0
- package/tokens/examples/tokens-moved/index.html +12 -0
- package/tokens/examples/tokens-moved/main.tsx +6 -0
- package/tokens/examples/tokens-moved/package.json +14 -0
- package/tokens/examples/tokens-moved/styles.css +12 -0
- package/tokens/examples/tokens-moved/tsconfig.json +14 -0
- package/tokens/examples/tokens-moved/vite.config.ts +11 -0
- package/tokens/src/css/border-beam.css +116 -0
- package/tokens/src/css/markdown.css +318 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
IconCheck,
|
|
4
|
+
IconMessageQuestion,
|
|
5
|
+
IconPencil,
|
|
6
|
+
} from "@tabler/icons-react";
|
|
7
|
+
|
|
8
|
+
import { Badge } from "../ui/badge";
|
|
9
|
+
import { useCn, useUiConfig } from "../ui/ui-config";
|
|
10
|
+
import { freeTextAnswer, selectedLabels } from "./activity-question-utils";
|
|
11
|
+
import type { ActivityQuestion } from "./activity-shared";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `UiConfig.ActivityQuestionCard`: `classes.selectedOption` replaces the fill
|
|
15
|
+
* of the picked option row (default `bg-surface-tertiary`).
|
|
16
|
+
*/
|
|
17
|
+
export interface ActivityQuestionCardConfig {
|
|
18
|
+
classes?: { selectedOption?: string };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const OPTION_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
22
|
+
|
|
23
|
+
const CHIP_CLASS =
|
|
24
|
+
"mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-md text-3xs font-bold tracking-wide";
|
|
25
|
+
|
|
26
|
+
function OptionRow({
|
|
27
|
+
chip,
|
|
28
|
+
label,
|
|
29
|
+
description,
|
|
30
|
+
isSelected,
|
|
31
|
+
}: {
|
|
32
|
+
chip: ReactNode;
|
|
33
|
+
label: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
isSelected: boolean;
|
|
36
|
+
}) {
|
|
37
|
+
const merge = useCn();
|
|
38
|
+
const selectedClass =
|
|
39
|
+
useUiConfig("ActivityQuestionCard")?.classes?.selectedOption ?? "bg-surface-tertiary";
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={merge(
|
|
43
|
+
"flex items-start gap-2 rounded-md px-2 py-1.5",
|
|
44
|
+
isSelected ? selectedClass : undefined,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<span
|
|
48
|
+
className={merge(
|
|
49
|
+
CHIP_CLASS,
|
|
50
|
+
isSelected
|
|
51
|
+
? "bg-primary text-primary-foreground"
|
|
52
|
+
: "bg-secondary text-muted-foreground",
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
{chip}
|
|
56
|
+
</span>
|
|
57
|
+
<div className="min-w-0 flex-1">
|
|
58
|
+
<span
|
|
59
|
+
className={merge(
|
|
60
|
+
"text-sm leading-snug",
|
|
61
|
+
isSelected
|
|
62
|
+
? "font-medium text-foreground"
|
|
63
|
+
: "text-muted-foreground",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{label}
|
|
67
|
+
</span>
|
|
68
|
+
{description ? (
|
|
69
|
+
<p className="mt-0.5 text-xs leading-relaxed text-muted-foreground">
|
|
70
|
+
{description}
|
|
71
|
+
</p>
|
|
72
|
+
) : null}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read-only record of an AskUserQuestion prompt and the answer given. The live
|
|
80
|
+
* prompt is `MultipleChoiceQuestion` in the composer dock; this is the history
|
|
81
|
+
* left behind in the transcript once the question is answered.
|
|
82
|
+
*/
|
|
83
|
+
export function ActivityQuestionCard({
|
|
84
|
+
questions,
|
|
85
|
+
answers,
|
|
86
|
+
}: {
|
|
87
|
+
questions: ActivityQuestion[];
|
|
88
|
+
answers?: Record<string, string>;
|
|
89
|
+
}) {
|
|
90
|
+
if (questions.length === 0) return null;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="rounded-surface bg-card p-3 space-y-3">
|
|
94
|
+
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
|
95
|
+
<IconMessageQuestion size={14} />
|
|
96
|
+
{questions.length > 1 ? "Questions" : "Question"}
|
|
97
|
+
</div>
|
|
98
|
+
{questions.map((question, questionIndex) => {
|
|
99
|
+
const answer = answers?.[question.question];
|
|
100
|
+
const hasAnswer = answer !== undefined && answer.trim().length > 0;
|
|
101
|
+
const chosen = selectedLabels(answer, question);
|
|
102
|
+
const freeText = freeTextAnswer(answer, chosen);
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
key={`${question.question}-${questionIndex}`}
|
|
106
|
+
className="space-y-1.5"
|
|
107
|
+
>
|
|
108
|
+
<div className="flex items-start justify-between gap-2">
|
|
109
|
+
<p className="text-sm font-medium text-foreground">
|
|
110
|
+
{question.question}
|
|
111
|
+
</p>
|
|
112
|
+
{question.header ? (
|
|
113
|
+
<Badge
|
|
114
|
+
variant="secondary"
|
|
115
|
+
className="shrink-0 text-3xs font-semibold"
|
|
116
|
+
>
|
|
117
|
+
{question.header}
|
|
118
|
+
</Badge>
|
|
119
|
+
) : null}
|
|
120
|
+
</div>
|
|
121
|
+
<div className="space-y-0.5">
|
|
122
|
+
{question.options.map((option, optionIndex) => {
|
|
123
|
+
const isSelected = chosen.includes(option.label);
|
|
124
|
+
return (
|
|
125
|
+
<OptionRow
|
|
126
|
+
key={`${option.label}-${optionIndex}`}
|
|
127
|
+
chip={
|
|
128
|
+
isSelected ? (
|
|
129
|
+
<IconCheck size={12} strokeWidth={3} />
|
|
130
|
+
) : (
|
|
131
|
+
(OPTION_LETTERS[optionIndex] ?? String(optionIndex + 1))
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
label={option.label}
|
|
135
|
+
description={option.description}
|
|
136
|
+
isSelected={isSelected}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
})}
|
|
140
|
+
{freeText ? (
|
|
141
|
+
<OptionRow
|
|
142
|
+
chip={<IconPencil size={12} strokeWidth={3} />}
|
|
143
|
+
label="Other"
|
|
144
|
+
description={freeText}
|
|
145
|
+
isSelected
|
|
146
|
+
/>
|
|
147
|
+
) : null}
|
|
148
|
+
</div>
|
|
149
|
+
{hasAnswer ? null : (
|
|
150
|
+
<p className="text-xs text-muted-foreground">
|
|
151
|
+
Answered in the next message
|
|
152
|
+
</p>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentType } from "react";
|
|
4
|
+
import {
|
|
5
|
+
IconFileSearch,
|
|
6
|
+
IconPencil,
|
|
7
|
+
IconFilePlus,
|
|
8
|
+
IconTerminal2,
|
|
9
|
+
IconFolderSearch,
|
|
10
|
+
IconFileText,
|
|
11
|
+
IconWorld,
|
|
12
|
+
IconSearch,
|
|
13
|
+
IconSitemap,
|
|
14
|
+
IconBook2,
|
|
15
|
+
IconTool,
|
|
16
|
+
IconMessage,
|
|
17
|
+
IconListCheck,
|
|
18
|
+
IconInfoCircle,
|
|
19
|
+
IconAnchor,
|
|
20
|
+
IconSteeringWheel,
|
|
21
|
+
IconBrain,
|
|
22
|
+
} from "@tabler/icons-react";
|
|
23
|
+
import { Spinner } from "../ui/spinner";
|
|
24
|
+
import { useCn, useUiConfig } from "../ui/ui-config";
|
|
25
|
+
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
26
|
+
|
|
27
|
+
import { quantizedSnapshot, subscribeQuantized } from "../utils/sharedClock";
|
|
28
|
+
|
|
29
|
+
/** Props the activity timeline passes to its Markdown renderer. */
|
|
30
|
+
export interface ActivityMarkdownProps {
|
|
31
|
+
className?: string;
|
|
32
|
+
/** `streaming` while the text is still arriving. */
|
|
33
|
+
mode?: "static" | "streaming";
|
|
34
|
+
children: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `UiConfig.ActivityTasks`: defaults for the agent activity timeline
|
|
39
|
+
* (`ActivityTasks`, `ActivityThinkingIcon`).
|
|
40
|
+
*/
|
|
41
|
+
export interface ActivityTasksConfig {
|
|
42
|
+
/** Agent name in the streaming header ("<name> is thinking..."). Default "Agent"; the `name` prop wins. */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Renders reasoning and mid-turn reply text. Default: `Streamdown`. */
|
|
45
|
+
markdown?: ComponentType<ActivityMarkdownProps>;
|
|
46
|
+
/**
|
|
47
|
+
* An image for `ActivityThinkingIcon` (for example the app's logo mark),
|
|
48
|
+
* shown 16px and round. Without it the icon is the brain glyph.
|
|
49
|
+
*/
|
|
50
|
+
thinkingIcon?: { src: string; alt: string };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One item in a todo checklist step (type "todos"). */
|
|
54
|
+
export interface ActivityTodoItem {
|
|
55
|
+
content: string;
|
|
56
|
+
status: "pending" | "in_progress" | "completed";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ActivityStepOutput {
|
|
60
|
+
text: string;
|
|
61
|
+
exitCode?: number;
|
|
62
|
+
truncated?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ActivityStepEdit {
|
|
66
|
+
oldText: string;
|
|
67
|
+
newText: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One choice offered by an AskUserQuestion prompt. */
|
|
71
|
+
export interface ActivityQuestionOption {
|
|
72
|
+
label: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One question of an AskUserQuestion prompt, with the choices shown. */
|
|
77
|
+
export interface ActivityQuestion {
|
|
78
|
+
question: string;
|
|
79
|
+
header?: string;
|
|
80
|
+
multiSelect?: boolean;
|
|
81
|
+
options: ActivityQuestionOption[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ActivityStep {
|
|
85
|
+
type:
|
|
86
|
+
| "read"
|
|
87
|
+
| "edit"
|
|
88
|
+
| "write"
|
|
89
|
+
| "bash"
|
|
90
|
+
| "search_files"
|
|
91
|
+
| "search_code"
|
|
92
|
+
| "web_fetch"
|
|
93
|
+
| "web_search"
|
|
94
|
+
| "subtask"
|
|
95
|
+
| "notebook"
|
|
96
|
+
| "thinking"
|
|
97
|
+
| "reasoning"
|
|
98
|
+
| "response"
|
|
99
|
+
| "question"
|
|
100
|
+
| "todos"
|
|
101
|
+
| "tool"
|
|
102
|
+
| "notice"
|
|
103
|
+
| "hook"
|
|
104
|
+
| "status"
|
|
105
|
+
/** A queued user message the agent took into this live turn (Steer). */
|
|
106
|
+
| "steer";
|
|
107
|
+
label: string;
|
|
108
|
+
detail?: string;
|
|
109
|
+
/** Full, unshortened path for file-type steps. Powers the chat File Viewer. */
|
|
110
|
+
path?: string;
|
|
111
|
+
/** `pending`: a planned step that has not started (sandbox startup stages). */
|
|
112
|
+
status: "complete" | "active" | "pending";
|
|
113
|
+
/** The tool_use id that produced this step (Claude only). */
|
|
114
|
+
toolUseId?: string;
|
|
115
|
+
/** Parent `Agent` tool_use id — set on steps that ran inside a subagent. */
|
|
116
|
+
parentToolUseId?: string;
|
|
117
|
+
/** Todo checklist snapshot (type "todos" only). */
|
|
118
|
+
todos?: ActivityTodoItem[];
|
|
119
|
+
/** Bash command (fuller than detail, capped). */
|
|
120
|
+
command?: string;
|
|
121
|
+
/** Tool result transcript (tail-capped). */
|
|
122
|
+
output?: ActivityStepOutput;
|
|
123
|
+
/** Edit before/after snippets. */
|
|
124
|
+
edits?: ActivityStepEdit[];
|
|
125
|
+
/** Codex file_change paths. */
|
|
126
|
+
files?: string[];
|
|
127
|
+
/** Write tool content head preview. */
|
|
128
|
+
contentPreview?: string;
|
|
129
|
+
/** True when the tool failed or exited non-zero. */
|
|
130
|
+
isError?: boolean;
|
|
131
|
+
/** Wall time from push → complete (ms). */
|
|
132
|
+
durationMs?: number;
|
|
133
|
+
/** AskUserQuestion prompt (type "question" only): the questions and options shown to the user. */
|
|
134
|
+
questions?: ActivityQuestion[];
|
|
135
|
+
/** AskUserQuestion answers keyed by question text. Absent when the turn ended without a structured answer. */
|
|
136
|
+
answers?: Record<string, string>;
|
|
137
|
+
/**
|
|
138
|
+
* The transcript query stripped this step's expanded-only fields (`output`,
|
|
139
|
+
* `edits`, `contentPreview`) to keep the chat subscription small. The row
|
|
140
|
+
* still opens; the body fills in once the full payload is fetched.
|
|
141
|
+
*/
|
|
142
|
+
hasHiddenDetail?: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** True when the step has expandable rich detail to show. */
|
|
146
|
+
export function activityStepHasRichDetail(step: ActivityStep): boolean {
|
|
147
|
+
return Boolean(
|
|
148
|
+
step.command ||
|
|
149
|
+
step.output ||
|
|
150
|
+
(step.edits && step.edits.length > 0) ||
|
|
151
|
+
(step.files && step.files.length > 0) ||
|
|
152
|
+
step.contentPreview ||
|
|
153
|
+
step.hasHiddenDetail,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Glyph for thinking steps: the app's mark from `UiConfig.ActivityTasks.thinkingIcon`, or a brain. */
|
|
158
|
+
export function ActivityThinkingIcon({ className }: { className?: string }) {
|
|
159
|
+
const image = useUiConfig("ActivityTasks")?.thinkingIcon;
|
|
160
|
+
const merge = useCn();
|
|
161
|
+
if (!image) return <IconBrain aria-hidden className={className} />;
|
|
162
|
+
return (
|
|
163
|
+
<img
|
|
164
|
+
src={image.src}
|
|
165
|
+
alt={image.alt}
|
|
166
|
+
width={16}
|
|
167
|
+
height={16}
|
|
168
|
+
className={merge("rounded-full", className)}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The `status` step glyph: the `circle` {@link Spinner}. */
|
|
174
|
+
function ActivityStatusSpinner() {
|
|
175
|
+
return <Spinner variant="circle" />;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Glyph and fallback label per step type. */
|
|
179
|
+
export const activityStepConfig = {
|
|
180
|
+
read: { icon: IconFileSearch, defaultLabel: "Read file" },
|
|
181
|
+
edit: { icon: IconPencil, defaultLabel: "Edited file" },
|
|
182
|
+
write: { icon: IconFilePlus, defaultLabel: "Created file" },
|
|
183
|
+
bash: { icon: IconTerminal2, defaultLabel: "Ran command" },
|
|
184
|
+
search_files: { icon: IconFolderSearch, defaultLabel: "Found files" },
|
|
185
|
+
search_code: { icon: IconFileText, defaultLabel: "Searched code" },
|
|
186
|
+
web_fetch: { icon: IconWorld, defaultLabel: "Fetched URL" },
|
|
187
|
+
web_search: { icon: IconSearch, defaultLabel: "Web search" },
|
|
188
|
+
subtask: { icon: IconSitemap, defaultLabel: "Ran agent" },
|
|
189
|
+
notebook: { icon: IconBook2, defaultLabel: "Edited notebook" },
|
|
190
|
+
thinking: { icon: ActivityThinkingIcon, defaultLabel: "Thinking..." },
|
|
191
|
+
reasoning: { icon: ActivityThinkingIcon, defaultLabel: "Thinking..." },
|
|
192
|
+
response: { icon: IconMessage, defaultLabel: "Response" },
|
|
193
|
+
question: { icon: IconMessage, defaultLabel: "Asked a question" },
|
|
194
|
+
todos: { icon: IconListCheck, defaultLabel: "Task list" },
|
|
195
|
+
tool: { icon: IconTool, defaultLabel: "Used tool" },
|
|
196
|
+
notice: { icon: IconInfoCircle, defaultLabel: "Notice" },
|
|
197
|
+
hook: { icon: IconAnchor, defaultLabel: "Hook" },
|
|
198
|
+
status: { icon: ActivityStatusSpinner, defaultLabel: "Status" },
|
|
199
|
+
steer: { icon: IconSteeringWheel, defaultLabel: "Steered" },
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const SPINNER_VERBS = [
|
|
203
|
+
"Accomplishing",
|
|
204
|
+
"Actioning",
|
|
205
|
+
"Actualizing",
|
|
206
|
+
"Architecting",
|
|
207
|
+
"Baking",
|
|
208
|
+
"Beaming",
|
|
209
|
+
"Beboppin'",
|
|
210
|
+
"Befuddling",
|
|
211
|
+
"Billowing",
|
|
212
|
+
"Blanching",
|
|
213
|
+
"Bloviating",
|
|
214
|
+
"Boogieing",
|
|
215
|
+
"Boondoggling",
|
|
216
|
+
"Booping",
|
|
217
|
+
"Bootstrapping",
|
|
218
|
+
"Brewing",
|
|
219
|
+
"Bunning",
|
|
220
|
+
"Burrowing",
|
|
221
|
+
"Calculating",
|
|
222
|
+
"Canoodling",
|
|
223
|
+
"Caramelizing",
|
|
224
|
+
"Cascading",
|
|
225
|
+
"Catapulting",
|
|
226
|
+
"Cerebrating",
|
|
227
|
+
"Channeling",
|
|
228
|
+
"Channelling",
|
|
229
|
+
"Choreographing",
|
|
230
|
+
"Churning",
|
|
231
|
+
"Clauding",
|
|
232
|
+
"Coalescing",
|
|
233
|
+
"Cogitating",
|
|
234
|
+
"Combobulating",
|
|
235
|
+
"Composing",
|
|
236
|
+
"Computing",
|
|
237
|
+
"Concocting",
|
|
238
|
+
"Considering",
|
|
239
|
+
"Contemplating",
|
|
240
|
+
"Cooking",
|
|
241
|
+
"Crafting",
|
|
242
|
+
"Creating",
|
|
243
|
+
"Crunching",
|
|
244
|
+
"Crystallizing",
|
|
245
|
+
"Cultivating",
|
|
246
|
+
"Deciphering",
|
|
247
|
+
"Deliberating",
|
|
248
|
+
"Determining",
|
|
249
|
+
"Dilly-dallying",
|
|
250
|
+
"Discombobulating",
|
|
251
|
+
"Doing",
|
|
252
|
+
"Doodling",
|
|
253
|
+
"Drizzling",
|
|
254
|
+
"Ebbing",
|
|
255
|
+
"Effecting",
|
|
256
|
+
"Elucidating",
|
|
257
|
+
"Embellishing",
|
|
258
|
+
"Enchanting",
|
|
259
|
+
"Envisioning",
|
|
260
|
+
"Evaporating",
|
|
261
|
+
"Fermenting",
|
|
262
|
+
"Fiddle-faddling",
|
|
263
|
+
"Finagling",
|
|
264
|
+
"Flambéing",
|
|
265
|
+
"Flibbertigibbeting",
|
|
266
|
+
"Flowing",
|
|
267
|
+
"Flummoxing",
|
|
268
|
+
"Fluttering",
|
|
269
|
+
"Forging",
|
|
270
|
+
"Forming",
|
|
271
|
+
"Frolicking",
|
|
272
|
+
"Frosting",
|
|
273
|
+
"Gallivanting",
|
|
274
|
+
"Galloping",
|
|
275
|
+
"Garnishing",
|
|
276
|
+
"Generating",
|
|
277
|
+
"Gesticulating",
|
|
278
|
+
"Germinating",
|
|
279
|
+
"Gitifying",
|
|
280
|
+
"Grooving",
|
|
281
|
+
"Gusting",
|
|
282
|
+
"Harmonizing",
|
|
283
|
+
"Hashing",
|
|
284
|
+
"Hatching",
|
|
285
|
+
"Herding",
|
|
286
|
+
"Honking",
|
|
287
|
+
"Hullaballooing",
|
|
288
|
+
"Hyperspacing",
|
|
289
|
+
"Ideating",
|
|
290
|
+
"Imagining",
|
|
291
|
+
"Improvising",
|
|
292
|
+
"Incubating",
|
|
293
|
+
"Inferring",
|
|
294
|
+
"Infusing",
|
|
295
|
+
"Ionizing",
|
|
296
|
+
"Jitterbugging",
|
|
297
|
+
"Julienning",
|
|
298
|
+
"Kneading",
|
|
299
|
+
"Leavening",
|
|
300
|
+
"Levitating",
|
|
301
|
+
"Lollygagging",
|
|
302
|
+
"Manifesting",
|
|
303
|
+
"Marinating",
|
|
304
|
+
"Meandering",
|
|
305
|
+
"Metamorphosing",
|
|
306
|
+
"Misting",
|
|
307
|
+
"Moonwalking",
|
|
308
|
+
"Moseying",
|
|
309
|
+
"Mulling",
|
|
310
|
+
"Mustering",
|
|
311
|
+
"Musing",
|
|
312
|
+
"Nebulizing",
|
|
313
|
+
"Nesting",
|
|
314
|
+
"Newspapering",
|
|
315
|
+
"Noodling",
|
|
316
|
+
"Nucleating",
|
|
317
|
+
"Orbiting",
|
|
318
|
+
"Orchestrating",
|
|
319
|
+
"Osmosing",
|
|
320
|
+
"Perambulating",
|
|
321
|
+
"Percolating",
|
|
322
|
+
"Perusing",
|
|
323
|
+
"Philosophising",
|
|
324
|
+
"Photosynthesizing",
|
|
325
|
+
"Pollinating",
|
|
326
|
+
"Pondering",
|
|
327
|
+
"Pontificating",
|
|
328
|
+
"Pouncing",
|
|
329
|
+
"Precipitating",
|
|
330
|
+
"Prestidigitating",
|
|
331
|
+
"Processing",
|
|
332
|
+
"Proofing",
|
|
333
|
+
"Propagating",
|
|
334
|
+
"Puttering",
|
|
335
|
+
"Puzzling",
|
|
336
|
+
"Quantumizing",
|
|
337
|
+
"Razzle-dazzling",
|
|
338
|
+
"Razzmatazzing",
|
|
339
|
+
"Recombobulating",
|
|
340
|
+
"Reticulating",
|
|
341
|
+
"Roosting",
|
|
342
|
+
"Ruminating",
|
|
343
|
+
"Sautéing",
|
|
344
|
+
"Scampering",
|
|
345
|
+
"Schlepping",
|
|
346
|
+
"Scurrying",
|
|
347
|
+
"Seasoning",
|
|
348
|
+
"Shenaniganing",
|
|
349
|
+
"Shimmying",
|
|
350
|
+
"Simmering",
|
|
351
|
+
"Skedaddling",
|
|
352
|
+
"Sketching",
|
|
353
|
+
"Slithering",
|
|
354
|
+
"Smooshing",
|
|
355
|
+
"Sock-hopping",
|
|
356
|
+
"Spelunking",
|
|
357
|
+
"Spinning",
|
|
358
|
+
"Sprouting",
|
|
359
|
+
"Stewing",
|
|
360
|
+
"Sublimating",
|
|
361
|
+
"Swirling",
|
|
362
|
+
"Swooping",
|
|
363
|
+
"Symbioting",
|
|
364
|
+
"Synthesizing",
|
|
365
|
+
"Tempering",
|
|
366
|
+
"Thinking",
|
|
367
|
+
"Thundering",
|
|
368
|
+
"Tinkering",
|
|
369
|
+
"Tomfoolering",
|
|
370
|
+
"Topsy-turvying",
|
|
371
|
+
"Transfiguring",
|
|
372
|
+
"Transmuting",
|
|
373
|
+
"Twisting",
|
|
374
|
+
"Undulating",
|
|
375
|
+
"Unfurling",
|
|
376
|
+
"Unravelling",
|
|
377
|
+
"Vibing",
|
|
378
|
+
"Waddling",
|
|
379
|
+
"Wandering",
|
|
380
|
+
"Warping",
|
|
381
|
+
"Whatchamacalliting",
|
|
382
|
+
"Whirlpooling",
|
|
383
|
+
"Whirring",
|
|
384
|
+
"Whisking",
|
|
385
|
+
"Wibbling",
|
|
386
|
+
"Working",
|
|
387
|
+
"Wrangling",
|
|
388
|
+
"Zesting",
|
|
389
|
+
"Zigzagging",
|
|
390
|
+
] as const;
|
|
391
|
+
|
|
392
|
+
export function getRandomActivityVerb(): string {
|
|
393
|
+
return SPINNER_VERBS[Math.floor(Math.random() * SPINNER_VERBS.length)] ?? "Working";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function useActivitySpinnerVerb(active: boolean): string {
|
|
397
|
+
const [verb, setVerb] = useState(getRandomActivityVerb);
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (!active) return;
|
|
400
|
+
let id: ReturnType<typeof setInterval> | undefined;
|
|
401
|
+
const start = () => {
|
|
402
|
+
if (id !== undefined) return;
|
|
403
|
+
id = setInterval(() => {
|
|
404
|
+
setVerb(getRandomActivityVerb());
|
|
405
|
+
}, 3000);
|
|
406
|
+
};
|
|
407
|
+
const stop = () => {
|
|
408
|
+
if (id === undefined) return;
|
|
409
|
+
clearInterval(id);
|
|
410
|
+
id = undefined;
|
|
411
|
+
};
|
|
412
|
+
const onVisibility = () => {
|
|
413
|
+
if (
|
|
414
|
+
typeof document !== "undefined" &&
|
|
415
|
+
document.visibilityState === "hidden"
|
|
416
|
+
) {
|
|
417
|
+
stop();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
start();
|
|
421
|
+
};
|
|
422
|
+
onVisibility();
|
|
423
|
+
if (typeof document !== "undefined") {
|
|
424
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
425
|
+
}
|
|
426
|
+
return () => {
|
|
427
|
+
stop();
|
|
428
|
+
if (typeof document !== "undefined") {
|
|
429
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}, [active]);
|
|
433
|
+
return verb;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const SECOND_MS = 1000;
|
|
437
|
+
const noopSubscribe = () => () => {};
|
|
438
|
+
|
|
439
|
+
export function useActivityElapsedSeconds(
|
|
440
|
+
startedAt: number | undefined,
|
|
441
|
+
active: boolean,
|
|
442
|
+
) {
|
|
443
|
+
const tick = useSyncExternalStore(
|
|
444
|
+
active && startedAt
|
|
445
|
+
? (onChange) => subscribeQuantized(SECOND_MS, onChange)
|
|
446
|
+
: noopSubscribe,
|
|
447
|
+
() => quantizedSnapshot(SECOND_MS),
|
|
448
|
+
() => quantizedSnapshot(SECOND_MS),
|
|
449
|
+
);
|
|
450
|
+
if (!active || !startedAt) return 0;
|
|
451
|
+
return Math.max(0, Math.floor((tick - startedAt) / 1000));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function formatActivityElapsed(seconds: number): string {
|
|
455
|
+
if (seconds < 60) return `${seconds}s`;
|
|
456
|
+
const m = Math.floor(seconds / 60);
|
|
457
|
+
const s = seconds % 60;
|
|
458
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
459
|
+
}
|