@iloveagents/foundry-web-ui 0.9.0 → 0.10.1

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.
@@ -32,7 +32,7 @@ const AssistantMessage = () => (_jsx(MessagePrimitive.Root, { className: "w-full
32
32
  const AssistantActionBar = () => (_jsxs(ActionBarPrimitive.Root, { hideWhenRunning: true, className: cn("pointer-events-none flex items-center gap-1 -ml-2 opacity-0 transition-opacity", "group-hover:pointer-events-auto group-hover:opacity-100", "group-focus-within:pointer-events-auto group-focus-within:opacity-100"), children: [_jsx(ActionBarPrimitive.Copy, { asChild: true, children: _jsxs(TooltipIconButton, { tooltip: "Copy", size: "icon", children: [_jsx(MessagePrimitive.If, { copied: false, children: _jsx(Copy, { className: "size-4" }) }), _jsx(MessagePrimitive.If, { copied: true, children: _jsx(Check, { className: "size-4 text-primary" }) })] }) }), _jsx(ActionBarPrimitive.Reload, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Refresh", size: "icon", children: _jsx(RefreshCw, { className: "size-4" }) }) })] }));
33
33
  const EditComposer = () => (_jsx(MessagePrimitive.Root, { className: "w-full mb-2", children: _jsx("div", { className: "flex items-start justify-end", children: _jsx("div", { className: "w-full max-w-[80%]", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-2xl border border-input", "bg-background shadow-sm", "px-4 py-3"), children: [_jsx(ComposerPrimitive.Input, { autoFocus: true, className: cn("w-full bg-transparent", "text-base text-foreground", "outline-none resize-none", "placeholder:text-muted-foreground") }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }), _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "sm", children: "Update" }) })] })] }) }) }) }));
34
34
  const ComposerActionButton = () => {
35
- return (_jsxs(_Fragment, { children: [_jsx(ThreadPrimitive.If, { running: false, children: _jsx(ReasoningEffortPicker, { className: "mr-1" }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Stop generating", children: _jsx(Square, { className: "size-4" }) }) }) }), _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Send message", children: _jsx(ArrowUp, { className: "size-5" }) }) }) })] }));
35
+ return (_jsxs(_Fragment, { children: [_jsx(ThreadPrimitive.If, { running: false, children: _jsx(ReasoningEffortPicker, {}) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(ComposerPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Stop generating", children: _jsx(Square, { className: "size-4" }) }) }) }), _jsx(ThreadPrimitive.If, { running: false, children: _jsx(ComposerPrimitive.Send, { asChild: true, children: _jsx(Button, { size: "icon", className: "rounded-full size-10 flex-shrink-0", "aria-label": "Send message", children: _jsx(ArrowUp, { className: "size-5" }) }) }) })] }));
36
36
  };
37
37
  /**
38
38
  * Default starter prompts shown on empty state. Override per-app by passing
@@ -94,5 +94,5 @@ export function ChatContent({ starterSuggestions = DEFAULT_STARTER_SUGGESTIONS,
94
94
  UserMessage,
95
95
  EditComposer,
96
96
  AssistantMessage,
97
- } }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(ScrollToBottomButton, {}) })] }), _jsxs("div", { className: "shrink-0 bg-background", children: [_jsx(ChatComposerBannerSlot, {}), _jsx(ThreadPrimitive.If, { running: false, children: _jsx("div", { className: "border-t border-border" }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingBar, {}) }), _jsx("div", { className: "mx-auto max-w-3xl px-4 py-4", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-3xl border border-input", "bg-background shadow-sm", "px-4 py-2", "focus-within:border-primary/50 focus-within:shadow-md", "transition-all duration-300"), children: [_jsx(ComposerSubmitBridge, {}), _jsx(ComposerContextBadges, {}), _jsx(ComposerPrimitive.Attachments, { components: composerAttachmentComponents }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center -space-x-1 shrink-0", children: [_jsx(ComposerPrimitive.AddAttachment, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Attach file", variant: "ghost", className: "shrink-0", children: _jsx(Paperclip, { className: "size-5" }) }) }), _jsx(ContextPins, { showPinButton: showPinButton })] }), _jsx(ComposerPrimitive.Input, { autoFocus: true, placeholder: "Message Assistant...", rows: 1, className: cn("flex-1 bg-transparent", "py-2 text-base", "text-foreground outline-none", "placeholder:text-muted-foreground", "resize-none max-h-32") }), _jsx(ComposerActionButton, {})] })] }) })] })] })] }));
97
+ } }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingIndicator, {}) })] }) })] }), _jsx(ThreadPrimitive.If, { empty: false, children: _jsx(ScrollToBottomButton, {}) })] }), _jsxs("div", { className: "shrink-0 bg-background", children: [_jsx(ChatComposerBannerSlot, {}), _jsx(ThreadPrimitive.If, { running: false, children: _jsx("div", { className: "border-t border-border" }) }), _jsx(ThreadPrimitive.If, { running: true, children: _jsx(LoadingBar, {}) }), _jsx("div", { className: "mx-auto max-w-3xl px-4 py-4", children: _jsxs(ComposerPrimitive.Root, { className: cn("flex flex-col gap-2", "rounded-3xl border border-input", "bg-background shadow-sm", "px-4 py-2", "focus-within:border-primary/50 focus-within:shadow-md", "transition-all duration-300"), children: [_jsx(ComposerSubmitBridge, {}), _jsx(ComposerContextBadges, {}), _jsx(ComposerPrimitive.Attachments, { components: composerAttachmentComponents }), _jsx(ComposerPrimitive.Input, { autoFocus: true, placeholder: "Message Assistant...", rows: 1, className: cn("w-full bg-transparent", "pt-2 text-base", "text-foreground outline-none", "placeholder:text-muted-foreground", "resize-none max-h-32") }), _jsxs("div", { className: "flex items-center gap-2 pb-1", children: [_jsxs("div", { className: "flex items-center -space-x-1 shrink-0", children: [_jsx(ComposerPrimitive.AddAttachment, { asChild: true, children: _jsx(TooltipIconButton, { tooltip: "Attach file", variant: "ghost", className: "shrink-0", children: _jsx(Paperclip, { className: "size-5" }) }) }), _jsx(ContextPins, { showPinButton: showPinButton })] }), _jsx("div", { className: "flex-1" }), _jsx(ComposerActionButton, {})] })] }) })] })] })] }));
98
98
  }
@@ -3,8 +3,11 @@
3
3
  *
4
4
  * Sits in the composer next to Send. The choice rides on every request as
5
5
  * an AG-UI forwarded prop and is read at send time, so switching it
6
- * mid-conversation applies from the next message on. `Auto` sends nothing
7
- * and leaves the backend's configured level in place.
6
+ * mid-conversation applies from the next message on.
7
+ *
8
+ * `Auto` follows the backend's configured level and names it, so the user
9
+ * can always tell what they are actually running. An explicit level is a
10
+ * pin — nothing moves it, including a change of context.
8
11
  */
9
12
  export declare function ReasoningEffortPicker({ className }: {
10
13
  className?: string;
@@ -1,26 +1,50 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useStore } from "zustand";
3
3
  import { Check, ChevronDown } from "lucide-react";
4
- import { reasoningEffortStore, REASONING_EFFORT_LABELS, } from "@iloveagents/foundry-agent";
4
+ import { reasoningDefaultStore, reasoningEffortStore, REASONING_EFFORT_LABELS, } from "@iloveagents/foundry-agent";
5
5
  import { cn } from "@iloveagents/foundry-web-primitives";
6
6
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
7
7
  const OPTIONS = [
8
- { value: "default", hint: "Use the app's configured level" },
8
+ { value: "default", hint: "" }, // hint is resolved at render — see autoHint
9
9
  { value: "low", hint: "Answers sooner, thinks less" },
10
10
  { value: "medium", hint: "Good default for most questions" },
11
11
  { value: "high", hint: "Slower, better on hard problems" },
12
12
  { value: "xhigh", hint: "Slowest, for the hardest questions" },
13
13
  ];
14
+ /**
15
+ * What `Auto` is currently doing, in words.
16
+ *
17
+ * Never guesses. If the app has not told us what the backend resolves to,
18
+ * the hint says so rather than naming a level — an `Auto` that claims the
19
+ * wrong level is the exact failure this option is meant to avoid.
20
+ */
21
+ function autoHint(resolved, scope) {
22
+ if (!resolved)
23
+ return "Uses the app's configured level";
24
+ const where = scope ? ` for ${scope}` : "";
25
+ return `Currently ${REASONING_EFFORT_LABELS[resolved]}${where}`;
26
+ }
14
27
  /**
15
28
  * Lets the user choose how hard the model thinks, per conversation.
16
29
  *
17
30
  * Sits in the composer next to Send. The choice rides on every request as
18
31
  * an AG-UI forwarded prop and is read at send time, so switching it
19
- * mid-conversation applies from the next message on. `Auto` sends nothing
20
- * and leaves the backend's configured level in place.
32
+ * mid-conversation applies from the next message on.
33
+ *
34
+ * `Auto` follows the backend's configured level and names it, so the user
35
+ * can always tell what they are actually running. An explicit level is a
36
+ * pin — nothing moves it, including a change of context.
21
37
  */
22
38
  export function ReasoningEffortPicker({ className }) {
23
39
  const effort = useStore(reasoningEffortStore, (s) => s.effort);
24
40
  const setEffort = useStore(reasoningEffortStore, (s) => s.setEffort);
25
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("flex items-center gap-1 rounded-md px-2 py-1", "text-xs text-muted-foreground hover:text-foreground hover:bg-muted", "transition-colors", className), "aria-label": `Intelligence: ${REASONING_EFFORT_LABELS[effort]}`, children: [_jsx("span", { children: REASONING_EFFORT_LABELS[effort] }), _jsx(ChevronDown, { className: "size-3.5" })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [_jsx(DropdownMenuLabel, { className: "text-xs font-normal text-muted-foreground", children: "Intelligence" }), OPTIONS.map((opt) => (_jsxs(DropdownMenuItem, { onSelect: () => setEffort(opt.value), className: "flex items-start gap-2", children: [_jsx(Check, { className: cn("size-4 mt-0.5 shrink-0", effort === opt.value ? "opacity-100" : "opacity-0") }), _jsxs("span", { className: "flex flex-col", children: [_jsx("span", { className: "text-sm", children: REASONING_EFFORT_LABELS[opt.value] }), _jsx("span", { className: "text-xs text-muted-foreground", children: opt.hint })] })] }, opt.value)))] })] }));
41
+ const resolved = useStore(reasoningDefaultStore, (s) => s.resolved);
42
+ const scope = useStore(reasoningDefaultStore, (s) => s.scope);
43
+ // The trigger is the only thing visible without opening the menu, so it
44
+ // carries the resolved level too — "Auto · Medium" rather than a bare
45
+ // "Auto" that leaves the active level a mystery.
46
+ const triggerLabel = effort === "default" && resolved
47
+ ? `${REASONING_EFFORT_LABELS.default} · ${REASONING_EFFORT_LABELS[resolved]}`
48
+ : REASONING_EFFORT_LABELS[effort];
49
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("flex items-center gap-1 rounded-md px-2 py-1", "text-xs text-muted-foreground hover:text-foreground hover:bg-muted", "transition-colors", className), "aria-label": `Intelligence: ${triggerLabel}`, children: [_jsx("span", { children: triggerLabel }), _jsx(ChevronDown, { className: "size-3.5" })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [_jsx(DropdownMenuLabel, { className: "text-xs font-normal text-muted-foreground", children: "Intelligence" }), OPTIONS.map((opt) => (_jsxs(DropdownMenuItem, { onSelect: () => setEffort(opt.value), className: "flex items-start gap-2", children: [_jsx(Check, { className: cn("size-4 mt-0.5 shrink-0", effort === opt.value ? "opacity-100" : "opacity-0") }), _jsxs("span", { className: "flex flex-col", children: [_jsx("span", { className: "text-sm", children: REASONING_EFFORT_LABELS[opt.value] }), _jsx("span", { className: "text-xs text-muted-foreground", children: opt.value === "default" ? autoHint(resolved, scope) : opt.hint })] })] }, opt.value)))] })] }));
26
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloveagents/foundry-web-ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "license": "MIT",
5
5
  "description": "React agent UI core for Foundry UI — chat, composer, AG-UI adapter for assistant-ui, tool cards, panels, sidebar, theme runtime, and UI stores.",
6
6
  "keywords": [
@@ -70,8 +70,8 @@
70
70
  "tailwind-merge": "^3.5.0",
71
71
  "react-markdown": "^10.0.0",
72
72
  "remark-gfm": "^4.0.0",
73
- "@iloveagents/foundry-agent": "^0.9.0",
74
- "@iloveagents/foundry-web-primitives": "^0.9.0"
73
+ "@iloveagents/foundry-agent": "^0.10.1",
74
+ "@iloveagents/foundry-web-primitives": "^0.10.1"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@ag-ui/client": "^0.0.52",