@ocai/app-sdk-ui 0.1.0 → 0.3.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,22 @@
1
+ export type AiActivityKind = "waiting" | "reasoning" | "tools";
2
+ export interface AiActivity {
3
+ seed: number;
4
+ elapsedSeconds: number;
5
+ nowMs: number;
6
+ }
7
+ export interface ActivityLabelParts {
8
+ verb: string;
9
+ elapsedSeconds: number;
10
+ }
11
+ export interface GetAiActivityLabelArgs extends Pick<AiActivity, "seed" | "elapsedSeconds"> {
12
+ kind: AiActivityKind;
13
+ active: boolean;
14
+ verb?: string;
15
+ }
16
+ export declare function getAiActivityLabelParts({ kind, active, seed, elapsedSeconds, verb, }: GetAiActivityLabelArgs): ActivityLabelParts;
17
+ export declare function getAiActivityLabel(args: GetAiActivityLabelArgs): string;
18
+ export declare function traceElapsedSeconds(parts: ReadonlyArray<{
19
+ startedAtMs?: number;
20
+ completedAtMs?: number;
21
+ }>, nowMs?: number): number;
22
+ export declare function labelSeedFromString(value: string): number;
@@ -0,0 +1,17 @@
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
+ getActivityOverride?: (runningToolType: string | undefined) => string | undefined;
14
+ defaultOpen?: boolean;
15
+ className?: string;
16
+ };
17
+ export declare const MessageTrace: import('react').MemoExoticComponent<({ parts, isLoading, messageId, activity, getToolLabel, getToolIcon, getActivityOverride, defaultOpen, className, }: MessageTraceProps) => ReactElement>;
@@ -1,5 +1,4 @@
1
1
  import { ComponentProps } from 'react';
2
2
  import { Streamdown } from 'streamdown';
3
- type ResponseProps = ComponentProps<typeof Streamdown>;
3
+ export type ResponseProps = ComponentProps<typeof Streamdown>;
4
4
  export declare function Response({ className, ...props }: ResponseProps): import("react").JSX.Element;
5
- export {};
@@ -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
+ getLabel?: (count: number) => string;
10
+ getFaviconUrl?: (url: string) => string | null;
11
+ };
12
+ export declare const Sources: ({ sources, getLabel, getFaviconUrl, className, ...props }: SourcesProps) => ReactElement | null;
13
+ export declare const citationClassName = "ocui-cite";
@@ -0,0 +1,6 @@
1
+ import { ReactElement } from 'react';
2
+ export declare const hasRenderableValue: (v: unknown) => boolean;
3
+ export declare const capText: (value: string, max?: number) => string;
4
+ export declare const ToolValue: ({ value }: {
5
+ value: unknown;
6
+ }) => 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,21 @@
1
+ import { MessageSegment, TracePart } from './segments';
2
+ import { SourceItem } from './sources';
3
+ import { AiActivityKind } from './activity-labels';
4
+ export type TurnPhase = "waiting" | "reasoning" | "searching" | "tooling" | "answering" | "settled";
5
+ export interface TurnView {
6
+ phase: TurnPhase;
7
+ segments: MessageSegment[];
8
+ trace: {
9
+ parts: TracePart[];
10
+ isLoading: boolean;
11
+ } | null;
12
+ sources: SourceItem[];
13
+ activityKind: AiActivityKind;
14
+ hasFinalAnswer: boolean;
15
+ }
16
+ export interface DeriveTurnViewOptions {
17
+ isPresentationalTool?: (type: string) => boolean;
18
+ isSearchTool?: (type: string) => boolean;
19
+ messageLoading?: boolean;
20
+ }
21
+ export declare function deriveTurnView(parts: readonly TracePart[], options?: DeriveTurnViewOptions): TurnView;
@@ -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,35 @@
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 stripCitationParens(text: string): string;
29
+ export declare function extractWebSearchSources(parts: ReadonlyArray<{
30
+ type?: string;
31
+ state?: string;
32
+ output?: unknown;
33
+ }>, options?: {
34
+ toolType?: string;
35
+ }): SourceItem[];
package/dist/chat.css CHANGED
@@ -60,16 +60,119 @@
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
+
119
+ /* Activity-label shimmer: while the model streams, the trigger label
120
+ ("Thinking…", "Shaping for 4s") sweeps a full-contrast highlight
121
+ left→right across the muted label text, instead of pulsing the whole word.
122
+ Codex-style (a moving highlight band clipped to the glyphs). Base is the
123
+ muted label color; the highlight is the foreground seed. Applied by
124
+ Reasoning/MessageTrace's trigger only while streaming; reduced-motion
125
+ falls back to solid muted text. */
126
+ .ocui-shimmer {
127
+ /* Two glyph-clipped layers. LAYER 1 (background-color) is a SOLID base that
128
+ always fills every glyph, so text is never transparent. LAYER 2 (gradient,
129
+ painted on top) is transparent at both ends with an opaque --foreground band
130
+ in the middle — only that band tints the glyphs; elsewhere the base shows. */
131
+ background-color: var(--muted-foreground);
132
+ background-image: linear-gradient(
133
+ to right,
134
+ transparent 0%,
135
+ var(--foreground) 40%,
136
+ var(--foreground) 60%,
137
+ transparent 100%
138
+ );
139
+ background-size: 50% 100%;
140
+ background-repeat: no-repeat;
141
+ background-position: -100% 0;
142
+ -webkit-background-clip: text;
143
+ background-clip: text;
144
+ -webkit-text-fill-color: transparent;
145
+ color: transparent;
146
+ display: inline-block;
147
+ animation: ocui-shimmer-sweep 1.6s linear infinite;
148
+ }
149
+
150
+ /* --- Tool call params/results (chat/tool-value.tsx). Structured shallow
151
+ objects render as aligned key–value rows; deep/large values fall back to
152
+ this single muted mono block — a refined replacement for the old heavy
153
+ bg-muted/50 JSON.stringify dump. --- */
154
+ .ocui-tool-json {
155
+ tab-size: 2;
156
+ line-height: 1.5;
157
+ max-height: 22rem;
158
+ overflow: auto;
159
+ }
160
+ /* Key column top-aligns with the first line of a wrapping value. */
161
+ .ocui-tool-kv > li > span:first-child {
162
+ align-self: start;
163
+ }
164
+ }
165
+
166
+ @keyframes ocui-shimmer-sweep {
167
+ from { background-position: -100% 0; }
168
+ to { background-position: 250% 0; }
169
+ }
170
+
171
+ @media (prefers-reduced-motion: reduce) {
172
+ .ocui-shimmer {
173
+ animation: none;
174
+ background-image: none;
175
+ -webkit-text-fill-color: var(--muted-foreground);
176
+ color: var(--muted-foreground);
74
177
  }
75
178
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,13 @@
1
- export { Response } from './chat/response';
1
+ export { Response, type ResponseProps } from './chat/response';
2
2
  export { MessageContent, type MessageContentProps, type MessageContentVariant, } from './chat/message';
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 { deriveTurnView, type TurnView, type TurnPhase, type DeriveTurnViewOptions, } from './chat/turn-view';
7
+ export { MessageTrace, humanizeToolName, type MessageTraceProps, } from './chat/message-trace';
8
+ export { buildSegments, traceIsLoading, type BuildSegmentsOptions, type MessageSegment, type TracePart, } from './chat/segments';
9
+ export { getAiActivityLabel, getAiActivityLabelParts, labelSeedFromString, traceElapsedSeconds, type AiActivity, type AiActivityKind, type ActivityLabelParts, type GetAiActivityLabelArgs, } from './chat/activity-labels';
10
+ export { useActivityClock } from './chat/use-activity-clock';
11
+ export { Sources, citationClassName, type SourceItem, type SourcesProps, } from './chat/sources';
12
+ export { extractWebSearchSources, isWebSearchError, stripCitationParens, type WebSearchErrorOutput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WebSearchSuccessOutput, type WebSearchUIPart, type WebSearchUITool, } from './chat/web-search';
13
+ export { WebSearchToggle, type WebSearchToggleProps, } from './chat/web-search-toggle';
package/dist/index.js CHANGED
@@ -4,95 +4,97 @@ 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, useId as ee, useRef as b, useState as x } from "react";
9
+ import { Collapsible as S, Slot as te, Tooltip as C } from "radix-ui";
10
+ import { cva as w } from "class-variance-authority";
11
11
  //#region src/lib/cn.ts
