@ocai/app-sdk-ui 0.1.0 → 0.2.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.
@@ -0,0 +1,15 @@
1
+ export type AiActivityKind = "waiting" | "reasoning" | "tools";
2
+ export interface AiActivity {
3
+ seed: number;
4
+ elapsedSeconds: number;
5
+ nowMs: number;
6
+ }
7
+ export declare function getAiActivityLabel({ kind, active, seed, elapsedSeconds, }: Pick<AiActivity, "seed" | "elapsedSeconds"> & {
8
+ kind: AiActivityKind;
9
+ active: boolean;
10
+ }): string;
11
+ export declare function traceElapsedSeconds(parts: ReadonlyArray<{
12
+ startedAtMs?: number;
13
+ completedAtMs?: number;
14
+ }>, nowMs?: number): number;
15
+ export declare function labelSeedFromString(value: string): number;
@@ -0,0 +1,16 @@
1
+ import { LucideIcon } from 'lucide-react';
2
+ import { ReactElement } from 'react';
3
+ import { AiActivity } from './activity-labels';
4
+ import { TracePart } from './segments';
5
+ export declare function humanizeToolName(type: string): string;
6
+ export type MessageTraceProps = {
7
+ parts: readonly TracePart[];
8
+ isLoading: boolean;
9
+ messageId: string;
10
+ activity?: AiActivity;
11
+ getToolLabel?: (type: string) => string | undefined;
12
+ getToolIcon?: (type: string) => LucideIcon | undefined;
13
+ defaultOpen?: boolean;
14
+ className?: string;
15
+ };
16
+ export declare const MessageTrace: ({ parts, isLoading, messageId, activity, getToolLabel, getToolIcon, defaultOpen, className, }: MessageTraceProps) => ReactElement;
@@ -0,0 +1,23 @@
1
+ export type TracePart = {
2
+ type: string;
3
+ startedAtMs?: number;
4
+ completedAtMs?: number;
5
+ [key: string]: unknown;
6
+ };
7
+ export type MessageSegment = {
8
+ kind: "trace";
9
+ parts: TracePart[];
10
+ } | {
11
+ kind: "images";
12
+ parts: TracePart[];
13
+ index: number;
14
+ } | {
15
+ kind: "part";
16
+ part: TracePart;
17
+ index: number;
18
+ };
19
+ export type BuildSegmentsOptions = {
20
+ isPresentationalTool?: (type: string) => boolean;
21
+ };
22
+ export declare function buildSegments(parts: readonly TracePart[], options?: BuildSegmentsOptions): MessageSegment[];
23
+ export declare function traceIsLoading(parts: readonly TracePart[], messageLoading: boolean): boolean;
@@ -0,0 +1,13 @@
1
+ import { ComponentProps, ReactElement } from 'react';
2
+ import { Collapsible } from '../internal/collapsible';
3
+ export type SourceItem = {
4
+ url: string;
5
+ title?: string;
6
+ };
7
+ export type SourcesProps = Omit<ComponentProps<typeof Collapsible>, "children"> & {
8
+ sources: readonly SourceItem[];
9
+ label?: (count: number) => string;
10
+ getFaviconUrl?: (url: string) => string | null;
11
+ };
12
+ export declare const Sources: ({ sources, label, getFaviconUrl, className, ...props }: SourcesProps) => ReactElement | null;
13
+ export declare const citationClassName = "ocui-cite";
@@ -0,0 +1,5 @@
1
+ import { ReactElement } from 'react';
2
+ export declare const hasRenderableValue: (v: unknown) => boolean;
3
+ export declare const ToolValue: ({ value }: {
4
+ value: unknown;
5
+ }) => ReactElement | null;
@@ -1,5 +1,5 @@
1
1
  import { ToolUIPart } from 'ai';
2
- import { ComponentProps, ReactNode } from 'react';
2
+ import { ComponentProps } from 'react';
3
3
  import { Collapsible, CollapsibleContent } from '../internal/collapsible';
4
4
  export type ToolProps = ComponentProps<typeof Collapsible>;
5
5
  export declare const Tool: ({ className, ...props }: ToolProps) => import("react").JSX.Element;
@@ -15,9 +15,9 @@ export declare const ToolContent: ({ className, ...props }: ToolContentProps) =>
15
15
  export type ToolInputProps = ComponentProps<"div"> & {
16
16
  input: ToolUIPart["input"];
17
17
  };
18
- export declare const ToolInput: ({ className, input, ...props }: ToolInputProps) => import("react").JSX.Element;
18
+ export declare const ToolInput: ({ className, input, ...props }: ToolInputProps) => import("react").JSX.Element | null;
19
19
  export type ToolOutputProps = ComponentProps<"div"> & {
20
- output: ReactNode;
20
+ output: unknown;
21
21
  errorText: ToolUIPart["errorText"];
22
22
  };
23
23
  export declare const ToolOutput: ({ className, output, errorText, ...props }: ToolOutputProps) => import("react").JSX.Element | null;
@@ -0,0 +1,2 @@
1
+ import { AiActivity } from './activity-labels';
2
+ export declare function useActivityClock(isActive: boolean, seedBasis: number): AiActivity;
@@ -0,0 +1,9 @@
1
+ import { ComponentProps } from 'react';
2
+ import { Button } from '../internal/button';
3
+ export type WebSearchToggleProps = Omit<ComponentProps<typeof Button>, "children"> & {
4
+ pressed: boolean;
5
+ onPressedChange: (pressed: boolean) => void;
6
+ label?: string;
7
+ tooltip?: string;
8
+ };
9
+ export declare const WebSearchToggle: ({ pressed, onPressedChange, label, tooltip, className, variant, size, onClick, ...props }: WebSearchToggleProps) => import("react").JSX.Element;
@@ -0,0 +1,34 @@
1
+ import { ToolUIPart } from 'ai';
2
+ import { SourceItem } from './sources';
3
+ export interface WebSearchInput {
4
+ query: string;
5
+ }
6
+ export interface WebSearchResult {
7
+ title: string;
8
+ url: string;
9
+ content: string;
10
+ score: number;
11
+ }
12
+ export interface WebSearchSuccessOutput {
13
+ query: string;
14
+ results: WebSearchResult[];
15
+ }
16
+ export interface WebSearchErrorOutput {
17
+ error: string;
18
+ }
19
+ export type WebSearchOutput = WebSearchSuccessOutput | WebSearchErrorOutput;
20
+ export type WebSearchUITool = {
21
+ input: WebSearchInput;
22
+ output: WebSearchOutput;
23
+ };
24
+ export type WebSearchUIPart = ToolUIPart<{
25
+ webSearch: WebSearchUITool;
26
+ }>;
27
+ export declare function isWebSearchError(output: unknown): output is WebSearchErrorOutput;
28
+ export declare function extractWebSearchSources(parts: ReadonlyArray<{
29
+ type?: string;
30
+ state?: string;
31
+ output?: unknown;
32
+ }>, options?: {
33
+ toolType?: string;
34
+ }): SourceItem[];
package/dist/chat.css CHANGED
@@ -60,16 +60,125 @@
60
60
  @apply font-medium text-foreground;
61
61
  }
62
62
 
