@calliopelabs/ui 0.13.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -4
- package/dist/agent/AgentChat.d.ts +63 -0
- package/dist/agent/AgentChat.js +117 -0
- package/dist/agent/AgentLauncher.d.ts +25 -0
- package/dist/agent/AgentLauncher.js +25 -0
- package/dist/agent/AgentProvider.d.ts +82 -0
- package/dist/agent/AgentProvider.js +176 -0
- package/dist/agent/CalliopeAgent.d.ts +43 -0
- package/dist/agent/CalliopeAgent.js +111 -0
- package/dist/agent/WorkflowRenderers.d.ts +61 -0
- package/dist/agent/WorkflowRenderers.js +102 -0
- package/dist/agent/controller.d.ts +167 -0
- package/dist/agent/controller.js +496 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.d.ts +41 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +145 -0
- package/dist/agent/copilotkit/index.d.ts +6 -0
- package/dist/agent/copilotkit/index.js +6 -0
- package/dist/agent/engine.d.ts +41 -0
- package/dist/agent/engine.js +93 -0
- package/dist/agent/index.d.ts +17 -0
- package/dist/agent/index.js +17 -0
- package/dist/agent/mock.d.ts +56 -0
- package/dist/agent/mock.js +143 -0
- package/dist/agent/renderers.d.ts +49 -0
- package/dist/agent/renderers.js +27 -0
- package/dist/agent/surface.d.ts +30 -0
- package/dist/agent/surface.js +90 -0
- package/dist/agent/tool.d.ts +98 -0
- package/dist/agent/tool.js +81 -0
- package/dist/appearance/brands.d.ts +31 -12
- package/dist/appearance/brands.js +32 -51
- package/dist/chat/AgentProgress.d.ts +19 -0
- package/dist/chat/AgentProgress.js +34 -0
- package/dist/chat/Composer.d.ts +31 -0
- package/dist/chat/Composer.js +44 -0
- package/dist/chat/MessageBubble.d.ts +23 -0
- package/dist/chat/MessageBubble.js +26 -0
- package/dist/chat/MessageList.d.ts +43 -0
- package/dist/chat/MessageList.js +71 -0
- package/dist/chat/ModelPicker.d.ts +17 -0
- package/dist/chat/ModelPicker.js +13 -0
- package/dist/chat/ToolCallCard.d.ts +27 -0
- package/dist/chat/ToolCallCard.js +52 -0
- package/dist/chat/model.d.ts +123 -0
- package/dist/chat/model.js +337 -0
- package/dist/chat/patch.d.ts +12 -0
- package/dist/chat/patch.js +149 -0
- package/dist/chat/useAgUiRun.d.ts +52 -0
- package/dist/chat/useAgUiRun.js +137 -0
- package/dist/css/appearance.js +1 -1
- package/dist/css/calliope.css +5 -0
- package/dist/css/tokens.css +5 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +16 -1
- package/dist/primitives/Badge.d.ts +14 -0
- package/dist/primitives/Badge.js +25 -0
- package/dist/primitives/Button.d.ts +1 -1
- package/dist/primitives/ContextMenu.d.ts +18 -0
- package/dist/primitives/ContextMenu.js +34 -0
- package/dist/primitives/ScrollArea.d.ts +16 -0
- package/dist/primitives/ScrollArea.js +11 -0
- package/dist/primitives/Toast.d.ts +37 -0
- package/dist/primitives/Toast.js +43 -0
- package/dist/primitives/Tooltip.d.ts +16 -0
- package/dist/primitives/Tooltip.js +26 -0
- package/dist/provider/messages.d.ts +135 -0
- package/dist/provider/messages.js +131 -0
- package/dist/theme/css-layer.js +2 -1
- package/dist/theme/mappings/palette.d.ts +52 -0
- package/dist/theme/mappings/palette.js +208 -0
- package/dist/theme/mappings/render.d.ts +9 -0
- package/dist/theme/mappings/render.js +520 -0
- package/dist/theme/mappings/targets.d.ts +43 -0
- package/dist/theme/mappings/targets.js +222 -0
- package/dist/theme/tokens.d.ts +10 -9
- package/dist/theme/tokens.generated.d.ts +356 -0
- package/dist/theme/tokens.generated.js +360 -0
- package/dist/theme/tokens.js +20 -264
- package/dist/tokens/README.jupyterlab.md +125 -0
- package/dist/tokens/README.md +15 -0
- package/dist/tokens/README.native.md +29 -0
- package/dist/tokens/README.plain.md +84 -0
- package/dist/tokens/README.python.md +33 -0
- package/dist/tokens/README.vscode.md +114 -0
- package/dist/tokens/jupyterlab.css +1710 -0
- package/dist/tokens/plain.css +930 -0
- package/dist/tokens/tokens.native.d.ts +85 -0
- package/dist/tokens/tokens.native.js +773 -0
- package/dist/tokens/tokens.py +1043 -0
- package/dist/tokens/vscode.css +1138 -0
- package/dist/workbench/FileBrowser.d.ts +62 -0
- package/dist/workbench/FileBrowser.js +117 -0
- package/dist/workbench/FileGrid.d.ts +13 -0
- package/dist/workbench/FileGrid.js +114 -0
- package/dist/workbench/FileIcon.d.ts +10 -0
- package/dist/workbench/FileIcon.js +24 -0
- package/dist/workbench/FileList.d.ts +35 -0
- package/dist/workbench/FileList.js +160 -0
- package/dist/workbench/FilePreview.d.ts +66 -0
- package/dist/workbench/FilePreview.js +108 -0
- package/dist/workbench/FileTree.d.ts +37 -0
- package/dist/workbench/FileTree.js +218 -0
- package/dist/workbench/UploadDropzone.d.ts +52 -0
- package/dist/workbench/UploadDropzone.js +115 -0
- package/dist/workbench/dnd.d.ts +17 -0
- package/dist/workbench/dnd.js +77 -0
- package/dist/workbench/index.d.ts +16 -0
- package/dist/workbench/index.js +16 -0
- package/dist/workbench/model.d.ts +72 -0
- package/dist/workbench/model.js +214 -0
- package/dist/workbench/selection.d.ts +16 -0
- package/dist/workbench/selection.js +46 -0
- package/dist/workbench/typeahead.d.ts +15 -0
- package/dist/workbench/typeahead.js +22 -0
- package/package.json +39 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The shared operator UI of Calliope AI: the app shell, navigation, page frame, data table and list patterns, status colours, design tokens and appearance settings that Astrolift AI and Zentinelle AI both run on. Extracted from the Astrolift AI frontend and generalized with a per-product config, so each console keeps its own name, accent, sidebar colour and navigation while sharing everything else.
|
|
4
4
|
|
|
5
|
-
The shared base of the Calliope AI ui series. Product packages (`@calliopelabs/ui-astrolift`, `ui-zentinelle`, `ui-workbench
|
|
5
|
+
The shared base of the Calliope AI ui series. Product packages (`@calliopelabs/ui-astrolift`, `ui-zentinelle`, `ui-workbench`) depend on it and carry only what is specific to their product. `0.2.0` was published as `@calliopelabs/ui-astrolift` with the same exports; it is deprecated in favour of this package.
|
|
6
6
|
|
|
7
7
|
It is built Storybook first: every component and every screen pattern is built and reviewed in the catalog before an app uses it. The catalog is published at **https://calliope-ux.astro.conflict.cloud**.
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ It is built Storybook first: every component and every screen pattern is built a
|
|
|
16
16
|
npm i @calliopelabs/ui
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Peer dependencies: `react` and `react-dom` 19, and `tailwindcss` 4 for styling.
|
|
19
|
+
Peer dependencies: `react` and `react-dom` 19, and `tailwindcss` 4 for styling. The opt-in agent entries add `@ag-ui/client` and `@copilotkit/react-core` (see [Agent](#agent)).
|
|
20
20
|
|
|
21
21
|
## Styles
|
|
22
22
|
|
|
@@ -395,6 +395,180 @@ The canvas pans, zooms and fits without going below `READABLE_ZOOM`; loop bodies
|
|
|
395
395
|
|
|
396
396
|
`@xyflow/react` comes with the package; its structural CSS is in the preset.
|
|
397
397
|
|
|
398
|
+
## Chat
|
|
399
|
+
|
|
400
|
+
Engine-agnostic building blocks for agent chat, over the [AG-UI](https://docs.ag-ui.com) event shape (`@ag-ui/core` 1.0). An engine that speaks AG-UI drives them directly; any other gets an adapter that turns its stream into AG-UI events. Products keep their own screens and brands: there is no shipped chat page, only the pieces. Host-driven like everything else here: the package never connects, authenticates, runs a tool or resumes a run.
|
|
401
|
+
|
|
402
|
+
- **The model.** `reduceChat(view, event)` folds one AG-UI event into a `ChatView`: messages with streaming text, tool calls with their arguments, results, status and approvals, the timeline that interleaves them, the run's steps and status (`idle`, `running`, `finished`, `interrupted`, `cancelled`, `error`), the shared state (`STATE_SNAPSHOT` plus `STATE_DELTA` as JSON Patch, `applyJsonPatch`), and `CUSTOM` and `RAW` events in order. `MESSAGES_SNAPSHOT` replaces the conversation, so a watcher can join part way. It is pure and immutable, never throws on an event it does not expect, and counts a delta that fails in `rejectedDeltas`. `foldChat(events, view?)` folds a list; `createChatView()` starts one. Activity, reasoning and sub-agent events leave the view as it is.
|
|
403
|
+
- **The hook.** `useAgUiRun({ initialView?, onError? })` holds the view and reads one run at a time from the source the host hands `run(source)`: an `AsyncIterable` of events or a subscribe function `(emit, done) => unsubscribe`. `stop()` lets go and closes the view as cancelled (telling the agent to stop is the host's request); `dispatch(userMessageEvents(id, text))` shows the person's message before the run starts; a stream that fails lands in the view as a run error.
|
|
404
|
+
- **Human in the loop.** A `RUN_FINISHED` interrupt outcome whose interrupt names a `toolCallId` puts an `approval` on that call. `ToolCallCard` then offers approve and deny through `ConfirmDialog`, a denial always with a reason, and hands the decision to `onApprove` or `onDeny`; the host resumes the run (AG-UI `resume`) and the next `RUN_STARTED` clears the approval.
|
|
405
|
+
- **The components.** `MessageList` (the timeline, tool calls inline, follows the stream until the person scrolls up, a failed run's error with retry), `MessageBubble` (user, assistant, system, developer and tool), `Composer` (Enter sends, Shift+Enter breaks the line, stop while running, `attachments` and `actions` slots), `ToolCallCard` (arguments, result or error, `renderResult` for a table or chart), `AgentProgress` (status and steps on `RunTimeline`) and `ModelPicker` (the host's options).
|
|
406
|
+
- **Markdown is the host's.** Text renders as plain text unless the host passes `renderContent` (its markdown component, given the text so far). Plain text is never parsed as HTML.
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
const chat = useAgUiRun();
|
|
410
|
+
|
|
411
|
+
<MessageList
|
|
412
|
+
view={chat.view}
|
|
413
|
+
renderContent={(text) => <Markdown>{text}</Markdown>}
|
|
414
|
+
onApprove={(call) => chat.run(agent.resume(call.approval!.interruptId, "resolved"))}
|
|
415
|
+
onDeny={(call, reason) => chat.run(agent.resume(call.approval!.interruptId, "cancelled", reason))}
|
|
416
|
+
/>
|
|
417
|
+
<Composer
|
|
418
|
+
running={chat.running}
|
|
419
|
+
onStop={() => { agent.abort(); chat.stop(); }}
|
|
420
|
+
onSubmit={(text) => {
|
|
421
|
+
chat.dispatch(userMessageEvents(crypto.randomUUID(), text));
|
|
422
|
+
void chat.run(agent.stream({ threadId, text }));
|
|
423
|
+
}}
|
|
424
|
+
actions={<ModelPicker options={models} value={model} onValueChange={setModel} />}
|
|
425
|
+
/>
|
|
426
|
+
<AgentProgress view={chat.view} />
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
`agent` above is the host's own client. The catalog's `Chat/ChatPanel` composes the pieces the same way, driven by a recorded run. Their copy is in the `chat` message group.
|
|
430
|
+
|
|
431
|
+
## Agent
|
|
432
|
+
|
|
433
|
+
`CalliopeAgent` is the one embedded agent for every Calliope AI product: a floating bubble, the shell's right rail, inline in a page, or a page of its own. It speaks [AG-UI](https://docs.ag-ui.com) to any agent, directly or through a [CopilotKit](https://docs.copilotkit.ai) runtime, and draws every pixel with the chat kit above, on the product's tokens, mark and runtime brand. CopilotKit wires the runtime and runs the agent; its UI and stylesheet are never loaded.
|
|
434
|
+
|
|
435
|
+
It is an opt-in entry, so apps without an agent never load its dependencies:
|
|
436
|
+
|
|
437
|
+
```sh
|
|
438
|
+
npm i @ag-ui/client # @calliopelabs/ui/agent
|
|
439
|
+
npm i @ag-ui/client @copilotkit/react-core # @calliopelabs/ui/agent/copilotkit (1.79.1 or later, 1.x)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Both are optional peers. `npm run pack:check` fails if the main entry ever reaches them. Most of the entry's weight is `@ag-ui/client` itself (with its protobuf codec, about 150 KB gzipped; the agent's own code is about 24 KB minified), which a CopilotKit app already ships. To keep it off a page's first load, load the provider when the person first opens the assistant (`next/dynamic` with `ssr: false`) and keep it mounted after.
|
|
443
|
+
|
|
444
|
+
### A Next.js app with a CopilotKit runtime
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
// app/api/copilotkit/route.ts: the runtime, on the server (agents, keys and auth stay here)
|
|
448
|
+
import { HttpAgent } from "@ag-ui/client";
|
|
449
|
+
import { CopilotRuntime, createCopilotRuntimeHandler } from "@copilotkit/runtime/v2";
|
|
450
|
+
|
|
451
|
+
const runtime = new CopilotRuntime({
|
|
452
|
+
agents: { default: new HttpAgent({ url: process.env.AGENT_URL! }) },
|
|
453
|
+
});
|
|
454
|
+
const handler = createCopilotRuntimeHandler({
|
|
455
|
+
runtime,
|
|
456
|
+
basePath: "/api/copilotkit",
|
|
457
|
+
mode: "single-route",
|
|
458
|
+
});
|
|
459
|
+
export const POST = (request: Request) => handler(request);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
// app/assistant.tsx
|
|
464
|
+
"use client";
|
|
465
|
+
import { CalliopeAgent, useAgentReadable, useAgentTool } from "@calliopelabs/ui/agent";
|
|
466
|
+
import { CopilotKitAgentProvider } from "@calliopelabs/ui/agent/copilotkit";
|
|
467
|
+
import { z } from "zod";
|
|
468
|
+
|
|
469
|
+
export function Assistant({ children, project }: { children: React.ReactNode; project: string }) {
|
|
470
|
+
return (
|
|
471
|
+
<CopilotKitAgentProvider
|
|
472
|
+
runtimeUrl="/api/copilotkit"
|
|
473
|
+
useSingleEndpoint
|
|
474
|
+
headers={{ "x-project": project }}
|
|
475
|
+
>
|
|
476
|
+
{children}
|
|
477
|
+
<CalliopeAgent mode="bubble" persistKey="app.assistant.open" />
|
|
478
|
+
</CopilotKitAgentProvider>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Anywhere under it, while mounted:
|
|
483
|
+
useAgentReadable("The open project", { project });
|
|
484
|
+
useAgentTool({
|
|
485
|
+
name: "open_scene",
|
|
486
|
+
parameters: z.object({ id: z.string() }),
|
|
487
|
+
handler: ({ id }) => router.push(`/scenes/${id}`),
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Mount it from the root layout (a Server Component) as `<Assistant project={slug}>{children}</Assistant>`. Tools carry functions, so they are registered in client components, never passed from a server one.
|
|
492
|
+
|
|
493
|
+
### A plain AG-UI endpoint
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
"use client";
|
|
497
|
+
import { CalliopeAgent, CalliopeAgentProvider } from "@calliopelabs/ui/agent";
|
|
498
|
+
|
|
499
|
+
<CalliopeAgentProvider url="/api/agui" headers={{ "x-project": slug }} suggestions={startingPoints}>
|
|
500
|
+
<AppShell
|
|
501
|
+
{...shell}
|
|
502
|
+
rightRail={<CalliopeAgent mode="rail" open={open} onOpenChange={setOpen} />}
|
|
503
|
+
rightRailLabel="Assistant"
|
|
504
|
+
onToggleRightRail={() => setOpen((o) => !o)}
|
|
505
|
+
>
|
|
506
|
+
{children}
|
|
507
|
+
</AppShell>
|
|
508
|
+
</CalliopeAgentProvider>;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`url` makes an `HttpAgent` (same origin, so the session cookie goes along); `agent` takes any `AbstractAgent` instead. `createFunctionAgent(run)` turns a function that yields AG-UI events (or an existing non-AG-UI endpoint, adapted) into one.
|
|
512
|
+
|
|
513
|
+
### What it does
|
|
514
|
+
|
|
515
|
+
- **One conversation, one store.** `useCalliopeAgent()` returns the chat kit's `ChatView` (messages, tool calls, steps, interrupts, shared state, custom events) and the actions: `send(text, { parts })`, `stop()` (aborts the run and any tool waiting), `regenerate()`, `approve(call)` and `deny(call, reason)`, `resolveInterrupt(id, response)`, `respond(toolCallId, result)`, `getState()` and `setState(state)`, `switchThread(id)` (with the provider's `loadThread`) and `newThread()`, plus `running`, `waiting`, `unread` and `suggestions`.
|
|
516
|
+
- **Tools.** `useAgentTool(defineTool({ name, description, parameters, handler, render, followUp }))` offers a browser tool while the component is mounted. `parameters` is a Standard Schema (zod 4, valibot, arktype), which types and validates the arguments, or a JSON Schema. A tool with `render` and no `handler` is human-in-the-loop: the run waits until its card calls `respond(result)`, and the composer holds meanwhile.
|
|
517
|
+
- **Readables and suggestions.** `useAgentReadable(description, value)` sends page context with every run; `useAgentSuggestions([{ title, message }])` offers starting points. Both also come as provider props for the whole app.
|
|
518
|
+
- **Approvals.** A run that ends on an AG-UI interrupt pauses. A tool call it names gets approve and deny on its card (a denial always with a reason); any other interrupt gets `InterruptCard`, or the host's `renderers.interrupts[reason]`. The run resumes with AG-UI `resume` once every open interrupt has an answer; a new message cancels what is still open.
|
|
519
|
+
- **Generative UI.** `renderers` on `CalliopeAgent` maps tool names (backend tools too), `CUSTOM` event names and interrupt reasons to host components, and draws the shared state above the conversation. `workflowRenderers` draws the run and review components: `calliope.run` on `RunTimeline` (events with one `id` update one card), `calliope.artifact` on `ArtifactCard` with `DocumentReview`, `MediaReview` or `FormReview` (signing or sending the review resumes the run), and `calliope.question` interrupts on `WorkflowQuestionCard`.
|
|
520
|
+
- **Surfaces.** `mode="bubble"` (a launcher and a non-modal panel; a drawer on phones), `"rail"` (a `RailPanel` for `AppShell`'s `rightRail`; the shell's drawer on phones), `"inline"` and `"page"`. Open state is controlled, held, or remembered per person with `persistKey`; `shortcut` (`mod+/` by default) toggles it; answers that land while it is closed show as unread on the launcher. Slots: `renderContent` (the host's markdown), `attachments` and `sendOptions` (attachments as AG-UI input parts), `composerActions`, `modelPicker` (pass the choice in the provider's `forwardedProps`), `headerActions`, `empty`.
|
|
521
|
+
- **Telemetry and errors.** `onEvent(event)` sees every AG-UI event; `onError(error)` every failed run (also shown in the conversation with retry).
|
|
522
|
+
- **Testing.** `createMockAgent({ turns })` plays recorded turns; `createFunctionAgent(run)` makes an agent of any function that yields AG-UI events. Both are real `HttpAgent`s answered in the page, so stories and tests need no network.
|
|
523
|
+
|
|
524
|
+
Headers on the provider run in the browser: never put a secret there. Keys, the model and authorization stay on the server, in the runtime route or the agent. Copy is in the `agent` message group.
|
|
525
|
+
|
|
526
|
+
### Moving to it
|
|
527
|
+
|
|
528
|
+
| Product | Today | With `CalliopeAgent` |
|
|
529
|
+
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
530
|
+
| Astrolift Studio | `CopilotKitProvider` (1.79.1) and its own `AssistantPanel`, `AssistantTranscript`, `AssistantInput`, `SuggestionChips`, `AssistantLauncher`, `dock.tsx`, `useStored` open state | `CopilotKitAgentProvider runtimeUrl="/api/assistant" useSingleEndpoint agentId="studio" headers={{ [PROJECT_HEADER]: slug }}` (or keep its `CopilotKitProvider`: ours reuses it) and `<CalliopeAgent mode="rail" persistKey={openKey}>` in the shell's rail. The 15 `useHumanInTheLoop` actions become `useAgentTool({ name, parameters, render })` with `ActionCard` as `render` (calling `respond`); the whiteboard tools become `useAgentTool` with their zod schemas; `useAgentContext` readables become `useAgentReadable`; `startingPoints` becomes `useAgentSuggestions`; thread loading becomes `loadThread`. The server route and runner stay. |
|
|
531
|
+
| Zentinelle | `ChatBubble` (sparkle button, `Drawer`), `use-chat` parsing a custom `data:` stream, `MessageList`, `ChatInput`, `ModelSelector`, full-page `AssistantScreen` | `<CalliopeAgent mode="bubble">` and `mode="page"`, with `modelPicker` fed from `/assistant/providers` and the model in `forwardedProps`. Transport: port `/assistant/chat` to AG-UI (`pending_action` as an interrupt, `navigation` as a `CUSTOM` event drawn as a link chip), or until then adapt it with `createFunctionAgent` over `authenticatedFetch`. An approval resolves the interrupt; the host's `execute-tool` call with the approval token stays a host action in the interrupt renderer, gated on `canMutate`. |
|
|
532
|
+
| Astrolift app (Wayfinding) | `WayfindingBubble` and `use-wayfinding` posting to `/wayfinding/ask/` | `<CalliopeAgent mode="bubble">` over `createFunctionAgent` that posts the question and returns the answer as text and the routes as a `CUSTOM` event, drawn by a `renderers.custom.routes` component of `Link` chips that close the panel. No tools: read-only by construction. A 503 thrown from the function shows as the run's error. |
|
|
533
|
+
| Boilerworks django-nextjs-copilotkit | CopilotKit 1.63.1 v1 (`CopilotKit`, `CopilotSidebar`, `useCopilotAction`, `useCopilotReadable`, `react-ui` CSS re-themed) | Upgrade to `@copilotkit/react-core` 1.79+ and `@copilotkit/runtime/v2` (the runtime route keeps its `HttpAgent` to Django), then `CopilotKitAgentProvider runtimeUrl="/api/copilotkit" agentId="boilerworks_agent"` and `<CalliopeAgent mode="rail">` in place of `CopilotSidebar`. `useCopilotReadable` becomes `useAgentReadable`; `renderAndWaitForResponse` becomes `useAgentTool` with `render` and `respond`; the render-only `draft_form_definition` card becomes `renderers.tools.draft_form_definition`. Drop `@copilotkit/react-ui` and `copilot.css`. |
|
|
534
|
+
|
|
535
|
+
## Workbench
|
|
536
|
+
|
|
537
|
+
`@calliopelabs/ui/workbench` is an opt-in entry for IDE-style tools (File Manager, Loadr, Chat Studio): an app without a file browser never loads it. It needs nothing beyond the package's own dependencies, and its styles come from the preset. Like the rest, it is host-driven: data comes in as props, every change goes out through a callback, and nothing fetches.
|
|
538
|
+
|
|
539
|
+
- **`FileBrowser`**: breadcrumb (its crumbs are drop targets), list and grid views, sorting (folders first, natural order), selection, a context menu, drag-and-drop moves (Alt copies) and an optional folder tree. View, sort and selection work uncontrolled or controlled. Copy for an empty folder, a toolbar slot, and thumbnails for grid tiles.
|
|
540
|
+
- **`FileTree`**: a lazy tree on the WAI-ARIA tree pattern (arrows open and move, Home, End, typeahead). The host answers `onLoad(path)` with a `FileBranch` (`loading`, `error`, `ready`) per directory in `branches`; a failed branch retries through `onRetry`.
|
|
541
|
+
- **`FileList`** and **`FileGrid`**, the two views on their own: a grid (WAI-ARIA grid pattern: arrows between cells, selection follows focus, Shift extends, Cmd/Ctrl moves without selecting, Cmd/Ctrl+A, Escape, Enter opens, Delete, F2 renames) and a multi-select listbox of tiles with two-dimensional arrows.
|
|
542
|
+
- **`UploadDropzone`**: wraps an area (an overlay while desktop files hover, progress floating in a corner) or draws its own box with a picker. `accept` and `maxSize` filter into `onFiles` and `onReject`; the host's `uploads` (`queued`, `uploading` with a percentage or none, `done`, `error`, `canceled`) draw as progress bars with cancel and dismiss.
|
|
543
|
+
- **`FilePreview`**: CSV and TSV (parsed here, quoted fields included) and server-parsed tables in the library's `DataTable` with search, sort and paging; JSON pretty-printed; images; text; markdown through the host's `renderMarkdown` (the package ships no markdown parser, so without one the source shows); a binary fallback, loading, error and a truncation note.
|
|
544
|
+
- Helpers: `FileItem`, `sortFiles`, `previewKindFor`, `parseDelimited`, `formatBytes`, `canDrop`, `pathSegments`, `parentPath`, `joinPath`, `FileIcon`.
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
import {
|
|
548
|
+
FileBrowser,
|
|
549
|
+
FilePreview,
|
|
550
|
+
UploadDropzone,
|
|
551
|
+
previewKindFor,
|
|
552
|
+
} from "@calliopelabs/ui/workbench";
|
|
553
|
+
|
|
554
|
+
<UploadDropzone onFiles={(files) => upload(files, path)} uploads={uploads} onCancel={cancel}>
|
|
555
|
+
<FileBrowser
|
|
556
|
+
path={path}
|
|
557
|
+
items={listing.data?.entries.map(toFileItem)}
|
|
558
|
+
error={listing.error}
|
|
559
|
+
onRetry={listing.refetch}
|
|
560
|
+
onNavigate={setPath}
|
|
561
|
+
onOpen={(item) => setPreview(item.path)}
|
|
562
|
+
onMove={(sources, target, { copy }) => (copy ? copyTo : moveTo)(sources, target)}
|
|
563
|
+
onDelete={confirmDelete}
|
|
564
|
+
contextMenu={(targets) => <FileActions targets={targets} />}
|
|
565
|
+
tree={{ root, branches, onLoad: loadFolder, onRetry: loadFolder }}
|
|
566
|
+
/>
|
|
567
|
+
</UploadDropzone>;
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Their copy is in the `workbench` message group. The catalog's **Workbench** section shows every state, light and dark, narrow, and large trees.
|
|
571
|
+
|
|
398
572
|
## Appearance
|
|
399
573
|
|
|
400
574
|
The person sets four axes, per product: the **ground** (black, charcoal, graphite, emerald, navy, paper, mist; it decides light or dark), the **accent** (the product's own, a family accent, or any colour that stands 3:1 off the ground), the **density** (`compact` hairline rows or boxed `cards`) and the **corners** (0, 2, 4 or 10 px). `AppearancePicker` is the settings UI for all four.
|
|
@@ -554,6 +728,20 @@ document.addEventListener("calliope:appearance", (event) => console.log(event.de
|
|
|
554
728
|
|
|
555
729
|
A product that is not built in sets its sidebar with `[data-product="myapp"] { --brand-sidebar: #0b0b19; }` in its own CSS, picks a family accent, and boots with `CalliopeAppearance.boot(config)`, where `config` is the JSON `bootConfig(defineProduct(...))` returns (or one of `CalliopeAppearance.products`, edited).
|
|
556
730
|
|
|
731
|
+
## Token mappings
|
|
732
|
+
|
|
733
|
+
Stacks that cannot take the preset or the CSS layer read the same tokens through generated files under `@calliopelabs/ui/tokens/`, each with a guide beside it in `dist/tokens/`:
|
|
734
|
+
|
|
735
|
+
| File | For |
|
|
736
|
+
| ---------------------------------------- | ----------------------------------------------------------- |
|
|
737
|
+
| `@calliopelabs/ui/tokens/jupyterlab.css` | JupyterLab and Notebook 7: the `--jp-*` theme variables |
|
|
738
|
+
| `@calliopelabs/ui/tokens/vscode.css` | VS Code webviews: `--vscode-*` fallbacks outside VS Code |
|
|
739
|
+
| `@calliopelabs/ui/tokens/tokens.py` | Python (the installer): RGB triplets and a small accessor |
|
|
740
|
+
| `@calliopelabs/ui/tokens/native` | React Native: a typed palette object per ground and accent |
|
|
741
|
+
| `@calliopelabs/ui/tokens/plain.css` | Sites and Cykick: every token as a plain, concrete variable |
|
|
742
|
+
|
|
743
|
+
Every file covers every ground and family accent, chosen by `data-ground` and `data-accent` in the CSS ones and by argument in the others. They are generated by `npm run theme` from the design tokens (`tokens/*.json`, through `src/theme/tokens.ts`), and a test holds every mapped value to what the preset paints on that ground with that accent. The mapping tables are in each guide.
|
|
744
|
+
|
|
557
745
|
## Copy and languages
|
|
558
746
|
|
|
559
747
|
Every string the package renders is English by default and overridable, one group at a time:
|
|
@@ -573,11 +761,11 @@ Node 24 (`.nvmrc`).
|
|
|
573
761
|
| `npm run storybook` | The catalog, on port 6006 |
|
|
574
762
|
| `npm test` | Every test, including one that renders every story and runs the a11y addon on it |
|
|
575
763
|
| `npm run lint`, `npm run typecheck`, `npm run format:check` | The gates CI runs |
|
|
576
|
-
| `npm run theme` | Regenerates `
|
|
764
|
+
| `npm run theme` | Regenerates the constants, `theme.css`, the CSS layer and the token mappings |
|
|
577
765
|
| `npm run build`, `npm run pack:check` | Builds `dist/` and checks the package a consumer receives |
|
|
578
766
|
| `npm run build-storybook` | The static catalog |
|
|
579
767
|
|
|
580
|
-
Conventions for contributors and agents are in [`bootstrap.md`](bootstrap.md).
|
|
768
|
+
Conventions for contributors and agents are in [`bootstrap.md`](bootstrap.md). Designers: the token files, what each one changes and how to propose a change are in the Storybook pages under **Designer guide** (`src/docs/`).
|
|
581
769
|
|
|
582
770
|
## Releasing
|
|
583
771
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { ChatContentRenderer } from "../chat/MessageBubble.js";
|
|
3
|
+
import { type ChatToolCall } from "../chat/model.js";
|
|
4
|
+
import { type ModelPickerProps } from "../chat/ModelPicker.js";
|
|
5
|
+
import type { SendOptions } from "./controller.js";
|
|
6
|
+
import { type AgentRenderers } from "./renderers.js";
|
|
7
|
+
export interface AgentChatProps {
|
|
8
|
+
/** The panel's title. Defaults to the product's assistant ("Astrolift AI assistant"). */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Hides the heading row (inline and page surfaces that have their own). */
|
|
11
|
+
hideHeader?: boolean;
|
|
12
|
+
/** Controls beside the title, before the built-in ones. */
|
|
13
|
+
headerActions?: React.ReactNode;
|
|
14
|
+
/** Shows a close button. */
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
/** Shows expand and restore (the rail's full-width mode). */
|
|
17
|
+
expanded?: boolean;
|
|
18
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
19
|
+
/** Hides "New conversation". */
|
|
20
|
+
hideNewConversation?: boolean;
|
|
21
|
+
/** Draws message text: the host's markdown renderer. Plain text by default. */
|
|
22
|
+
renderContent?: ChatContentRenderer;
|
|
23
|
+
/** Draws a tool call the host has its own component for; undefined falls through to the tool's `render`, then the default card. */
|
|
24
|
+
renderToolCall?: (call: ChatToolCall) => React.ReactNode;
|
|
25
|
+
/** The tool's name for people, per call. */
|
|
26
|
+
toolTitle?: (call: ChatToolCall) => string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Generative UI: host components for tool calls, custom events, interrupts
|
|
29
|
+
* and the shared state (see `AgentRenderers`; `workflowRenderers` draws the
|
|
30
|
+
* run and review components). `renderToolCall` still wins for a call.
|
|
31
|
+
*/
|
|
32
|
+
renderers?: AgentRenderers;
|
|
33
|
+
/** "line": a one-line status while running (bubble, rail). "steps": the run's steps on the run timeline (page). */
|
|
34
|
+
progress?: "line" | "steps" | "none";
|
|
35
|
+
/** Replaces the empty state (the suggestions still show under it). */
|
|
36
|
+
empty?: React.ReactNode;
|
|
37
|
+
/** Above the composer's field: the host's attachment chips. */
|
|
38
|
+
attachments?: React.ReactNode;
|
|
39
|
+
/** Beside send: an attach button, other controls. */
|
|
40
|
+
composerActions?: React.ReactNode;
|
|
41
|
+
/** A model picker in the composer; the host passes the chosen model in `forwardedProps`. */
|
|
42
|
+
modelPicker?: ModelPickerProps;
|
|
43
|
+
/** What to send with the text: the attachments as AG-UI input parts. Called on send. */
|
|
44
|
+
sendOptions?: () => SendOptions | undefined;
|
|
45
|
+
placeholder?: string;
|
|
46
|
+
autoFocus?: boolean;
|
|
47
|
+
/** Offers the suggestions after each answer too, not only before the first message. */
|
|
48
|
+
suggestionsAfterRun?: boolean;
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Starts a new, empty conversation with the nearest agent. */
|
|
52
|
+
export declare function NewConversationButton({ className }: {
|
|
53
|
+
className?: string;
|
|
54
|
+
}): React.JSX.Element;
|
|
55
|
+
/**
|
|
56
|
+
* The conversation with the nearest agent, whole: a heading row, the
|
|
57
|
+
* messages with tool cards and approvals, starting points, run status and
|
|
58
|
+
* the composer. Every surface of `CalliopeAgent` draws this; a host that
|
|
59
|
+
* lays out its own screen can too. Tools registered with a `render` draw
|
|
60
|
+
* their own calls; one that waits on the person gets `respond`, and the
|
|
61
|
+
* composer holds until it is answered.
|
|
62
|
+
*/
|
|
63
|
+
export declare function AgentChat({ title, hideHeader, headerActions, onClose, expanded, onExpandedChange, hideNewConversation, renderContent, renderToolCall, toolTitle, renderers, progress, empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun, className, }: AgentChatProps): React.JSX.Element;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Maximize2Icon, Minimize2Icon, SquarePenIcon, XIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useProduct } from "../appearance/AppearanceProvider.js";
|
|
6
|
+
import { AgentProgress } from "../chat/AgentProgress.js";
|
|
7
|
+
import { Composer } from "../chat/Composer.js";
|
|
8
|
+
import { MessageList } from "../chat/MessageList.js";
|
|
9
|
+
import { parseToolCallArgs } from "../chat/model.js";
|
|
10
|
+
import { ModelPicker } from "../chat/ModelPicker.js";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
import { Button } from "../primitives/Button.js";
|
|
13
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
14
|
+
import { EmptyState } from "../states/EmptyState.js";
|
|
15
|
+
import { StatusDot } from "../status/StatusDot.js";
|
|
16
|
+
import { useCalliopeAgent } from "./AgentProvider.js";
|
|
17
|
+
import { parseResult } from "./renderers.js";
|
|
18
|
+
import { InterruptCard } from "./WorkflowRenderers.js";
|
|
19
|
+
function ToolRender({ tool, call, waiting, respond, }) {
|
|
20
|
+
return (_jsx(_Fragment, { children: tool.render?.({
|
|
21
|
+
call,
|
|
22
|
+
args: parseToolCallArgs(call.args),
|
|
23
|
+
...(waiting ? { respond: (result) => respond(call.id, result) } : {}),
|
|
24
|
+
}) }));
|
|
25
|
+
}
|
|
26
|
+
/** Starts a new, empty conversation with the nearest agent. */
|
|
27
|
+
export function NewConversationButton({ className }) {
|
|
28
|
+
const agent = useCalliopeAgent();
|
|
29
|
+
const { messages } = useUI();
|
|
30
|
+
const label = messages.agent.newConversation;
|
|
31
|
+
const fresh = agent.view.timeline.length === 0 && !agent.view.error;
|
|
32
|
+
return (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": label, title: label, disabled: fresh && !agent.running, onClick: () => void agent.newThread(), className: className, children: _jsx(SquarePenIcon, { "aria-hidden": true }) }));
|
|
33
|
+
}
|
|
34
|
+
function Suggestions({ suggestions, disabled, onPick, }) {
|
|
35
|
+
const { messages } = useUI();
|
|
36
|
+
if (suggestions.length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
return (_jsx("div", { role: "group", "aria-label": messages.agent.suggestions, "data-slot": "agent-suggestions", className: "flex min-w-0 flex-wrap gap-2", children: suggestions.map((s) => (_jsx(Button, { variant: "outline", size: "sm", disabled: disabled, onClick: () => onPick(s), className: "max-w-full", children: _jsx("span", { className: "truncate", children: s.title }) }, s.message))) }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The conversation with the nearest agent, whole: a heading row, the
|
|
42
|
+
* messages with tool cards and approvals, starting points, run status and
|
|
43
|
+
* the composer. Every surface of `CalliopeAgent` draws this; a host that
|
|
44
|
+
* lays out its own screen can too. Tools registered with a `render` draw
|
|
45
|
+
* their own calls; one that waits on the person gets `respond`, and the
|
|
46
|
+
* composer holds until it is answered.
|
|
47
|
+
*/
|
|
48
|
+
export function AgentChat({ title, hideHeader = false, headerActions, onClose, expanded, onExpandedChange, hideNewConversation = false, renderContent, renderToolCall, toolTitle, renderers, progress = "line", empty, attachments, composerActions, modelPicker, sendOptions, placeholder, autoFocus, suggestionsAfterRun = false, className, }) {
|
|
49
|
+
const agent = useCalliopeAgent();
|
|
50
|
+
const { messages } = useUI();
|
|
51
|
+
const product = useProduct();
|
|
52
|
+
const t = messages.agent;
|
|
53
|
+
const name = title ?? t.title(product?.name);
|
|
54
|
+
const headingId = React.useId();
|
|
55
|
+
const { view, running, waiting, suggestions, tools } = agent;
|
|
56
|
+
const blocked = waiting.length > 0;
|
|
57
|
+
const fresh = view.timeline.length === 0 && !view.error;
|
|
58
|
+
const send = (text) => void agent.send(text, sendOptions?.());
|
|
59
|
+
const root = React.useRef(null);
|
|
60
|
+
const pick = (s) => {
|
|
61
|
+
void agent.send(s.message);
|
|
62
|
+
// The chip goes away with the empty state; the composer keeps the person's place.
|
|
63
|
+
root.current?.querySelector("[data-slot=composer] textarea")?.focus();
|
|
64
|
+
};
|
|
65
|
+
const drawCall = (call) => {
|
|
66
|
+
const custom = renderToolCall?.(call);
|
|
67
|
+
if (custom !== undefined)
|
|
68
|
+
return custom;
|
|
69
|
+
const Renderer = renderers?.tools?.[call.name];
|
|
70
|
+
if (Renderer) {
|
|
71
|
+
return (_jsx(Renderer, { call: call, args: parseToolCallArgs(call.args), result: parseResult(call), agent: agent, ...(waiting.includes(call.id)
|
|
72
|
+
? { respond: (result) => agent.respond(call.id, result) }
|
|
73
|
+
: {}) }));
|
|
74
|
+
}
|
|
75
|
+
const tool = tools.find((t) => t.name === call.name && t.render);
|
|
76
|
+
if (!tool)
|
|
77
|
+
return undefined;
|
|
78
|
+
return (_jsx(ToolRender, { tool: tool, call: call, waiting: waiting.includes(call.id), respond: agent.respond }));
|
|
79
|
+
};
|
|
80
|
+
// Custom events, each after the timeline item it arrived after (the first, when before any).
|
|
81
|
+
const first = view.timeline[0];
|
|
82
|
+
const firstKey = first ? `${first.kind}:${first.id}` : null;
|
|
83
|
+
const drawAfter = (item) => {
|
|
84
|
+
const key = `${item.kind}:${item.id}`;
|
|
85
|
+
const here = agent.custom.filter((c) => renderers?.custom?.[c.name] && (c.after ?? firstKey) === key);
|
|
86
|
+
if (here.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
return here.map((c) => {
|
|
89
|
+
const Renderer = renderers.custom[c.name];
|
|
90
|
+
return (_jsx("div", { "data-slot": "agent-custom", className: "min-w-0", children: _jsx(Renderer, { name: c.name, value: c.value, history: c.history, agent: agent }) }, c.key));
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
// Interrupts that no tool card or artifact answers: drawn after the conversation.
|
|
94
|
+
const claimed = new Set(agent.custom.flatMap((c) => {
|
|
95
|
+
const id = c.value?.interruptId;
|
|
96
|
+
return renderers?.custom?.[c.name] && typeof id === "string" ? [id] : [];
|
|
97
|
+
}));
|
|
98
|
+
const loose = view.status === "interrupted"
|
|
99
|
+
? view.interrupts.filter((i) => !i.toolCallId && !claimed.has(i.id))
|
|
100
|
+
: [];
|
|
101
|
+
const footer = loose.length > 0
|
|
102
|
+
? loose.map((interrupt) => {
|
|
103
|
+
const Renderer = renderers?.interrupts?.[interrupt.reason] ?? InterruptCard;
|
|
104
|
+
return (_jsx(Renderer, { interrupt: interrupt, agent: agent, resolve: (payload) => agent.resolveInterrupt(interrupt.id, {
|
|
105
|
+
status: "resolved",
|
|
106
|
+
...(payload === undefined ? {} : { payload }),
|
|
107
|
+
}), cancel: () => agent.resolveInterrupt(interrupt.id, { status: "cancelled" }) }, interrupt.id));
|
|
108
|
+
})
|
|
109
|
+
: undefined;
|
|
110
|
+
const StateView = renderers?.state;
|
|
111
|
+
const step = [...view.steps].reverse().find((s) => s.status === "running");
|
|
112
|
+
return (_jsxs("section", { ref: root, "aria-labelledby": hideHeader ? undefined : headingId, "aria-label": hideHeader ? name : undefined, "data-slot": "agent-chat", className: cn("flex min-h-0 min-w-0 flex-col bg-card text-card-foreground", className), children: [!hideHeader && (_jsxs("div", { className: "flex min-w-0 items-center gap-1 border-b px-3 py-2", children: [_jsx("h2", { id: headingId, className: "min-w-0 flex-1 truncate text-sm font-semibold", children: name }), headerActions, !hideNewConversation && _jsx(NewConversationButton, {}), onExpandedChange && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": expanded ? t.restore : t.expand, title: expanded ? t.restore : t.expand, onClick: () => onExpandedChange(!expanded), children: expanded ? _jsx(Minimize2Icon, { "aria-hidden": true }) : _jsx(Maximize2Icon, { "aria-hidden": true }) })), onClose && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": t.close, title: t.close, onClick: onClose, children: _jsx(XIcon, { "aria-hidden": true }) }))] })), progress === "steps" && view.steps.length > 0 && (_jsx(AgentProgress, { view: view, className: "border-b px-3 py-2" })), StateView && view.state !== undefined && (_jsx("div", { role: "region", "aria-label": t.sharedState, "data-slot": "agent-state",
|
|
113
|
+
// A scrolling region is reached by keyboard (WCAG 2.1.1).
|
|
114
|
+
tabIndex: 0, className: "max-h-48 min-w-0 shrink-0 overflow-y-auto border-b px-3 py-2", children: _jsx(StateView, { state: view.state, setState: agent.setState, agent: agent }) })), _jsx(MessageList, { view: view, renderContent: renderContent, renderToolCall: drawCall, renderAfter: drawAfter, footer: footer, toolTitle: toolTitle, onApprove: (call) => agent.approve(call), onDeny: (call, reason) => agent.deny(call, reason), onRetry: () => void agent.regenerate(), empty: _jsxs("div", { className: "flex flex-col items-center gap-4 py-6", children: [empty ?? _jsx(EmptyState, { title: t.emptyTitle, description: t.emptyDescription }), _jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })] }), className: "min-h-0 flex-1" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [suggestionsAfterRun && !fresh && !running && (_jsx(Suggestions, { suggestions: suggestions, disabled: running, onPick: pick })), _jsx("p", { role: "status", "data-slot": "agent-status", className: cn("flex min-w-0 items-center gap-2 text-xs text-muted-foreground", !(progress === "line" && running) && !blocked && "sr-only"), children: blocked ? (t.waitingOnYou) : running ? (_jsxs(_Fragment, { children: [_jsx(StatusDot, { tone: "info", pulse: true }), _jsx("span", { className: "truncate", children: step ? step.name : messages.chat.runStatus("running") })] })) : null }), _jsx(Composer, { onSubmit: send, running: running, onStop: agent.stop, disabled: blocked, placeholder: placeholder,
|
|
115
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus -- the person just opened the assistant to type
|
|
116
|
+
autoFocus: autoFocus, attachments: attachments, actions: modelPicker || composerActions ? (_jsxs(_Fragment, { children: [modelPicker && _jsx(ModelPicker, { ...modelPicker }), composerActions] })) : undefined })] })] }));
|
|
117
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface AgentLauncherProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
/** The assistant's name, for the button's label. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Answers that arrived while closed: a badge, and part of the name. */
|
|
8
|
+
unread?: number;
|
|
9
|
+
/** The id of the panel it opens. */
|
|
10
|
+
controls?: string;
|
|
11
|
+
/** Shown in the tooltip and announced ("mod+/"). */
|
|
12
|
+
shortcut?: string | false;
|
|
13
|
+
/** The mark on the button. Defaults to the product's logo mark, then a sparkle. */
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
/** "floating": the round button at the page's corner. "inline": a header button. */
|
|
16
|
+
variant?: "floating" | "inline";
|
|
17
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The button that opens the agent: floating at the page's corner (the
|
|
22
|
+
* bubble) or in a header (beside a rail). Carries the product's mark, an
|
|
23
|
+
* unread badge whose count is part of its name, and its shortcut.
|
|
24
|
+
*/
|
|
25
|
+
export declare function AgentLauncher({ open, onOpenChange, title, unread, controls, shortcut, icon, variant, ref, className, }: AgentLauncherProps): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { SparklesIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useProduct } from "../appearance/AppearanceProvider.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
import { shortcutAria, shortcutLabel } from "./surface.js";
|
|
9
|
+
/**
|
|
10
|
+
* The button that opens the agent: floating at the page's corner (the
|
|
11
|
+
* bubble) or in a header (beside a rail). Carries the product's mark, an
|
|
12
|
+
* unread badge whose count is part of its name, and its shortcut.
|
|
13
|
+
*/
|
|
14
|
+
export function AgentLauncher({ open, onOpenChange, title, unread = 0, controls, shortcut, icon, variant = "floating", ref, className, }) {
|
|
15
|
+
const { messages } = useUI();
|
|
16
|
+
const product = useProduct();
|
|
17
|
+
const t = messages.agent;
|
|
18
|
+
const mark = icon ??
|
|
19
|
+
(product?.logo?.mark ? (_jsx("img", { src: product.logo.mark, alt: "", className: "size-5 rounded-sm object-contain" })) : (_jsx(SparklesIcon, { "aria-hidden": true, className: "size-5" })));
|
|
20
|
+
const label = open ? t.close : t.open(title);
|
|
21
|
+
const hint = shortcut ? `${label} (${shortcutLabel(shortcut)})` : label;
|
|
22
|
+
return (_jsxs("button", { ref: ref, type: "button", "data-slot": "agent-launcher", "aria-expanded": open, "aria-controls": controls, "aria-keyshortcuts": shortcut ? shortcutAria(shortcut) : undefined, title: hint, onClick: () => onOpenChange(!open), className: cn("relative inline-flex shrink-0 items-center justify-center outline-none focus-visible:ring-3 focus-visible:ring-ring/50", variant === "floating"
|
|
23
|
+
? "size-12 rounded-full bg-primary text-primary-foreground shadow-lg transition-transform hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100"
|
|
24
|
+
: "h-8 gap-1.5 rounded-lg border border-border bg-background px-2.5 text-sm font-medium hover:bg-muted", className), children: [mark, variant === "inline" && _jsx("span", { children: title }), _jsxs("span", { className: "sr-only", children: [variant === "floating" ? label : "", unread > 0 ? `, ${t.unread(unread)}` : ""] }), unread > 0 && (_jsx("span", { "aria-hidden": true, "data-slot": "agent-unread", className: "absolute -top-1 -right-1 inline-flex min-w-5 items-center justify-center rounded-full border border-danger-border bg-danger-bg px-1 text-2xs font-semibold text-danger-fg", children: unread > 9 ? "9+" : unread }))] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type AbstractAgent } from "@ag-ui/client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import type { ChatToolCall } from "../chat/model.js";
|
|
4
|
+
import { AgentController, type AgentContextEntry, type AgentControllerOptions, type AgentSnapshot, type AgentSuggestion, type SendOptions } from "./controller.js";
|
|
5
|
+
import { type AgentEngine, type ToolInvoker } from "./engine.js";
|
|
6
|
+
import { type AgentTool } from "./tool.js";
|
|
7
|
+
/** What every agent provider takes, whatever runs the agent. */
|
|
8
|
+
export interface CalliopeAgentOptions extends AgentControllerOptions {
|
|
9
|
+
/** Tools for the whole app; a page adds its own with `useAgentTool`. */
|
|
10
|
+
tools?: readonly AgentTool<any>[];
|
|
11
|
+
/** Readables for the whole app; a page adds its own with `useAgentReadable`. */
|
|
12
|
+
context?: readonly AgentContextEntry[];
|
|
13
|
+
/** Prompts offered as chips; a page adds its own with `useAgentSuggestions`. */
|
|
14
|
+
suggestions?: readonly AgentSuggestion[];
|
|
15
|
+
}
|
|
16
|
+
export interface CalliopeAgentProviderProps extends Omit<CalliopeAgentOptions, "forwardedProps"> {
|
|
17
|
+
/** The AG-UI agent: an `HttpAgent`, any `AbstractAgent`, or `createMockAgent` in a story. */
|
|
18
|
+
agent?: AbstractAgent;
|
|
19
|
+
/** Or a plain AG-UI endpoint: an `HttpAgent` is made for it. Same origin, so the session cookie goes along. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** Request headers for `url` (a project id). Never a secret: this runs in the browser. */
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
/** The conversation to start in; `switchThread` moves later. */
|
|
24
|
+
threadId?: string;
|
|
25
|
+
/** Sent with every run (the model picked, the page). */
|
|
26
|
+
forwardedProps?: Record<string, unknown>;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/** The agent as a page reads it: the conversation and what the person can do. */
|
|
30
|
+
export interface CalliopeAgentApi extends AgentSnapshot {
|
|
31
|
+
send: (text: string, options?: SendOptions) => Promise<void>;
|
|
32
|
+
stop: () => void;
|
|
33
|
+
regenerate: () => Promise<void>;
|
|
34
|
+
approve: (call: ChatToolCall, payload?: Record<string, unknown>) => void;
|
|
35
|
+
deny: (call: ChatToolCall, reason: string) => void;
|
|
36
|
+
resolveInterrupt: AgentController["resolveInterrupt"];
|
|
37
|
+
respond: (toolCallId: string, result: unknown) => void;
|
|
38
|
+
getState: <T = unknown>() => T;
|
|
39
|
+
setState: (state: unknown) => void;
|
|
40
|
+
registerTool: AgentController["registerTool"];
|
|
41
|
+
addContext: (entry: AgentContextEntry) => () => void;
|
|
42
|
+
switchThread: (threadId: string) => Promise<void>;
|
|
43
|
+
newThread: () => Promise<string>;
|
|
44
|
+
setVisible: (visible: boolean) => void;
|
|
45
|
+
agent: AbstractAgent;
|
|
46
|
+
controller: AgentController;
|
|
47
|
+
}
|
|
48
|
+
/** The controller of the nearest agent provider, or null outside one. */
|
|
49
|
+
export declare function useAgentController(): AgentController | null;
|
|
50
|
+
/**
|
|
51
|
+
* Hosts a controller for any engine; the providers below and the CopilotKit
|
|
52
|
+
* one are this with their engine. Registers the option-level tools, context
|
|
53
|
+
* and suggestions, and connects after mount.
|
|
54
|
+
*/
|
|
55
|
+
export declare function AgentHost({ createEngine, engineKey, options, children, }: {
|
|
56
|
+
createEngine: (invoke: ToolInvoker) => AgentEngine;
|
|
57
|
+
/** A new key swaps the engine (a new agent). */
|
|
58
|
+
engineKey: unknown;
|
|
59
|
+
options: CalliopeAgentOptions;
|
|
60
|
+
children: React.ReactNode;
|
|
61
|
+
}): React.JSX.Element;
|
|
62
|
+
/**
|
|
63
|
+
* The agent for a page tree, over a plain AG-UI endpoint (`url`) or any
|
|
64
|
+
* AG-UI agent (`agent`). Headless: it renders nothing of its own; put
|
|
65
|
+
* `CalliopeAgent` (a bubble, a rail, inline or a page) or the chat kit's
|
|
66
|
+
* components under it, and read it with `useCalliopeAgent`. For a CopilotKit
|
|
67
|
+
* runtime use `CopilotKitAgentProvider` from `@calliopelabs/ui/agent/copilotkit`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function CalliopeAgentProvider({ agent: given, url, headers, threadId, forwardedProps, children, ...options }: CalliopeAgentProviderProps): React.JSX.Element;
|
|
70
|
+
/** The nearest agent: its conversation, state and actions. Re-renders on every change. */
|
|
71
|
+
export declare function useCalliopeAgent(): CalliopeAgentApi;
|
|
72
|
+
/**
|
|
73
|
+
* Offers a tool to the agent while the component is mounted (a whiteboard's
|
|
74
|
+
* tools only while it is open). The handler and renderer may change every
|
|
75
|
+
* render; the tool re-registers only when its name, description, schema,
|
|
76
|
+
* availability or follow-up change.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useAgentTool<Args>(tool: AgentTool<Args>): void;
|
|
79
|
+
/** Tells the agent about the page while mounted: a readable. Re-sent when the value's JSON changes. */
|
|
80
|
+
export declare function useAgentReadable(description: string, value: unknown): void;
|
|
81
|
+
/** Offers prompts as chips while mounted (a page's starting points). */
|
|
82
|
+
export declare function useAgentSuggestions(suggestions: readonly AgentSuggestion[]): void;
|