12
- function S(...e) {
12
+ function T(...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 E = e({ singleDollarTextMath: !0 });
18
+ function ne({ 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: T("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: E },
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 D = {
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
+ }, O = ({ children: e, className: t, variant: n = "user", ...r }) => /* @__PURE__ */ a("div", {
35
+ className: T("flex flex-col gap-2 overflow-hidden text-base font-normal leading-6", D[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 k({ ...e }) {
42
+ return /* @__PURE__ */ a(S.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 A({ ...e }) {
48
+ return /* @__PURE__ */ a(S.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 j({ ...e }) {
54
+ return /* @__PURE__ */ a(S.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 re({ prop: e, defaultProp: t, onChange: n }) {
62
+ let [r, i] = x(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 M = h(null), ie = () => {
68
+ let e = v(M);
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
+ }, ae = 500, N = _(({ className: e, isStreaming: t = !1, duration: n = 0, open: r, defaultOpen: i = !0, onOpenChange: o, children: s, ...c }) => {
72
+ let [l, u] = re({
70
73
  prop: r,
71
74
  defaultProp: i,
72
75
  onChange: o
73
- }), [d, f] = _(!1);
74
- return g(() => {
76
+ }), [d, f] = x(!1), p = b(u);
77
+ return p.current = u, y(() => {
75
78
  if (i && !t && l && !d) {
76
79
  let e = setTimeout(() => {
77
- u(!1), f(!0);
78
- }, N);
80
+ p.current(!1), f(!0);
81
+ }, ae);
79
82
  return () => clearTimeout(e);
80
83
  }
81
84
  }, [
82
85
  t,
83
86
  l,
84
87
  i,
85
- u,
86
88
  d
87
- ]), /* @__PURE__ */ a(j.Provider, {
89
+ ]), /* @__PURE__ */ a(M.Provider, {
88
90
  value: {
89
91
  isStreaming: t,
90
92
  isOpen: l,
91
93
  setIsOpen: u,
92
94
  duration: n
93
95
  },
94
- children: /* @__PURE__ */ a(D, {
95
- className: S("not-prose", e),
96
+ children: /* @__PURE__ */ a(k, {
97
+ className: T("not-prose", e),
96
98
  onOpenChange: u,
97
99
  open: l,
98
100
  ...c,
@@ -100,39 +102,106 @@ var j = p(null), M = () => {
100
102
  })
101
103
  });
102
104
  });
103
- function F({ variant: e, isStreaming: t, duration: n }) {
105
+ function oe({ variant: e, isStreaming: t, duration: n }) {
104
106
  let r = e === "tools";
105
107
  return t ? r ? "Working…" : "Thinking…" : n > 0 ? r ? `Worked for ${n}s` : `Thought for ${n}s` : r ? "Worked" : "Thought";
106
108
  }
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({
109
+ var P = _(({ className: e, children: t, variant: n = "reasoning", label: r, ...s }) => {
110
+ let { isStreaming: l, isOpen: u, duration: d } = ie(), f = r ?? oe({
109
111
  variant: n,
110
112
  isStreaming: l,
111
- duration: f
113
+ duration: d
112
114
  });
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),
115
+ return /* @__PURE__ */ a(A, {
116
+ className: T("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
117
  ...s,
116
118
  children: t ?? /* @__PURE__ */ o(i, { children: [
117
- n === "tools" && /* @__PURE__ */ a(d, { className: "size-3" }),
119
+ n === "tools" && /* @__PURE__ */ a(p, { className: "size-3" }),
118
120
  /* @__PURE__ */ a("span", {
119
- className: S(l && "animate-pulse"),
120
- children: p
121
+ className: T(l && "ocui-shimmer"),
122
+ children: f
121
123
  }),
122
- /* @__PURE__ */ a(c, { className: S("size-2.5 transition-transform", u ? "rotate-180" : "rotate-0") })
124
+ /* @__PURE__ */ a(c, { className: T("size-2.5 transition-transform", u ? "rotate-180" : "rotate-0") })
123
125
  ] })
124
126
  });
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),
127
+ }), F = "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", I = _(({ className: e, ...t }) => /* @__PURE__ */ a(j, {
128
+ className: T(F, "mt-1.5", e),
127
129
  ...t
128
130
  }));
129
- P.displayName = "Reasoning", I.displayName = "ReasoningTrigger", R.displayName = "ReasoningContent";
131
+ N.displayName = "Reasoning", P.displayName = "ReasoningTrigger", I.displayName = "ReasoningContent";
130
132
  //#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),
133
+ //#region src/chat/tool-value.tsx
134
+ var se = 8, L = 240, ce = 2e4, R = (e) => e === null || typeof e != "object" && typeof e != "function", z = (e) => {
135
+ if (typeof e != "object" || !e) return !1;
136
+ let t = Object.getPrototypeOf(e);
137
+ return t === Object.prototype || t === null;
138
+ }, B = (e) => Array.isArray(e) && e.every(R), le = (e) => {
139
+ if (!z(e)) return !1;
140
+ let t = Object.keys(e);
141
+ return t.length === 0 || t.length > se ? !1 : t.every((t) => R(e[t]) || B(e[t]));
142
+ }, V = (e) => g(e) ? !0 : e == null ? !1 : typeof e == "string" ? e.trim().length > 0 : R(e) ? !0 : Array.isArray(e) ? e.length > 0 : z(e) ? Object.keys(e).length > 0 : !0, H = (e, t = ce) => e.length > t ? `${e.slice(0, t)}\n… (truncated)` : e, ue = (e) => {
143
+ try {
144
+ let t = JSON.stringify(e, null, 2);
145
+ return t === void 0 ? String(e) : H(t);
146
+ } catch {
147
+ return String(e);
148
+ }
149
+ }, U = ({ value: e }) => {
150
+ if (e == null) return /* @__PURE__ */ a("span", {
151
+ className: "font-mono text-muted-foreground",
152
+ children: "null"
153
+ });
154
+ if (typeof e != "string") return /* @__PURE__ */ a("span", {
155
+ className: "font-mono text-foreground",
156
+ children: String(e)
157
+ });
158
+ if (e === "") return /* @__PURE__ */ a("span", {
159
+ className: "text-muted-foreground italic",
160
+ children: "empty"
161
+ });
162
+ let t = e.length > L;
163
+ return /* @__PURE__ */ a("span", {
164
+ className: "text-foreground",
165
+ title: t ? e : void 0,
166
+ children: t ? `${e.slice(0, L)}…` : e
167
+ });
168
+ }, W = ({ value: e }) => e.length === 0 ? /* @__PURE__ */ a("span", {
169
+ className: "text-muted-foreground italic",
170
+ children: "none"
171
+ }) : /* @__PURE__ */ a("span", {
172
+ className: "text-foreground",
173
+ children: e.map((e, t) => /* @__PURE__ */ o("span", { children: [t > 0 && /* @__PURE__ */ a("span", {
174
+ className: "px-1 text-muted-foreground/70",
175
+ children: "·"
176
+ }), /* @__PURE__ */ a(U, { value: e })] }, t))
177
+ }), de = ({ data: e }) => /* @__PURE__ */ a("ul", {
178
+ className: "ocui-tool-kv grid min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs",
179
+ children: Object.entries(e).map(([e, t]) => /* @__PURE__ */ o("li", {
180
+ className: "contents",
181
+ children: [/* @__PURE__ */ a("span", {
182
+ className: "break-words text-muted-foreground",
183
+ children: e
184
+ }), /* @__PURE__ */ a("span", {
185
+ className: "min-w-0 break-words whitespace-pre-wrap",
186
+ children: B(t) ? /* @__PURE__ */ a(W, { value: t }) : /* @__PURE__ */ a(U, { value: t })
187
+ })]
188
+ }, e))
189
+ }), fe = ({ value: e }) => /* @__PURE__ */ a("pre", {
190
+ className: "ocui-tool-json overflow-x-auto rounded-md bg-muted/40 px-2.5 py-2 font-mono text-xs text-muted-foreground",
191
+ children: ue(e)
192
+ }), G = ({ value: e }) => g(e) ? /* @__PURE__ */ a(i, { children: e }) : e == null ? null : typeof e == "string" ? e.trim() === "" ? null : /* @__PURE__ */ a("div", {
193
+ className: "whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-muted-foreground",
194
+ children: H(e)
195
+ }) : R(e) ? /* @__PURE__ */ a("div", {
196
+ className: "font-mono text-xs text-foreground",
197
+ children: String(e)
198
+ }) : le(e) ? /* @__PURE__ */ a(de, { data: e }) : B(e) ? /* @__PURE__ */ a("div", {
199
+ className: "text-xs",
200
+ children: /* @__PURE__ */ a(W, { value: e })
201
+ }) : /* @__PURE__ */ a(fe, { value: e }), pe = ({ className: e, ...t }) => /* @__PURE__ */ a(k, {
202
+ className: T("not-prose w-full rounded-md border border-border", e),
134
203
  ...t
135
- }), B = {
204
+ }), me = {
136
205
  "input-streaming": "Pending",
137
206
  "input-available": "Running",
138
207
  "approval-requested": "Pending",
@@ -140,54 +209,54 @@ var z = ({ className: e, ...t }) => /* @__PURE__ */ a(D, {
140
209
  "output-available": "Completed",
141
210
  "output-error": "Error",
142
211
  "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" }),
212
+ }, he = {
213
+ "input-streaming": /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin motion-reduce:animate-none" }),
214
+ "input-available": /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin motion-reduce:animate-none" }),
215
+ "approval-requested": /* @__PURE__ */ a(u, { className: "size-3.5" }),
147
216
  "approval-responded": /* @__PURE__ */ a(s, { className: "size-3.5" }),
148
217
  "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", {
218
+ "output-error": /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" }),
219
+ "output-denied": /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" })
220
+ }, ge = ({ state: e }) => /* @__PURE__ */ o("span", {
152
221
  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),
222
+ children: [he[e], me[e]]
223
+ }), _e = ({ className: e, type: t, state: n, title: r, ...i }) => /* @__PURE__ */ o(A, {
224
+ className: T("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
225
  ...i,
157
226
  children: [/* @__PURE__ */ o("div", {
158
227
  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", {
228
+ children: [/* @__PURE__ */ a(p, { className: "size-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ a("span", {
160
229
  className: "truncate text-sm font-medium",
161
230
  children: r ?? t
162
231
  })]
163
232
  }), /* @__PURE__ */ o("div", {
164
233
  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" })]
234
+ children: [/* @__PURE__ */ a(ge, { state: n }), /* @__PURE__ */ a(c, { className: "size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })]
166
235
  })]
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),
236
+ }), ve = ({ className: e, ...t }) => /* @__PURE__ */ a(j, {
237
+ className: T("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
238
  ...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),
239
+ }), ye = ({ className: e, input: t, ...n }) => V(t) ? /* @__PURE__ */ o("div", {
240
+ className: T("space-y-1.5 overflow-hidden px-2.5 pb-2.5", e),
172
241
  ...n,
173
242
  children: [/* @__PURE__ */ a("h4", {
174
243
  className: "text-xs font-medium tracking-wide text-muted-foreground uppercase",
175
244
  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),
245
+ }), /* @__PURE__ */ a(G, { value: t })]
246
+ }) : null, be = ({ className: e, output: t, errorText: n, ...r }) => V(t) || n ? /* @__PURE__ */ o("div", {
247
+ className: T("space-y-1.5 px-2.5 pb-2.5", e),
182
248
  ...r,
183
249
  children: [/* @__PURE__ */ a("h4", {
184
250
  className: "text-xs font-medium tracking-wide text-muted-foreground uppercase",
185
251
  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 })]
252
+ }), n ? /* @__PURE__ */ a("div", {
253
+ className: "whitespace-pre-wrap break-words rounded-md bg-destructive/10 px-2.5 py-2 text-xs text-destructive",
254
+ children: n
255
+ }) : /* @__PURE__ */ a("div", {
256
+ className: "text-xs [&_table]:w-full",
257
+ children: /* @__PURE__ */ a(G, { value: t })
189
258
  })]
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", {
259
+ }) : null, xe = w("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
260
  variants: {
192
261
  variant: {
193
262
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
@@ -213,12 +282,12 @@ var z = ({ className: e, ...t }) => /* @__PURE__ */ a(D, {
213
282
  size: "default"
214
283
  }
215
284
  });
216
- function J({ className: e, variant: t = "default", size: n = "default", asChild: r = !1, ...i }) {
217
- return /* @__PURE__ */ a(r ? y.Root : "button", {
285
+ function K({ className: e, variant: t = "default", size: n = "default", asChild: r = !1, ...i }) {
286
+ return /* @__PURE__ */ a(r ? te.Root : "button", {
218
287
  "data-slot": "button",
219
288
  "data-variant": t,
220
289
  "data-size": n,
221
- className: S(q({
290
+ className: T(xe({
222
291
  variant: t,
223
292
  size: n,
224
293
  className: e
@@ -228,43 +297,43 @@ function J({ className: e, variant: t = "default", size: n = "default", asChild:
228
297
  }
229
298
  //#endregion
230
299
  //#region src/internal/tooltip.tsx
231
- function Y({ delayDuration: e = 0, ...t }) {
232
- return /* @__PURE__ */ a(b.Provider, {
300
+ function q({ delayDuration: e = 0, ...t }) {
301
+ return /* @__PURE__ */ a(C.Provider, {
233
302
  "data-slot": "tooltip-provider",
234
303
  delayDuration: e,
235
304
  ...t
236
305
  });
237
306
  }
238
- function X({ ...e }) {
239
- return /* @__PURE__ */ a(b.Root, {
307
+ function J({ ...e }) {
308
+ return /* @__PURE__ */ a(C.Root, {
240
309
  "data-slot": "tooltip",
241
310
  ...e
242
311
  });
243
312
  }
244
- function Z({ ...e }) {
245
- return /* @__PURE__ */ a(b.Trigger, {
313
+ function Y({ ...e }) {
314
+ return /* @__PURE__ */ a(C.Trigger, {
246
315
  "data-slot": "tooltip-trigger",
247
316
  ...e
248
317
  });
249
318
  }
250
- function Q({ className: e, sideOffset: t = 0, children: n, ...r }) {
251
- return /* @__PURE__ */ a(b.Portal, { children: /* @__PURE__ */ o(b.Content, {
319
+ function X({ className: e, sideOffset: t = 0, children: n, ...r }) {
320
+ return /* @__PURE__ */ a(C.Portal, { children: /* @__PURE__ */ o(C.Content, {
252
321
  "data-slot": "tooltip-content",
253
322
  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),
323
+ className: T("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
324
  ...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" })]
325
+ children: [n, /* @__PURE__ */ a(C.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
326
  }) });
258
327
  }
259
328
  //#endregion
260
329
  //#region src/chat/actions.tsx
261
- var $ = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
262
- className: S("flex items-center", e),
330
+ var Se = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
331
+ className: T("-ml-1.5 flex items-center", e),
263
332
  ...n,
264
333
  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),
334
+ }), Ce = ({ tooltip: e, children: t, label: n, className: r, variant: i = "ghost", size: s = "icon", ...c }) => {
335
+ let l = /* @__PURE__ */ o(K, {
336
+ className: T("relative", r),
268
337
  size: s,
269
338
  type: "button",
270
339
  variant: i,
@@ -274,13 +343,416 @@ var $ = ({ className: e, children: t, ...n }) => /* @__PURE__ */ a("div", {
274
343
  children: n || e
275
344
  })]
276
345
  });
277
- return e ? /* @__PURE__ */ a(Y, {
278
- delayDuration: 300,
279
- children: /* @__PURE__ */ o(X, { children: [/* @__PURE__ */ a(Z, {
346
+ return e ? /* @__PURE__ */ a(q, {
347
+ delayDuration: 500,
348
+ children: /* @__PURE__ */ o(J, { children: [/* @__PURE__ */ a(Y, {
280
349
  asChild: !0,
281
350
  children: l
282
- }), /* @__PURE__ */ a(Q, { children: /* @__PURE__ */ a("p", { children: e }) })] })
351
+ }), /* @__PURE__ */ a(X, { children: /* @__PURE__ */ a("p", { children: e }) })] })
283
352
  }) : l;
284
353
  };
285
354
  //#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 };
355
+ //#region src/chat/segments.ts
356
+ function we(e, t) {
357
+ return e.startsWith("tool-") && !t(e);
358
+ }
359
+ function Te(e, t) {
360
+ let n = t?.isPresentationalTool ?? (() => !1), r = -1;
361
+ e.forEach((e, t) => {
362
+ (e.type === "reasoning" || we(e.type, n)) && (r = t);
363
+ });
364
+ let i = [], a = null, o = null, s = (e) => {
365
+ o = null, a || (a = {
366
+ kind: "trace",
367
+ parts: []
368
+ }, i.push(a)), a.parts.push(e);
369
+ };
370
+ return e.forEach((e, t) => {
371
+ if (!(e.type === "step-start" || e.type === "step-finish")) if (e.type === "reasoning") {
372
+ let t = String(e.text ?? ""), n = e.state === "streaming";
373
+ if (!(t.trim() || n)) return;
374
+ s(e);
375
+ } else we(e.type, n) ? s(e) : e.type === "text" && t <= r ? String(e.text ?? "").trim() && s(e) : e.type === "file" ? (a = null, o || (o = {
376
+ kind: "images",
377
+ parts: [],
378
+ index: t
379
+ }, i.push(o)), o.parts.push(e)) : (a = null, o = null, i.push({
380
+ kind: "part",
381
+ part: e,
382
+ index: t
383
+ }));
384
+ }), i;
385
+ }
386
+ function Ee(e, t) {
387
+ return e.some((e) => {
388
+ let n = e.state;
389
+ return typeof n == "string" ? e.type === "reasoning" || e.type === "text" ? n === "streaming" : n === "input-streaming" || n === "input-available" : t;
390
+ });
391
+ }
392
+ //#endregion
393
+ //#region src/chat/web-search.ts
394
+ function De(e) {
395
+ return typeof e == "object" && !!e && "error" in e && typeof e.error == "string";
396
+ }
397
+ var Oe = /\(\s*(\[[^\]\n]*\]\((?:[^()\n]|\([^()\n]*\))*\))\s*\)/g;
398
+ function ke(e) {
399
+ return e.replace(Oe, "$1");
400
+ }
401
+ var Ae = "tool-webSearch";
402
+ function je(e) {
403
+ try {
404
+ let t = new URL(e);
405
+ return `${t.hostname.toLowerCase()}${t.pathname.replace(/\/$/, "")}${t.search}`;
406
+ } catch {
407
+ return e.replace(/#.*$/, "").replace(/\/$/, "");
408
+ }
409
+ }
410
+ function Me(e, t) {
411
+ let n = t?.toolType ?? Ae, r = /* @__PURE__ */ new Set(), i = [];
412
+ for (let t of e) {
413
+ if (t.type !== n || t.state !== "output-available" || De(t.output)) continue;
414
+ let e = t.output?.results;
415
+ if (Array.isArray(e)) for (let t of e) {
416
+ if (typeof t?.url != "string") continue;
417
+ let e = je(t.url);
418
+ r.has(e) || (r.add(e), i.push({
419
+ url: t.url,
420
+ title: typeof t.title == "string" ? t.title : void 0
421
+ }));
422
+ }
423
+ }
424
+ return i;
425
+ }
426
+ //#endregion
427
+ //#region src/chat/turn-view.ts
428
+ function Ne(e, t) {
429
+ let n = t?.isSearchTool ?? (() => !1), r = t?.messageLoading, i = Te(e, { isPresentationalTool: t?.isPresentationalTool }), a = i.flatMap((e) => e.kind === "trace" ? e.parts : []), o = i.filter((e) => e.kind === "part" && e.part.type === "text"), s = o.some((e) => String(e.part.text ?? "").trim() !== ""), c = s && o.some((e) => e.part.state === "streaming"), l = a.some((e) => e.type === "reasoning" && String(e.text ?? "").trim() !== ""), u = a.some((e) => e.type.startsWith("tool-")), d = i.length > 0, f = a.some((e) => e.type === "reasoning" && e.state === "streaming"), p;
430
+ for (let e of a) {
431
+ if (!e.type.startsWith("tool-")) continue;
432
+ let t = String(e.state ?? "");
433
+ t === "output-available" || t === "output-error" || t === "output-denied" || (p = e.type);
434
+ }
435
+ let m = a.length > 0 && Ee(a, r ?? !1), h = (r ?? !0) && !s, g = a.length > 0 ? {
436
+ parts: a,
437
+ isLoading: h
438
+ } : null, _;
439
+ _ = d ? h ? m && f ? "reasoning" : m && p && n(p) ? "searching" : m && p ? "tooling" : l ? "reasoning" : u ? "tooling" : "reasoning" : c ? "answering" : "settled" : h ? "waiting" : "settled";
440
+ let v = d ? l ? "reasoning" : "tools" : "waiting";
441
+ return {
442
+ phase: _,
443
+ segments: i,
444
+ trace: g,
445
+ sources: Me(e),
446
+ activityKind: v,
447
+ hasFinalAnswer: s
448
+ };
449
+ }
450
+ //#endregion
451
+ //#region src/chat/activity-labels.ts
452
+ var Pe = [
453
+ {
454
+ active: "Thinking",
455
+ done: "Thought"
456
+ },
457
+ {
458
+ active: "Reading",
459
+ done: "Read"
460
+ },
461
+ {
462
+ active: "Drafting",
463
+ done: "Drafted"
464
+ },
465
+ {
466
+ active: "Shaping",
467
+ done: "Shaped"
468
+ },
469
+ {
470
+ active: "Composing",
471
+ done: "Composed"
472
+ },
473
+ {
474
+ active: "Polishing",
475
+ done: "Polished"
476
+ },
477
+ {
478
+ active: "Mapping",
479
+ done: "Mapped"
480
+ },
481
+ {
482
+ active: "Sorting",
483
+ done: "Sorted"
484
+ },
485
+ {
486
+ active: "Refining",
487
+ done: "Refined"
488
+ },
489
+ {
490
+ active: "Working",
491
+ done: "Done"
492
+ }
493
+ ], Fe = {
494
+ waiting: Pe,
495
+ reasoning: Pe,
496
+ tools: [{
497
+ active: "Working",
498
+ done: "Worked"
499
+ }]
500
+ };
501
+ function Z({ kind: e, active: t, seed: n, elapsedSeconds: r, verb: i }) {
502
+ let a;
503
+ if (i !== void 0) a = i;
504
+ else {
505
+ let r = Fe[e], i = r[Math.abs(n) % r.length];
506
+ a = t ? i.active : i.done;
507
+ }
508
+ return {
509
+ verb: a,
510
+ elapsedSeconds: r > 0 ? r : 0
511
+ };
512
+ }
513
+ function Q(e) {
514
+ let { verb: t, elapsedSeconds: n } = Z(e);
515
+ return n <= 0 ? t : `${t} for ${n}s`;
516
+ }
517
+ function Ie(e, t) {
518
+ let n = Infinity, r = -Infinity, i = !1;
519
+ for (let t of e) Number.isFinite(t.startedAtMs) && (n = Math.min(n, t.startedAtMs), Number.isFinite(t.completedAtMs) ? r = Math.max(r, t.completedAtMs) : i = !0);
520
+ if (n === Infinity) return 0;
521
+ let a = i && Number.isFinite(t) ? t : r;
522
+ return a === -Infinity ? 0 : Math.max(0, Math.floor((a - n) / 1e3));
523
+ }
524
+ function Le(e) {
525
+ let t = 0;
526
+ for (let n = 0; n < e.length; n += 1) t = t * 31 + e.charCodeAt(n) | 0;
527
+ return t;
528
+ }
529
+ //#endregion
530
+ //#region src/chat/message-trace.tsx
531
+ function Re(e) {
532
+ let t = e.replace(/^tool-/, "").replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
533
+ return t.charAt(0).toUpperCase() + t.slice(1);
534
+ }
535
+ var ze = _(({ parts: e, isLoading: t, messageId: n, activity: r, getToolLabel: i, getToolIcon: s, getActivityOverride: c, defaultOpen: l = !1, className: u }) => {
536
+ let d = e.filter((e) => e.type === "reasoning").map((e) => String(e.text ?? "")).filter((e) => e.trim()).join("\n\n"), f = d.trim().length > 0, p = [], m = !1, h;
537
+ for (let t of e) if (t.type.startsWith("tool-")) {
538
+ m = !0;
539
+ let e = String(t.state ?? "");
540
+ e === "output-available" || e === "output-error" || e === "output-denied" || (h = t.type), p.push({
541
+ kind: "tool",
542
+ label: i?.(t.type) ?? Re(t.type),
543
+ state: e,
544
+ toolType: t.type
545
+ });
546
+ } else t.type === "text" && String(t.text ?? "").trim() && p.push({
547
+ kind: "note",
548
+ text: String(t.text)
549
+ });
550
+ m && !t && p.push({ kind: "done" });
551
+ let g = e.some((e) => Number.isFinite(e.startedAtMs)) ? Ie(e, r?.nowMs) : r?.elapsedSeconds ?? 0, _ = r?.seed ?? Le(n), v = t ? g : 0, y = t ? c?.(h) : void 0, b = {
552
+ kind: f ? "reasoning" : "tools",
553
+ active: t,
554
+ seed: _,
555
+ elapsedSeconds: v,
556
+ verb: y
557
+ }, S = Q(b), { elapsedSeconds: te } = Z(b), C = f || p.length > 0, w = C && t && te === 0, [E, D] = x(l), O = ee();
558
+ return /* @__PURE__ */ o("div", {
559
+ className: T("not-prose flex flex-col", u),
560
+ children: [/* @__PURE__ */ a("button", {
561
+ type: "button",
562
+ "data-testid": "message-reasoning",
563
+ onClick: () => D((e) => !e),
564
+ "aria-expanded": E,
565
+ "aria-controls": C ? O : void 0,
566
+ className: "flex items-center gap-1 self-start text-sm text-muted-foreground transition-colors hover:text-foreground",
567
+ children: /* @__PURE__ */ o("span", {
568
+ className: T(t && "ocui-shimmer"),
569
+ children: [S, w ? "..." : ""]
570
+ })
571
+ }), E && C && /* @__PURE__ */ o("div", {
572
+ id: O,
573
+ className: "mt-2 flex flex-col gap-2",
574
+ children: [f && /* @__PURE__ */ a(ne, {
575
+ 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",
576
+ children: d
577
+ }), p.length > 0 && /* @__PURE__ */ a("div", {
578
+ className: "flex flex-col",
579
+ children: p.map((e, t) => /* @__PURE__ */ a(Be, {
580
+ node: e,
581
+ isFirst: t === 0,
582
+ isLast: t === p.length - 1,
583
+ getToolIcon: s
584
+ }, `step-${t}`))
585
+ })]
586
+ })]
587
+ });
588
+ });
589
+ ze.displayName = "MessageTrace";
590
+ function Be({ node: e, isFirst: t, isLast: n, getToolIcon: r }) {
591
+ return /* @__PURE__ */ o("div", {
592
+ className: "flex items-stretch gap-2.5",
593
+ children: [/* @__PURE__ */ o("div", {
594
+ className: "flex w-4 flex-col items-center",
595
+ children: [
596
+ /* @__PURE__ */ a("div", { className: T("w-px flex-1", t ? "bg-transparent" : "bg-border") }),
597
+ /* @__PURE__ */ a(Ve, {
598
+ node: e,
599
+ getToolIcon: r
600
+ }),
601
+ /* @__PURE__ */ a("div", { className: T("w-px flex-1", n ? "bg-transparent" : "bg-border") })
602
+ ]
603
+ }), /* @__PURE__ */ a("div", {
604
+ className: "min-w-0 py-1 text-sm leading-5",
605
+ children: e.kind === "tool" ? /* @__PURE__ */ a("span", {
606
+ className: "text-muted-foreground",
607
+ children: e.label
608
+ }) : e.kind === "note" ? /* @__PURE__ */ a("span", {
609
+ className: "text-muted-foreground/80",
610
+ children: e.text
611
+ }) : /* @__PURE__ */ a("span", {
612
+ className: "text-muted-foreground",
613
+ children: "Done"
614
+ })
615
+ })]
616
+ });
617
+ }
618
+ function Ve({ node: e, getToolIcon: t }) {
619
+ let n = "flex items-center justify-center bg-background py-0.5";
620
+ return e.kind === "note" ? /* @__PURE__ */ a("div", {
621
+ className: n,
622
+ children: /* @__PURE__ */ a("div", { className: "size-1 rounded-full bg-muted-foreground/60" })
623
+ }) : e.kind === "tool" ? e.state === "output-error" || e.state === "output-denied" ? /* @__PURE__ */ a("div", {
624
+ className: n,
625
+ children: /* @__PURE__ */ a(m, { className: "size-3.5 text-destructive" })
626
+ }) : e.state === "output-available" ? /* @__PURE__ */ a("div", {
627
+ className: n,
628
+ children: /* @__PURE__ */ a(t?.(e.toolType) ?? s, { className: "size-3.5 text-muted-foreground" })
629
+ }) : /* @__PURE__ */ a("div", {
630
+ className: n,
631
+ children: /* @__PURE__ */ a(f, { className: "size-3.5 animate-spin text-muted-foreground motion-reduce:animate-none" })
632
+ }) : /* @__PURE__ */ a("div", {
633
+ className: n,
634
+ children: /* @__PURE__ */ a(l, { className: "size-4 text-muted-foreground" })
635
+ });
636
+ }
637
+ //#endregion
638
+ //#region src/chat/use-activity-clock.ts
639
+ function He(e, t) {
640
+ let n = b(null), [r, i] = x(() => ({
641
+ seed: t,
642
+ elapsedSeconds: 0,
643
+ nowMs: Date.now()
644
+ }));
645
+ return y(() => {
646
+ if (!e) {
647
+ if (n.current === null) return;
648
+ let e = n.current;
649
+ n.current = null;
650
+ let t = setTimeout(() => {
651
+ i((t) => ({
652
+ ...t,
653
+ elapsedSeconds: Math.max(1, Math.round((Date.now() - e) / 1e3)),
654
+ nowMs: Date.now()
655
+ }));
656
+ }, 0);
657
+ return () => clearTimeout(t);
658
+ }
659
+ let r;
660
+ n.current === null && (n.current = Date.now(), r = setTimeout(() => {
661
+ i({
662
+ seed: t,
663
+ elapsedSeconds: 0,
664
+ nowMs: Date.now()
665
+ });
666
+ }, 0));
667
+ let a = setInterval(() => {
668
+ n.current !== null && i((e) => ({
669
+ ...e,
670
+ elapsedSeconds: Math.floor((Date.now() - n.current) / 1e3),
671
+ nowMs: Date.now()
672
+ }));
673
+ }, 1e3);
674
+ return () => {
675
+ r && clearTimeout(r), clearInterval(a);
676
+ };
677
+ }, [e, t]), r;
678
+ }
679
+ //#endregion
680
+ //#region src/chat/sources.tsx
681
+ function Ue(e) {
682
+ return `Searched ${e} source${e === 1 ? "" : "s"}`;
683
+ }
684
+ function $(e) {
685
+ try {
686
+ return new URL(e).hostname;
687
+ } catch {
688
+ return e;
689
+ }
690
+ }
691
+ function We(e) {
692
+ return `https://www.google.com/s2/favicons?domain=${$(e)}&sz=32`;
693
+ }
694
+ function Ge({ url: e, getFaviconUrl: t }) {
695
+ let n = t(e), [r, i] = x(!1);
696
+ return !n || r ? /* @__PURE__ */ a(d, { className: "size-3.5 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ a("img", {
697
+ src: n,
698
+ alt: "",
699
+ className: "size-3.5 shrink-0 rounded-xs",
700
+ onError: () => i(!0)
701
+ });
702
+ }
703
+ var Ke = ({ sources: e, getLabel: t = Ue, getFaviconUrl: n = We, className: r, ...i }) => e.length === 0 ? null : /* @__PURE__ */ o(k, {
704
+ className: T("not-prose", r),
705
+ ...i,
706
+ children: [/* @__PURE__ */ o(A, {
707
+ className: "group flex items-center gap-1 self-start text-sm text-muted-foreground transition-colors hover:text-foreground",
708
+ children: [/* @__PURE__ */ a("span", { children: t(e.length) }), /* @__PURE__ */ a(c, { className: "size-2.5 transition-transform group-data-[state=open]:rotate-180" })]
709
+ }), /* @__PURE__ */ a(j, {
710
+ 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",
711
+ children: e.map((e, t) => {
712
+ let r = $(e.url), i = e.title || r;
713
+ return /* @__PURE__ */ o("a", {
714
+ href: e.url,
715
+ target: "_blank",
716
+ rel: "noreferrer",
717
+ 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",
718
+ children: [
719
+ /* @__PURE__ */ a(Ge, {
720
+ getFaviconUrl: n,
721
+ url: e.url
722
+ }),
723
+ /* @__PURE__ */ a("span", {
724
+ className: "truncate",
725
+ children: i
726
+ }),
727
+ i !== r && /* @__PURE__ */ a("span", {
728
+ className: "shrink-0 truncate text-xs text-muted-foreground/70",
729
+ children: r
730
+ })
731
+ ]
732
+ }, `${e.url}-${t}`);
733
+ })
734
+ })]
735
+ }), qe = "ocui-cite", Je = ({ pressed: e, onPressedChange: t, label: n = "Search the web", tooltip: r, className: i, variant: s, size: c = "icon", onClick: l, ...u }) => {
736
+ let f = s ?? (e ? "default" : "ghost"), p = /* @__PURE__ */ a(K, {
737
+ ...u,
738
+ "aria-label": n,
739
+ "aria-pressed": e,
740
+ className: T(i),
741
+ size: c,
742
+ type: "button",
743
+ variant: f,
744
+ onClick: (n) => {
745
+ l?.(n), t(!e);
746
+ },
747
+ children: /* @__PURE__ */ a(d, {})
748
+ });
749
+ return r ? /* @__PURE__ */ a(q, {
750
+ delayDuration: 300,
751
+ children: /* @__PURE__ */ o(J, { children: [/* @__PURE__ */ a(Y, {
752
+ asChild: !0,
753
+ children: p
754
+ }), /* @__PURE__ */ a(X, { children: /* @__PURE__ */ a("p", { children: r }) })] })
755
+ }) : p;
756
+ };
757
+ //#endregion
758
+ export { Ce as Action, Se as Actions, O as MessageContent, ze as MessageTrace, N as Reasoning, I as ReasoningContent, P as ReasoningTrigger, ne as Response, Ke as Sources, pe as Tool, ve as ToolContent, _e as ToolHeader, ye as ToolInput, be as ToolOutput, Je as WebSearchToggle, Te as buildSegments, qe as citationClassName, Ne as deriveTurnView, Me as extractWebSearchSources, Q as getAiActivityLabel, Z as getAiActivityLabelParts, Re as humanizeToolName, De as isWebSearchError, Le as labelSeedFromString, F as reasoningTextClassName, ke as stripCitationParens, Ie as traceElapsedSeconds, Ee as traceIsLoading, He 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.3.0",
4
4
  "description": "Shared design tokens and AI-app UI primitives for OceanAI products.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,21 +47,26 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@streamdown/math": "^1.0.2",
50
+ "@testing-library/dom": "^10.4.1",
51
+ "@testing-library/react": "^16.3.2",
50
52
  "@types/node": "^22.0.0",
51
53
  "@types/react": "^19.0.0",
52
54
  "@types/react-dom": "^19.0.0",
53
55
  "@vitejs/plugin-react": "^6.0.1",
54
56
  "ai": "^6.0.0",
57
+ "jsdom": "^26.1.0",
55
58
  "react": "^19.0.0",
56
59
  "react-dom": "^19.0.0",
57
60
  "streamdown": "^2.5.0",
58
61
  "typescript": "~5.6.0",
59
62
  "vite": "^8.0.16",
60
- "vite-plugin-dts": "^4.5.4"
63
+ "vite-plugin-dts": "^4.5.4",
64
+ "vitest": "^4.1.10"
61
65
  },
62
66
  "scripts": {
63
67
  "build": "vite build && cp src/tokens.css src/chat/chat.css dist/",
64
68
  "dev": "vite build --watch",
65
- "typecheck": "tsc --noEmit"
69
+ "typecheck": "tsc --noEmit",
70
+ "test": "vitest run"
66
71
  }
67
72
  }