63
- /* --- Code block: Rune-style shell only. Container gets the rounded border;
64
- the header strip (language label + copy/download) gets a tinted bar; the
65
- `code-block-body` (shiki) keeps its own background + token colors. --- */
63
+ /* --- Code block: Claude.ai-style shell. ONE subtle card; the language is a
64
+ plain muted label (no header bar, no divider); and a single copy button
65
+ fades in on hover at the top-right (download is turned off via the
66
+ `controls` prop in chat/response.tsx). Streamdown frames the block with its
67
+ own utility classes as a padded `bg-sidebar` card wrapping a SECOND bordered
68
+ `bg-background` card, plus an always-visible copy/download pill floated up
69
+ by -mt-10 — we flatten both cards into one and re-home the copy overlay. */
66
70
  .ocui-md [data-streamdown="code-block"] {
67
- @apply my-4 overflow-hidden rounded-lg border border-border;
71
+ @apply relative my-4 gap-0 overflow-hidden rounded-lg border border-border bg-muted/40 p-0;
68
72
  }
73
+ /* Language label: not a bar — small muted text with padding, no divider. The
74
+ inner <span> is Streamdown's `font-mono lowercase ml-1`; drop the mono and
75
+ the indent so the label lines up with the code's left edge. */
69
76
  .ocui-md [data-streamdown="code-block-header"] {
70
- @apply border-b border-border bg-muted/50 px-3 py-1 text-xs text-muted-foreground;
77
+ @apply h-auto px-4 pt-3 pb-0 text-xs font-normal text-muted-foreground;
71
78
  }
79
+ .ocui-md [data-streamdown="code-block-header"] span {
80
+ @apply ml-0 font-sans;
81
+ }
82
+ /* Copy overlay: Streamdown wraps the buttons in an un-hooked div (no
83
+ data-streamdown), floated by `-mt-10` and always visible. Target it as the
84
+ div directly wrapping `code-block-actions` (mirrors the table's :has hook),
85
+ pin it top-right, and fade it in only on hover / keyboard focus. */
86
+ .ocui-md [data-streamdown="code-block"] > div:has(> [data-streamdown="code-block-actions"]) {
87
+ @apply absolute right-2 top-2 z-10 mt-0 h-auto opacity-0 transition-opacity;
88
+ }
89
+ .ocui-md [data-streamdown="code-block"]:hover > div:has(> [data-streamdown="code-block-actions"]),
90
+ .ocui-md [data-streamdown="code-block"]:focus-within > div:has(> [data-streamdown="code-block-actions"]) {
91
+ @apply opacity-100;
92
+ }
93
+ /* Drop the pill chrome (border + tinted bg) around the button. */
94
+ .ocui-md [data-streamdown="code-block-actions"] {
95
+ @apply gap-1 rounded-md border-0 bg-transparent p-0;
96
+ }
97
+ /* Copy button: 32px square, subtle hover fill, blur so it reads over code. */
98
+ .ocui-md [data-streamdown="code-block-copy-button"] {
99
+ @apply inline-flex h-8 w-8 items-center justify-center rounded-md p-0 backdrop-blur-md hover:bg-muted;
100
+ }
101
+ /* Body: strip Streamdown's inner card (border + rounding + bg) so the code
102
+ sits flush in the single shell; keep horizontal scroll + a code padding. */
72
103
  .ocui-md [data-streamdown="code-block-body"] {
73
- @apply text-sm;
104
+ @apply rounded-none border-0 bg-transparent px-4 pb-4 pt-2 text-sm;
105
+ }
106
+
107
+ /* Opt-in inline-citation chips: <Response className={citationClassName}>
108
+ renders model-emitted "[title](url)" citations as compact pills. Opt-in so
109
+ ordinary markdown links elsewhere keep their default look. Streamdown 2.x
110
+ renders markdown links as <button data-streamdown="link"> (click-to-
111
+ confirm hardening), NOT <a href> — so target its data hook like every
112
+ other rule in this file; an href-based selector matches nothing. Stays
113
+ `inline` (never inline-flex) so the pill breaks with the surrounding text
114
+ like any other word instead of forcing a rigid box. */
115
+ .ocui-md.ocui-cite [data-streamdown="link"] {
116
+ @apply inline cursor-pointer rounded-sm bg-muted px-1.5 py-0.5 align-baseline text-xs font-normal text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-accent-foreground;
117
+ }
118
+ /* External-link hint: a small superscript arrow, added via content so it
119
+ never disturbs inline flow the way an <svg> child would. */
120
+ .ocui-md.ocui-cite [data-streamdown="link"]::after {
121
+ content: "↗";
122
+ @apply ml-0.5 align-super text-[0.65em] text-muted-foreground/70;
123
+ }
124
+
125
+ /* Activity-label shimmer: while the model streams, the trigger label
126
+ ("Thinking…", "Shaping for 4 seconds") sweeps a full-contrast highlight
127
+ left→right across the muted label text, instead of pulsing the whole word.
128
+ Codex-style (a moving highlight band clipped to the glyphs). Base is the
129
+ muted label color; the highlight is the foreground seed. Applied by
130
+ Reasoning/MessageTrace's trigger only while streaming; reduced-motion
131
+ falls back to solid muted text. */
132
+ .ocui-shimmer {
133
+ /* Two glyph-clipped layers. LAYER 1 (background-color) is a SOLID base that
134
+ always fills every glyph, so text is never transparent. LAYER 2 (gradient,
135
+ painted on top) is transparent at both ends with an opaque --foreground band
136
+ in the middle — only that band tints the glyphs; elsewhere the base shows. */
137
+ background-color: var(--muted-foreground);
138
+ background-image: linear-gradient(
139
+ to right,
140
+ transparent 0%,
141
+ var(--foreground) 40%,
142
+ var(--foreground) 60%,
143
+ transparent 100%
144
+ );
145
+ background-size: 50% 100%;
146
+ background-repeat: no-repeat;
147
+ background-position: -100% 0;
148
+ -webkit-background-clip: text;
149
+ background-clip: text;
150
+ -webkit-text-fill-color: transparent;
151
+ color: transparent;
152
+ display: inline-block;
153
+ animation: ocui-shimmer-sweep 1.6s linear infinite;
154
+ }
155
+
156
+ /* --- Tool call params/results (chat/tool-value.tsx). Structured shallow
157
+ objects render as aligned key–value rows; deep/large values fall back to
158
+ this single muted mono block — a refined replacement for the old heavy
159
+ bg-muted/50 JSON.stringify dump. --- */
160
+ .ocui-tool-json {
161
+ tab-size: 2;
162
+ line-height: 1.5;
163
+ max-height: 22rem;
164
+ overflow: auto;
165
+ }
166
+ /* Key column top-aligns with the first line of a wrapping value. */
167
+ .ocui-tool-kv > li > span:first-child {
168
+ align-self: start;
169
+ }
170
+ }
171
+
172
+ @keyframes ocui-shimmer-sweep {
173
+ from { background-position: -100% 0; }
174
+ to { background-position: 250% 0; }
175
+ }
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ .ocui-shimmer {
179
+ animation: none;
180
+ background-image: none;
181
+ -webkit-text-fill-color: var(--muted-foreground);
182
+ color: var(--muted-foreground);
74
183
  }
75
184
  }
package/dist/index.d.ts CHANGED
@@ -3,3 +3,10 @@ export { MessageContent, type MessageContentProps, type MessageContentVariant, }
3
3
  export { Reasoning, ReasoningContent, ReasoningTrigger, reasoningTextClassName, type ReasoningProps, type ReasoningTriggerProps, } from './chat/reasoning';
4
4
  export { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, type ToolContentProps, type ToolHeaderProps, type ToolInputProps, type ToolOutputProps, type ToolProps, } from './chat/tool';
5
5
  export { Action, Actions, type ActionProps, type ActionsProps, } from './chat/actions';
6
+ export { MessageTrace, humanizeToolName, type MessageTraceProps, } from './chat/message-trace';
7
+ export { buildSegments, traceIsLoading, type BuildSegmentsOptions, type MessageSegment, type TracePart, } from './chat/segments';
8
+ export { getAiActivityLabel, labelSeedFromString, traceElapsedSeconds, type AiActivity, type AiActivityKind, } from './chat/activity-labels';
9
+ export { useActivityClock } from './chat/use-activity-clock';
10
+ export { Sources, citationClassName, type SourceItem, type SourcesProps, } from './chat/sources';
11
+ export { extractWebSearchSources, isWebSearchError, type WebSearchErrorOutput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WebSearchSuccessOutput, type WebSearchUIPart, type WebSearchUITool, } from './chat/web-search';
12
+ export { WebSearchToggle, type WebSearchToggleProps, } from './chat/web-search-toggle';
package/dist/index.js CHANGED
@@ -4,78 +4,81 @@ import { Streamdown as t } from "streamdown";
4
4
  import { clsx as n } from "clsx";
5
5
  import { twMerge as r } from "tailwind-merge";
6
6
  import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
7
- import { CheckIcon as s, ChevronDownIcon as c, ClockIcon as l, Loader2Icon as u, WrenchIcon as d, XIcon as f } from "lucide-react";
8
- import { createContext as p, memo as m, useContext as h, useEffect as g, useState as _ } from "react";
9
- import { Collapsible as v, Slot as y, Tooltip as b } from "radix-ui";
10
- import { cva as x } from "class-variance-authority";
7
+ import { CheckIcon as s, ChevronDownIcon as c, CircleCheckIcon as l, ClockIcon as u, GlobeIcon as d, Loader2Icon as f, WrenchIcon as p, XIcon as m } from "lucide-react";
8
+ import { createContext as h, isValidElement as g, memo as _, useContext as v, useEffect as y, useRef as ee, useState as b } from "react";
9
+ import { Collapsible as x, Slot as te, Tooltip as S } from "radix-ui";
10
+ import { cva as ne } from "class-variance-authority";
11
11
  //#region src/lib/cn.ts
12
- function S(...e) {
12
+ function C(...e) {
13
13
  return r(n(e));
14
14
  }
15
15
  //#endregion
16
16
  //#region src/chat/response.tsx
17
- var C = e({ singleDollarTextMath: !0 });
18
- function w({ className: e, ...n }) {
17
+ var re = e({ singleDollarTextMath: !0 });
18
+ function ie({ className: e, ...n }) {
19
19
  return /* @__PURE__ */ a(t, {
20
- className: S("ocui-md size-full text-base font-normal leading-6 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:wrap-break-word [&_pre]:max-w-full [&_pre]:overflow-x-auto", e),
21
- plugins: { math: C },
22
- controls: { table: !1 },
20
+ className: C("ocui-md size-full text-base font-normal leading-6 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_code]:whitespace-pre-wrap [&_code]:wrap-break-word [&_pre]:max-w-full [&_pre]:overflow-x-auto", e),
21
+ plugins: { math: re },
22
+ controls: {
23
+ table: !1,
24
+ code: { download: !1 }
25
+ },
23
26
  ...n
24
27
  });
25
28
  }
26
29
  //#endregion
27
30
  //#region src/chat/message.tsx
28
- var T = {
31
+ var ae = {
29
32
  user: "w-fit wrap-break-word rounded-md bg-card px-3 py-1.5 text-primary",
30
33
  assistant: "bg-transparent px-0 py-0 text-left text-foreground"
31
- }, E = ({ children: e, className: t, variant: n = "user", ...r }) => /* @__PURE__ */ a("div", {
32
- className: S("flex flex-col gap-2 overflow-hidden text-base font-normal leading-6", T[n], t),
34
+ }, oe = ({ children: e, className: t, variant: n = "user", ...r }) => /* @__PURE__ */ a("div", {
35
+ className: C("flex flex-col gap-2 overflow-hidden text-base font-normal leading-6", ae[n], t),
33
36
  ...r,
34
37
  children: e
35
38
  });
36
39
  //#endregion
37
40
  //#region src/internal/collapsible.tsx
38
- function D({ ...e }) {
39
- return /* @__PURE__ */ a(v.Root, {
41
+ function w({ ...e }) {
42
+ return /* @__PURE__ */ a(x.Root, {
40
43
  "data-slot": "collapsible",
41
44
  ...e
42
45
  });
43
46
  }
44
- function O({ ...e }) {
45
- return /* @__PURE__ */ a(v.CollapsibleTrigger, {
47
+ function T({ ...e }) {
48
+ return /* @__PURE__ */ a(x.CollapsibleTrigger, {
46
49
  "data-slot": "collapsible-trigger",
47
50
  ...e
48
51
  });
49
52
  }
50
- function k({ ...e }) {
51
- return /* @__PURE__ */ a(v.CollapsibleContent, {
53
+ function E({ ...e }) {
54
+ return /* @__PURE__ */ a(x.CollapsibleContent, {
52
55
  "data-slot": "collapsible-content",
53
56
  ...e
54
57
  });
55
58
  }
56
59
  //#endregion
57
60
  //#region src/chat/reasoning.tsx
58
- function A({ prop: e, defaultProp: t, onChange: n }) {
59
- let [r, i] = _(t), a = e !== void 0;
61
+ function se({ prop: e, defaultProp: t, onChange: n }) {
62
+ let [r, i] = b(t), a = e !== void 0;
60
63
  return [a ? e : r, (e) => {
61
64
  a || i(e), n?.(e);
62
65
  }];
63
66
  }
64
- var j = p(null), M = () => {
65
- let e = h(j);
67
+ var D = h(null), ce = () => {
68
+ let e = v(D);
66
69
  if (!e) throw Error("Reasoning components must be used within Reasoning");
67
70
  return e;
68
- }, N = 500, P = m(({ className: e, isStreaming: t = !1, duration: n = 0, open: r, defaultOpen: i = !0, onOpenChange: o, children: s, ...c }) => {
69
- let [l, u] = A({
71
+ }, le = 500, O = _(({ className: e, isStreaming: t = !1, duration: n = 0, open: r, defaultOpen: i = !0, onOpenChange: o, children: s, ...c }) => {
72
+ let [l, u] = se({
70
73
  prop: r,
71
74
  defaultProp: i,
72
75
  onChange: o
73
- }), [d, f] = _(!1);
74
- return g(() => {
76
+ }), [d, f] = b(!1);
77
+ return y(() => {
75
78
  if (i && !t && l && !d) {
76
79
  let e = setTimeout(() => {
77
80
  u(!1), f(!0);
78
- }, N);
81
+ }, le);
79
82
  return () => clearTimeout(e);
80
83
  }
81
84
  }, [
@@ -84,15 +87,15 @@ var j = p(null), M = () => {
84
87
  i,
85
88
  u,
86
89
  d
87
- ]), /* @__PURE__ */ a(j.Provider, {
90
+ ]), /* @__PURE__ */ a(D.Provider, {
88
91
  value: {
89
92
  isStreaming: t,
90
93
  isOpen: l,
91
94
  setIsOpen: u,
92
95
  duration: n
93
96
  },
94
- children: /* @__PURE__ */ a(D, {
95
- className: S("not-prose", e),
97
+ children: /* @__PURE__ */ a(w, {
98
+ className: C("not-prose", e),
96
99
  onOpenChange: u,
97
100
  open: l,
98
101
  ...c,
@@ -100,39 +103,106 @@ var j = p(null), M = () => {
100
103
  })
101
104
  });
102
105
  });
103
- function F({ variant: e, isStreaming: t, duration: n }) {
106
+ function ue({ variant: e, isStreaming: t, duration: n }) {
104
107
  let r = e === "tools";
105
108
  return t ? r ? "Working…" : "Thinking…" : n > 0 ? r ? `Worked for ${n}s` : `Thought for ${n}s` : r ? "Worked" : "Thought";
106
109
  }
107
- var I = m(({ className: e, children: t, variant: n = "reasoning", label: r, ...s }) => {
108
- let { isStreaming: l, isOpen: u, duration: f } = M(), p = r ?? F({
110
+ var k = _(({ className: e, children: t, variant: n = "reasoning", label: r, ...s }) => {
111
+ let { isStreaming: l, isOpen: u, duration: d } = ce(), f = r ?? ue({
109
112
  variant: n,
110
113
  isStreaming: l,
111
- duration: f
114
+ duration: d
112
115
  });
113
- return /* @__PURE__ */ a(O, {
114
- className: S("flex items-center gap-1 rounded-md px-1.5 py-0.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", e),
116
+ return /* @__PURE__ */ a(T, {
117
+ className: C("flex items-center gap-1 rounded-md px-1.5 py-0.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", e),
115
118
  ...s,
116
119
  children: t ?? /* @__PURE__ */ o(i, { children: [
117
- n === "tools" && /* @__PURE__ */ a(d, { className: "size-3" }),
120
+ n === "tools" && /* @__PURE__ */ a(p, { className: "size-3" }),
118
121
  /* @__PURE__ */ a("span", {
119
- className: S(l && "animate-pulse"),
120
- children: p
122
+ className: C(l && "ocui-shimmer"),
123
+ children: f
121
124
  }),
122
- /* @__PURE__ */ a(c, { className: S("size-2.5 transition-transform", u ? "rotate-180" : "rotate-0") })
125
+ /* @__PURE__ */ a(c, { className: C("size-2.5 transition-transform", u ? "rotate-180" : "rotate-0") })
123
126
  ] })
124
127
  });
125
- }), L = "grid gap-1 text-sm text-muted-foreground/80 **:text-sm [&_code]:bg-transparent [&_code]:p-0 [&_code]:font-semibold [&_code]:text-foreground [&_li]:my-0 [&_ol]:my-1 [&_p]:my-0 [&_ul]:my-1", R = m(({ className: e, ...t }) => /* @__PURE__ */ a(k, {
126
- className: S(L, "mt-1.5", e),
128
+ }), A = "grid gap-1 text-sm text-muted-foreground/80 **:text-sm [&_code]:bg-transparent [&_code]:p-0 [&_code]:font-semibold [&_code]:text-foreground [&_li]:my-0 [&_ol]:my-1 [&_p]:my-0 [&_ul]:my-1", j = _(({ className: e, ...t }) => /* @__PURE__ */ a(E, {
129
+ className: C(A, "mt-1.5", e),
127
130
  ...t
128
131
  }));
129
- P.displayName = "Reasoning", I.displayName = "ReasoningTrigger", R.displayName = "ReasoningContent";
132
+ O.displayName = "Reasoning", k.displayName = "ReasoningTrigger", j.displayName = "ReasoningContent";
130
133
  //#endregion
131
- //#region src/chat/tool.tsx
132
- var z = ({ className: e, ...t }) => /* @__PURE__ */ a(D, {
133
- className: S("not-prose w-full rounded-md border border-border", e),
134
+ //#region src/chat/tool-value.tsx
135
+ var de = 8, M = 240, N = 2e4, P = (e) => e === null || typeof e != "object" && typeof e != "function", F = (e) => {
136
+ if (typeof e != "object" || !e) return !1;
137
+ let t = Object.getPrototypeOf(e);
138
+ return t === Object.prototype || t === null;
139
+ }, I = (e) => Array.isArray(e) && e.every(P), fe = (e) => {
140
+ if (!F(e)) return !1;
141
+ let t = Object.keys(e);
142
+ return t.length === 0 || t.length > de ? !1 : t.every((t) => P(e[t]) || I(e[t]));
143
+ }, L = (e) => g(e) ? !0 : e == null ? !1 : typeof e == "string" ? e.trim().length > 0 : P(e) ? !0 : Array.isArray(e) ? e.length > 0 : F(e) ? Object.keys(e).length > 0 : !0, pe = (e) => {
144
+ try {
145
+ let t = JSON.stringify(e, null, 2);
146
+ return t === void 0 ? String(e) : t.length > N ? `${t.slice(0, N)}\n… (truncated)` : t;
147
+ } catch {
148
+ return String(e);
149
+ }
150
+ }, R = ({ value: e }) => {
151
+ if (e == null) return /* @__PURE__ */ a("span", {
152
+ className: "font-mono text-muted-foreground",
153
+ children: "null"
154
+ });
155
+ if (typeof e != "string") return /* @__PURE__ */ a("span", {
156
+ className: "font-mono text-foreground",
157
+ children: String(e)
158
+ });
159
+ if (e === "") return /* @__PURE__ */ a("span", {
160
+ className: "text-muted-foreground italic",
161
+ children: "empty"
162
+ });
163
+ let t = e.length > M;
164
+ return /* @__PURE__ */ a("span", {
165
+ className: "text-foreground",
166
+ title: t ? e : void 0,
167
+ children: t ? `${e.slice(0, M)}…` : e
168
+ });
169
+ }, z = ({ value: e }) => e.length === 0 ? /* @__PURE__ */ a("span", {
170
+ className: "text-muted-foreground italic",
171
+ children: "none"
172
+ }) : /* @__PURE__ */ a("span", {
173
+ className: "text-foreground",
174
+ children: e.map((e, t) => /* @__PURE__ */ o("span", { children: [t > 0 && /* @__PURE__ */ a("span", {
175
+ className: "px-1 text-muted-foreground/70",
176
+ children: "·"
177
+ }), /* @__PURE__ */ a(R, { value: e })] }, t))
178
+ }), me = ({ data: e }) => /* @__PURE__ */ a("ul", {
179
+ className: "ocui-tool-kv grid min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs",
180
+ children: Object.entries(e).map(([e, t]) => /* @__PURE__ */ o("li", {
181
+ className: "contents",
182
+ children: [/* @__PURE__ */ a("span", {
183
+ className: "break-words text-muted-foreground",
184
+ children: e
185
+ }), /* @__PURE__ */ a("span", {
186
+ className: "min-w-0 break-words whitespace-pre-wrap",
187
+ children: I(t) ? /* @__PURE__ */ a(z, { value: t }) : /* @__PURE__ */ a(R, { value: t })
188
+ })]
189
+ }, e))
190
+ }), he = ({ value: e }) => /* @__PURE__ */ a("pre", {
191
+ className: "ocui-tool-json overflow-x-auto rounded-md bg-muted/40 px-2.5 py-2 font-mono text-xs text-muted-foreground",
192
+ children: pe(e)
193
+ }), B = ({ value: e }) => g(e) ? /* @__PURE__ */ a(i, { children: e }) : e == null ? null : typeof e == "string" ? e.trim() === "" ? null : /* @__PURE__ */ a("div", {
194
+ className: "whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-muted-foreground",
195
+ children: e
196
+ }) : P(e) ? /* @__PURE__ */ a("div", {
197
+ className: "font-mono text-xs text-foreground",
198
+ children: String(e)
199
+ }) : fe(e) ? /* @__PURE__ */ a(me, { data: e }) : I(e) ? /* @__PURE__ */ a("div", {
200
+ className: "text-xs",
201
+ children: /* @__PURE__ */ a(z, { value: e })
202
+ }) : /* @__PURE__ */ a(he, { value: e }), ge = ({ className: e, ...t }) => /* @__PURE__ */ a(w, {
203
+ className: C("not-prose w-full rounded-md border border-border", e),
134
204
  ...t
135
- }), B = {
205
+ }), _e = {
136
206
  "input-streaming": "Pending",
137
207
  "input-available": "Running",
138
208
  "approval-requested": "Pending",
@@ -140,54 +210,54 @@ var z = ({ className: e, ...t }) => /* @__PURE__ */ a(D, {
140
210
  "output-available": "Completed",
141
211
  "output-error": "Error",
142
212
  "output-denied": "Denied"
143
- }, V = {
144
- "input-streaming": /* @__PURE__ */ a(u, { className: "size-3.5 animate-spin" }),
145
- "input-available": /* @__PURE__ */ a(u, { className: "size-3.5 animate-spin" }),
146
- "approval-requested": /* @__PURE__ */ a(l, { className: "size-3.5" }),
213
+ }, ve = {
214
+ "input-streaming": /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin" }),
215
+ "input-available": /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin" }),
216
+ "approval-requested": /* @__PURE__ */ a(u, { className: "size-3.5" }),
147
217
  "approval-responded": /* @__PURE__ */ a(s, { className: "size-3.5" }),
148
218
  "output-available": /* @__PURE__ */ a(s, { className: "size-3.5" }),
149
- "output-error": /* @__PURE__ */ a(f, { className: "size-3.5 text-destructive" }),
150
- "output-denied": /* @__PURE__ */ a(f, { className: "size-3.5 text-destructive" })
151
- }, H = ({ state: e }) => /* @__PURE__ */ o("span", {
219
+ "output-error": /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" }),
220
+ "output-denied": /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" })
221
+ }, ye = ({ state: e }) => /* @__PURE__ */ o("span", {
152
222
  className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground",
153
- children: [V[e], B[e]]
154
- }), U = ({ className: e, type: t, state: n, title: r, ...i }) => /* @__PURE__ */ o(O, {
155
- className: S("group flex w-full min-w-0 items-center justify-between gap-2 rounded-md p-2.5 transition-colors hover:bg-muted/50", e),
223
+ children: [ve[e], _e[e]]
224
+ }), be = ({ className: e, type: t, state: n, title: r, ...i }) => /* @__PURE__ */ o(T, {
225
+ className: C("group flex w-full min-w-0 items-center justify-between gap-2 rounded-md p-2.5 transition-colors hover:bg-muted/50", e),
156
226
  ...i,
157
227
  children: [/* @__PURE__ */ o("div", {
158
228
  className: "flex min-w-0 flex-1 items-center gap-2",
159
- children: [/* @__PURE__ */ a(d, { className: "size-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ a("span", {
229
+ children: [/* @__PURE__ */ a(p, { className: "size-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ a("span", {
160
230
  className: "truncate text-sm font-medium",
161
231
  children: r ?? t
162
232
  })]
163
233
  }), /* @__PURE__ */ o("div", {
164
234
  className: "flex shrink-0 items-center gap-2",
165
- children: [/* @__PURE__ */ a(H, { state: n }), /* @__PURE__ */ a(c, { className: "size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })]
235
+ children: [/* @__PURE__ */ a(ye, { state: n }), /* @__PURE__ */ a(c, { className: "size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })]
166
236
  })]
167
- }), W = ({ className: e, ...t }) => /* @__PURE__ */ a(k, {
168
- className: S("data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in", e),
237
+ }), xe = ({ className: e, ...t }) => /* @__PURE__ */ a(E, {
238
+ className: C("data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in", e),
169
239
  ...t
170
- }), G = ({ className: e, input: t, ...n }) => /* @__PURE__ */ o("div", {
171
- className: S("space-y-1.5 overflow-hidden px-2.5 pb-2.5", e),
240
+ }), Se = ({ className: e, input: t, ...n }) => L(t) ? /* @__PURE__ */ o("div", {
241
+ className: C("space-y-1.5 overflow-hidden px-2.5 pb-2.5", e),
172
242
  ...n,
173
243
  children: [/* @__PURE__ */ a("h4", {
174
244
  className: "text-xs font-medium tracking-wide text-muted-foreground uppercase",
175
245
  children: "Parameters"
176
- }), /* @__PURE__ */ a("pre", {
177
- className: "overflow-x-auto rounded-md bg-muted/50 p-2.5 font-mono text-xs",
178
- children: JSON.stringify(t, null, 2)
179
- })]
180
- }), K = ({ className: e, output: t, errorText: n, ...r }) => t || n ? /* @__PURE__ */ o("div", {
181
- className: S("space-y-1.5 px-2.5 pb-2.5", e),
246
+ }), /* @__PURE__ */ a(B, { value: t })]
247
+ }) : null, Ce = ({ className: e, output: t, errorText: n, ...r }) => L(t) || n ? /* @__PURE__ */ o("div", {
248
+ className: C("space-y-1.5 px-2.5 pb-2.5", e),
182
249
  ...r,
183
250
  children: [/* @__PURE__ */ a("h4", {
184
251
  className: "text-xs font-medium tracking-wide text-muted-foreground uppercase",
185
252
  children: n ? "Error" : "Result"
186
- }), /* @__PURE__ */ o("div", {
187
- className: S("overflow-x-auto rounded-md text-xs [&_table]:w-full", n ? "bg-destructive/10 p-2.5 text-destructive" : "bg-muted/50 p-2.5 text-foreground"),
188
- children: [n && /* @__PURE__ */ a("div", { children: n }), t && /* @__PURE__ */ a("div", { children: t })]
253
+ }), n ? /* @__PURE__ */ a("div", {
254
+ className: "whitespace-pre-wrap break-words rounded-md bg-destructive/10 px-2.5 py-2 text-xs text-destructive",
255
+ children: n
256
+ }) : /* @__PURE__ */ a("div", {
257
+ className: "text-xs [&_table]:w-full",
258
+ children: /* @__PURE__ */ a(B, { value: t })
189
259
  })]
190
- }) : null, q = x("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
260
+ }) : null, we = ne("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
191
261
  variants: {
192
262
  variant: {
193
263
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
@@ -213,12 +283,12 @@ var z = ({ className: e, ...t }) => /* @__PURE__ */ a(D, {
213
283
  size: "default"
214
284
  }
215
285
  });
216
- function J({ className: e, variant: t = "default", size: n = "default", asChild: r = !1, ...i }) {
217
- return /* @__PURE__ */ a(r ? y.Root : "button", {
286
+ function V({ className: e, variant: t = "default", size: n = "default", asChild: r = !1, ...i }) {
287
+ return /* @__PURE__ */ a(r ? te.Root : "button", {
218
288
  "data-slot": "button",
219
289
  "data-variant": t,
220
290
  "data-size": n,
221
- className: S(q({
291
+ className: C(we({
222
292
  variant: t,
223
293
  size: n,
224
294
  className: e
@@ -228,43 +298,43 @@ function J({ className: e, variant: t = "default", size: n = "default", asChild:
228
298
  }
229
299
  //#endregion
230
300
  //#region src/internal/tooltip.tsx
231
- function Y({ delayDuration: e = 0, ...t }) {
232
- return /* @__PURE__ */ a(b.Provider, {
301
+ function H({ delayDuration: e = 0, ...t }) {
302
+ return /* @__PURE__ */ a(S.Provider, {
233
303
  "data-slot": "tooltip-provider",
234
304
  delayDuration: e,
235
305
  ...t
236
306
  });
237
307
  }
238
- function X({ ...e }) {
239
- return /* @__PURE__ */ a(b.Root, {
308
+ function U({ ...e }) {
309
+ return /* @__PURE__ */ a(S.Root, {
240
310
  "data-slot": "tooltip",
241
311
  ...e
242
312
  });
243
313
  }
244
- function Z({ ...e }) {
245
- return /* @__PURE__ */ a(b.Trigger, {
314
+ function W({ ...e }) {
315
+ return /* @__PURE__ */ a(S.Trigger, {
246
316
  "data-slot": "tooltip-trigger",
247
317
  ...e
248
318
  });
249
319
  }
250
- function Q({ className: e, sideOffset: t = 0, children: n, ...r }) {
251
- return /* @__PURE__ */ a(b.Portal, { children: /* @__PURE__ */ o(b.Content, {
320
+ function G({ className: e, sideOffset: t = 0, children: n, ...r }) {
321
+ return /* @__PURE__ */ a(S.Portal, { children: /* @__PURE__ */ o(S.Content, {
252
322
  "data-slot": "tooltip-content",
253
323
  sideOffset: t,
254
- className: S("z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:animate-in data-[state=instant-open]:fade-in-0 data-[state=instant-open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", e),
324
+ className: C("z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:animate-in data-[state=instant-open]:fade-in-0 data-[state=instant-open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", e),
255
325
  ...r,
256
- children: [n, /* @__PURE__ */ a(b.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] border-r border-b border-border bg-popover fill-popover" })]
326
+ children: [n, /* @__PURE__ */ a(S.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] border-r border-b border-border bg-popover fill-popover" })]
257
327
  }) });
258
328
  }
259
329
  //#endregion
260
330
  //#region src/chat/actions.tsx
261
- var $ = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
262
- className: S("flex items-center", e),
331
+ var Te = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
332
+ className: C("flex items-center", e),
263
333
  ...n,
264
334
  children: t
265
- }), ee = ({ tooltip: e, children: t, label: n, className: r, variant: i = "ghost", size: s = "icon", ...c }) => {
266
- let l = /* @__PURE__ */ o(J, {
267
- className: S("relative", r),
335
+ }), Ee = ({ tooltip: e, children: t, label: n, className: r, variant: i = "ghost", size: s = "icon", ...c }) => {
336
+ let l = /* @__PURE__ */ o(V, {
337
+ className: C("relative", r),
268
338
  size: s,
269
339
  type: "button",
270
340
  variant: i,
@@ -274,13 +344,367 @@ var $ = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
274
344
  children: n || e
275
345
  })]
276
346
  });
277
- return e ? /* @__PURE__ */ a(Y, {
278
- delayDuration: 300,
279
- children: /* @__PURE__ */ o(X, { children: [/* @__PURE__ */ a(Z, {
347
+ return e ? /* @__PURE__ */ a(H, {
348
+ delayDuration: 500,
349
+ children: /* @__PURE__ */ o(U, { children: [/* @__PURE__ */ a(W, {
280
350
  asChild: !0,
281
351
  children: l
282
- }), /* @__PURE__ */ a(Q, { children: /* @__PURE__ */ a("p", { children: e }) })] })
352
+ }), /* @__PURE__ */ a(G, { children: /* @__PURE__ */ a("p", { children: e }) })] })
283
353
  }) : l;
354
+ }, K = [
355
+ {
356
+ active: "Thinking",
357
+ done: "Thought"
358
+ },
359
+ {
360
+ active: "Reading",
361
+ done: "Read"
362
+ },
363
+ {
364
+ active: "Drafting",
365
+ done: "Drafted"
366
+ },
367
+ {
368
+ active: "Shaping",
369
+ done: "Shaped"
370
+ },
371
+ {
372
+ active: "Composing",
373
+ done: "Composed"
374
+ },
375
+ {
376
+ active: "Polishing",
377
+ done: "Polished"
378
+ },
379
+ {
380
+ active: "Mapping",
381
+ done: "Mapped"
382
+ },
383
+ {
384
+ active: "Sorting",
385
+ done: "Sorted"
386
+ },
387
+ {
388
+ active: "Refining",
389
+ done: "Refined"
390
+ },
391
+ {
392
+ active: "Working",
393
+ done: "Done"
394
+ }
395
+ ], De = {
396
+ waiting: K,
397
+ reasoning: K,
398
+ tools: [{
399
+ active: "Working",
400
+ done: "Worked"
401
+ }]
402
+ }, Oe = 5;
403
+ function q({ kind: e, active: t, seed: n, elapsedSeconds: r }) {
404
+ let i = De[e], a = Math.floor(Math.max(0, r) / Oe), o = i[Math.abs(n + a) % i.length], s = t ? o.active : o.done;
405
+ return r <= 0 ? s : `${s} for ${r}s`;
406
+ }
407
+ function J(e, t) {
408
+ let n = Infinity, r = -Infinity, i = !1;
409
+ for (let t of e) typeof t.startedAtMs == "number" && (n = Math.min(n, t.startedAtMs), typeof t.completedAtMs == "number" ? r = Math.max(r, t.completedAtMs) : i = !0);
410
+ if (n === Infinity) return 0;
411
+ let a = i ? t ?? r : r;
412
+ return a === -Infinity ? 0 : Math.max(0, Math.floor((a - n) / 1e3));
413
+ }
414
+ function Y(e) {
415
+ let t = 0;
416
+ for (let n = 0; n < e.length; n += 1) t = t * 31 + e.charCodeAt(n) | 0;
417
+ return t;
418
+ }
419
+ //#endregion
420
+ //#region src/chat/message-trace.tsx
421
+ function X(e) {
422
+ let t = e.replace(/^tool-/, "").replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
423
+ return t.charAt(0).toUpperCase() + t.slice(1);
424
+ }
425
+ var ke = ({ parts: e, isLoading: t, messageId: n, activity: r, getToolLabel: i, getToolIcon: s, defaultOpen: c = !1, className: l }) => {
426
+ let u = e.filter((e) => e.type === "reasoning").map((e) => String(e.text ?? "")).filter((e) => e.trim()).join("\n\n"), d = u.trim().length > 0, f = [], p = !1;
427
+ for (let t of e) t.type.startsWith("tool-") ? (p = !0, f.push({
428
+ kind: "tool",
429
+ label: i?.(t.type) ?? X(t.type),
430
+ state: String(t.state ?? ""),
431
+ toolType: t.type
432
+ })) : t.type === "text" && String(t.text ?? "").trim() && f.push({
433
+ kind: "note",
434
+ text: String(t.text)
435
+ });
436
+ p && !t && f.push({ kind: "done" });
437
+ let m = e.some((e) => typeof e.startedAtMs == "number") ? J(e, r?.nowMs) : r?.elapsedSeconds ?? 0, h = r?.seed ?? Y(n), g = q({
438
+ kind: d ? "reasoning" : "tools",
439
+ active: t,
440
+ seed: h,
441
+ elapsedSeconds: t ? m : 0
442
+ }), _ = d || f.length > 0, [v, y] = b(c);
443
+ return /* @__PURE__ */ o("div", {
444
+ className: C("not-prose flex flex-col", l),
445
+ children: [/* @__PURE__ */ a("button", {
446
+ type: "button",
447
+ "data-testid": "message-reasoning",
448
+ onClick: () => y((e) => !e),
449
+ className: "flex items-center gap-1 self-start text-sm text-muted-foreground transition-colors hover:text-foreground",
450
+ children: /* @__PURE__ */ o("span", {
451
+ className: C(t && "ocui-shimmer"),
452
+ children: [g, _ ? "..." : ""]
453
+ })
454
+ }), v && _ && /* @__PURE__ */ o("div", {
455
+ className: "mt-2 flex flex-col gap-2",
456
+ children: [d && /* @__PURE__ */ a(ie, {
457
+ className: "grid gap-1 text-sm text-muted-foreground/80 **:text-sm [&_code]:bg-transparent [&_code]:p-0 [&_code]:font-semibold [&_code]:text-foreground [&_li]:my-0 [&_ol]:my-1 [&_p]:my-0 [&_ul]:my-1",
458
+ children: u
459
+ }), f.length > 0 && /* @__PURE__ */ a("div", {
460
+ className: "flex flex-col",
461
+ children: f.map((e, t) => /* @__PURE__ */ a(Ae, {
462
+ node: e,
463
+ isFirst: t === 0,
464
+ isLast: t === f.length - 1,
465
+ getToolIcon: s
466
+ }, `step-${t}`))
467
+ })]
468
+ })]
469
+ });
470
+ };
471
+ function Ae({ node: e, isFirst: t, isLast: n, getToolIcon: r }) {
472
+ return /* @__PURE__ */ o("div", {
473
+ className: "flex items-stretch gap-2.5",
474
+ children: [/* @__PURE__ */ o("div", {
475
+ className: "flex w-4 flex-col items-center",
476
+ children: [
477
+ /* @__PURE__ */ a("div", { className: C("w-px flex-1", t ? "bg-transparent" : "bg-border") }),
478
+ /* @__PURE__ */ a(je, {
479
+ node: e,
480
+ getToolIcon: r
481
+ }),
482
+ /* @__PURE__ */ a("div", { className: C("w-px flex-1", n ? "bg-transparent" : "bg-border") })
483
+ ]
484
+ }), /* @__PURE__ */ a("div", {
485
+ className: "min-w-0 py-1 text-sm leading-5",
486
+ children: e.kind === "tool" ? /* @__PURE__ */ a("span", {
487
+ className: "text-muted-foreground",
488
+ children: e.label
489
+ }) : e.kind === "note" ? /* @__PURE__ */ a("span", {
490
+ className: "text-muted-foreground/80",
491
+ children: e.text
492
+ }) : /* @__PURE__ */ a("span", {
493
+ className: "text-muted-foreground",
494
+ children: "Done"
495
+ })
496
+ })]
497
+ });
498
+ }
499
+ function je({ node: e, getToolIcon: t }) {
500
+ let n = "flex items-center justify-center bg-background py-0.5";
501
+ return e.kind === "note" ? /* @__PURE__ */ a("div", {
502
+ className: n,
503
+ children: /* @__PURE__ */ a("div", { className: "size-1 rounded-full bg-muted-foreground/60" })
504
+ }) : e.kind === "tool" ? e.state === "output-error" || e.state === "output-denied" ? /* @__PURE__ */ a("div", {
505
+ className: n,
506
+ children: /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" })
507
+ }) : e.state === "output-available" ? /* @__PURE__ */ a("div", {
508
+ className: n,
509
+ children: /* @__PURE__ */ a(t?.(e.toolType) ?? s, { className: "size-3.5 text-muted-foreground" })
510
+ }) : /* @__PURE__ */ a("div", {
511
+ className: n,
512
+ children: /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin text-muted-foreground" })
513
+ }) : /* @__PURE__ */ a("div", {
514
+ className: n,
515
+ children: /* @__PURE__ */ a(l, { className: "size-4 text-muted-foreground" })
516
+ });
517
+ }
518
+ //#endregion
519
+ //#region src/chat/segments.ts
520
+ function Z(e, t) {
521
+ return e.startsWith("tool-") && !t(e);
522
+ }
523
+ function Me(e, t) {
524
+ let n = t?.isPresentationalTool ?? (() => !1), r = -1;
525
+ e.forEach((e, t) => {
526
+ (e.type === "reasoning" || Z(e.type, n)) && (r = t);
527
+ });
528
+ let i = [], a = null, o = null, s = (e) => {
529
+ o = null, a || (a = {
530
+ kind: "trace",
531
+ parts: []
532
+ }, i.push(a)), a.parts.push(e);
533
+ };
534
+ return e.forEach((e, t) => {
535
+ if (!(e.type === "step-start" || e.type === "step-finish")) if (e.type === "reasoning") {
536
+ let t = String(e.text ?? ""), n = e.state === "streaming";
537
+ if (!(t.trim() || n)) return;
538
+ s(e);
539
+ } else Z(e.type, n) ? s(e) : e.type === "text" && t <= r ? String(e.text ?? "").trim() && s(e) : e.type === "file" ? (a = null, o || (o = {
540
+ kind: "images",
541
+ parts: [],
542
+ index: t
543
+ }, i.push(o)), o.parts.push(e)) : (a = null, o = null, i.push({
544
+ kind: "part",
545
+ part: e,
546
+ index: t
547
+ }));
548
+ }), i;
549
+ }
550
+ function Ne(e, t) {
551
+ return e.some((e) => {
552
+ let n = e.state;
553
+ return typeof n == "string" ? e.type === "reasoning" || e.type === "text" ? n === "streaming" : n === "input-streaming" || n === "input-available" : t;
554
+ });
555
+ }
556
+ //#endregion
557
+ //#region src/chat/use-activity-clock.ts
558
+ function Pe(e, t) {
559
+ let n = ee(null), [r, i] = b(() => ({
560
+ seed: t,
561
+ elapsedSeconds: 0,
562
+ nowMs: Date.now()
563
+ }));
564
+ return y(() => {
565
+ if (!e) {
566
+ if (n.current === null) return;
567
+ let e = n.current;
568
+ n.current = null;
569
+ let t = setTimeout(() => {
570
+ i((t) => ({
571
+ ...t,
572
+ elapsedSeconds: Math.max(1, Math.round((Date.now() - e) / 1e3)),
573
+ nowMs: Date.now()
574
+ }));
575
+ }, 0);
576
+ return () => clearTimeout(t);
577
+ }
578
+ let r;
579
+ n.current === null && (n.current = Date.now(), r = setTimeout(() => {
580
+ i({
581
+ seed: t,
582
+ elapsedSeconds: 0,
583
+ nowMs: Date.now()
584
+ });
585
+ }, 0));
586
+ let a = setInterval(() => {
587
+ n.current !== null && i((e) => ({
588
+ ...e,
589
+ elapsedSeconds: Math.floor((Date.now() - n.current) / 1e3),
590
+ nowMs: Date.now()
591
+ }));
592
+ }, 1e3);
593
+ return () => {
594
+ r && clearTimeout(r), clearInterval(a);
595
+ };
596
+ }, [e, t]), r;
597
+ }
598
+ //#endregion
599
+ //#region src/chat/sources.tsx
600
+ function Fe(e) {
601
+ return `Used ${e} source${e === 1 ? "" : "s"}`;
602
+ }
603
+ function Q(e) {
604
+ try {
605
+ return new URL(e).hostname;
606
+ } catch {
607
+ return e;
608
+ }
609
+ }
610
+ function Ie(e) {
611
+ return `https://www.google.com/s2/favicons?domain=${Q(e)}&sz=32`;
612
+ }
613
+ function Le({ url: e, getFaviconUrl: t }) {
614
+ let n = t(e), [r, i] = b(!1);
615
+ return !n || r ? /* @__PURE__ */ a(d, { className: "size-3.5 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ a("img", {
616
+ src: n,
617
+ alt: "",
618
+ className: "size-3.5 shrink-0 rounded-xs",
619
+ onError: () => i(!0)
620
+ });
621
+ }
622
+ var Re = ({ sources: e, label: t = Fe, getFaviconUrl: n = Ie, className: r, ...i }) => e.length === 0 ? null : /* @__PURE__ */ o(w, {
623
+ className: C("not-prose", r),
624
+ ...i,
625
+ children: [/* @__PURE__ */ o(T, {
626
+ className: "group flex items-center gap-1 rounded-md px-1.5 py-0.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
627
+ children: [/* @__PURE__ */ a("span", { children: t(e.length) }), /* @__PURE__ */ a(c, { className: "size-2.5 transition-transform group-data-[state=open]:rotate-180" })]
628
+ }), /* @__PURE__ */ a(E, {
629
+ className: "mt-1 flex flex-col gap-0.5 data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 data-[state=closed]:animate-out data-[state=open]:animate-in",
630
+ children: e.map((e, t) => {
631
+ let r = Q(e.url), i = e.title || r;
632
+ return /* @__PURE__ */ o("a", {
633
+ href: e.url,
634
+ target: "_blank",
635
+ rel: "noreferrer",
636
+ className: "flex min-w-0 items-center gap-1.5 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
637
+ children: [
638
+ /* @__PURE__ */ a(Le, {
639
+ getFaviconUrl: n,
640
+ url: e.url
641
+ }),
642
+ /* @__PURE__ */ a("span", {
643
+ className: "truncate",
644
+ children: i
645
+ }),
646
+ i !== r && /* @__PURE__ */ a("span", {
647
+ className: "shrink-0 truncate text-xs text-muted-foreground/70",
648
+ children: r
649
+ })
650
+ ]
651
+ }, `${e.url}-${t}`);
652
+ })
653
+ })]
654
+ }), ze = "ocui-cite";
655
+ //#endregion
656
+ //#region src/chat/web-search.ts
657
+ function $(e) {
658
+ return typeof e == "object" && !!e && "error" in e && typeof e.error == "string";
659
+ }
660
+ var Be = "tool-webSearch";
661
+ function Ve(e) {
662
+ try {
663
+ let t = new URL(e);
664
+ return `${t.hostname.toLowerCase()}${t.pathname.replace(/\/$/, "")}${t.search}`;
665
+ } catch {
666
+ return e.replace(/#.*$/, "").replace(/\/$/, "");
667
+ }
668
+ }
669
+ function He(e, t) {
670
+ let n = t?.toolType ?? Be, r = /* @__PURE__ */ new Set(), i = [];
671
+ for (let t of e) {
672
+ if (t.type !== n || t.state !== "output-available" || $(t.output)) continue;
673
+ let e = t.output?.results;
674
+ if (Array.isArray(e)) for (let t of e) {
675
+ if (typeof t?.url != "string") continue;
676
+ let e = Ve(t.url);
677
+ r.has(e) || (r.add(e), i.push({
678
+ url: t.url,
679
+ title: t.title
680
+ }));
681
+ }
682
+ }
683
+ return i;
684
+ }
685
+ //#endregion
686
+ //#region src/chat/web-search-toggle.tsx
687
+ var Ue = ({ pressed: e, onPressedChange: t, label: n = "Search the web", tooltip: r, className: i, variant: s, size: c = "icon", onClick: l, ...u }) => {
688
+ let f = s ?? (e ? "default" : "ghost"), p = /* @__PURE__ */ a(V, {
689
+ "aria-label": n,
690
+ "aria-pressed": e,
691
+ className: C(i),
692
+ onClick: (n) => {
693
+ l?.(n), t(!e);
694
+ },
695
+ size: c,
696
+ type: "button",
697
+ variant: f,
698
+ ...u,
699
+ children: /* @__PURE__ */ a(d, {})
700
+ });
701
+ return r ? /* @__PURE__ */ a(H, {
702
+ delayDuration: 300,
703
+ children: /* @__PURE__ */ o(U, { children: [/* @__PURE__ */ a(W, {
704
+ asChild: !0,
705
+ children: p
706
+ }), /* @__PURE__ */ a(G, { children: /* @__PURE__ */ a("p", { children: r }) })] })
707
+ }) : p;
284
708
  };
285
709
  //#endregion
286
- export { ee as Action, $ as Actions, E as MessageContent, P as Reasoning, R as ReasoningContent, I as ReasoningTrigger, w as Response, z as Tool, W as ToolContent, U as ToolHeader, G as ToolInput, K as ToolOutput, L as reasoningTextClassName };
710
+ export { Ee as Action, Te as Actions, oe as MessageContent, ke as MessageTrace, O as Reasoning, j as ReasoningContent, k as ReasoningTrigger, ie as Response, Re as Sources, ge as Tool, xe as ToolContent, be as ToolHeader, Se as ToolInput, Ce as ToolOutput, Ue as WebSearchToggle, Me as buildSegments, ze as citationClassName, He as extractWebSearchSources, q as getAiActivityLabel, X as humanizeToolName, $ as isWebSearchError, Y as labelSeedFromString, A as reasoningTextClassName, J as traceElapsedSeconds, Ne as traceIsLoading, Pe as useActivityClock };
package/dist/tokens.css CHANGED
@@ -73,7 +73,7 @@
73
73
 
74
74
  --radius: 0.625rem;
75
75
 
76
- --card: var(--background);
76
+ --card: color-mix(in oklab, var(--foreground) 4%, var(--background));
77
77
  --card-foreground: var(--foreground);
78
78
  --popover: var(--background);
79
79
  --popover-foreground: var(--foreground);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ocai/app-sdk-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared design tokens and AI-app UI primitives for OceanAI products.",
5
5
  "license": "MIT",
6
6
  "type": "module",