rift-ds 1.6.0 → 1.8.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 +34 -13
- package/bin/rift-ds.mjs +231 -44
- package/bin/templates/chat/.env.chat.example +21 -0
- package/bin/templates/chat/app/api/chat/feedback/route.ts +12 -0
- package/bin/templates/chat/app/api/chat/followups/route.ts +23 -0
- package/bin/templates/chat/app/api/chat/guardrails.ts +18 -0
- package/bin/templates/chat/app/api/chat/route.ts +31 -0
- package/bin/templates/chat/components/chat/ChatMount.tsx +52 -0
- package/bin/templates/chat/components/chat/models.ts +29 -0
- package/chat/AssistantTurn.d.ts +5 -0
- package/chat/AssistantTurn.js +82 -0
- package/chat/ChatAgentPane.css +56 -0
- package/chat/ChatAgentPane.d.ts +21 -0
- package/chat/ChatAgentPane.js +25 -0
- package/chat/ChatLauncher.d.ts +64 -0
- package/chat/ChatLauncher.js +347 -0
- package/chat/ChatProvider.d.ts +53 -0
- package/chat/ChatProvider.js +181 -0
- package/chat/ChatTabs.d.ts +13 -0
- package/chat/ChatTabs.js +24 -0
- package/chat/ChatThreads.d.ts +26 -0
- package/chat/ChatThreads.js +55 -0
- package/chat/ChatWidget.css +1072 -0
- package/chat/ChatWidget.d.ts +113 -0
- package/chat/ChatWidget.js +495 -0
- package/chat/DemoWelcome.d.ts +13 -0
- package/chat/DemoWelcome.js +131 -0
- package/chat/FollowupSuggestions.d.ts +23 -0
- package/chat/FollowupSuggestions.js +34 -0
- package/chat/ResponseActions.d.ts +18 -0
- package/chat/ResponseActions.js +79 -0
- package/chat/TurnContent.d.ts +9 -0
- package/chat/TurnContent.js +12 -0
- package/chat/context.d.ts +65 -0
- package/chat/context.js +27 -0
- package/chat/demo-agent-panes.d.ts +5 -0
- package/chat/demo-agent-panes.js +49 -0
- package/chat/demo-agent.d.ts +3 -0
- package/chat/demo-agent.js +28 -0
- package/chat/demo-card-components.d.ts +46 -0
- package/chat/demo-card-components.js +59 -0
- package/chat/demo-cards.d.ts +32 -0
- package/chat/demo-cards.js +59 -0
- package/chat/demo-fixtures.d.ts +20 -0
- package/chat/demo-fixtures.js +113 -0
- package/chat/demo-script.d.ts +6 -0
- package/chat/demo-script.js +215 -0
- package/chat/demo.css +46 -0
- package/chat/demo.d.ts +9 -0
- package/chat/demo.js +24 -0
- package/chat/followups.d.ts +41 -0
- package/chat/followups.js +55 -0
- package/chat/greeting.d.ts +11 -0
- package/chat/greeting.js +21 -0
- package/chat/index.d.ts +34 -0
- package/chat/index.js +80 -0
- package/chat/placement.d.ts +48 -0
- package/chat/placement.js +39 -0
- package/chat/queries.d.ts +9 -0
- package/chat/queries.js +22 -0
- package/chat/registry.d.ts +25 -0
- package/chat/registry.js +9 -0
- package/chat/registry.json.d.ts +69 -0
- package/chat/registry.json.js +11 -0
- package/chat/server.d.ts +217 -0
- package/chat/server.js +488 -0
- package/chat/sim.d.ts +71 -0
- package/chat/sim.js +125 -0
- package/chat/starters.d.ts +15 -0
- package/chat/starters.js +38 -0
- package/chat/suggestions.d.ts +22 -0
- package/chat/suggestions.js +9 -0
- package/chat/threads.d.ts +73 -0
- package/chat/threads.js +270 -0
- package/chat/transport.d.ts +49 -0
- package/chat/transport.js +105 -0
- package/chat/types.d.ts +210 -0
- package/chat/types.js +16 -0
- package/chat/upstash.d.ts +63 -0
- package/chat/upstash.js +147 -0
- package/chat/useChat.d.ts +64 -0
- package/chat/useChat.js +241 -0
- package/cli/templates/chat/app/api/chat/feedback/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/followups/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/guardrails.d.ts +10 -0
- package/cli/templates/chat/app/api/chat/route.d.ts +4 -0
- package/cli/templates/chat/components/chat/ChatMount.d.ts +1 -0
- package/cli/templates/chat/components/chat/models.d.ts +11 -0
- package/components/AgentRail/AgentRail.css +2 -2
- package/components/Attachment/Attachment.css +14 -0
- package/components/Attachment/AttachmentGroup.d.ts +2 -2
- package/components/Attachment/AttachmentGroup.js +7 -2
- package/components/Composer/Composer.css +88 -4
- package/components/Composer/Composer.d.ts +21 -0
- package/components/Composer/Composer.js +199 -14
- package/components/Mention/Mention.css +195 -0
- package/components/Mention/Mention.d.ts +68 -0
- package/components/Mention/Mention.js +145 -0
- package/components/Mention/mentions.d.ts +81 -0
- package/components/Mention/mentions.js +83 -0
- package/components/ShaderField/ShaderField.d.ts +6 -5
- package/components/ShaderField/useShaderField.d.ts +2 -1
- package/components/ShaderField/useShaderField.js +6 -6
- package/components/StatusDot/StatusDot.css +36 -13
- package/components/StatusDot/StatusDot.d.ts +16 -4
- package/components/StatusDot/StatusDot.js +25 -5
- package/components/ThreadPanel/ThreadPanel.css +20 -11
- package/components/ThreadPanel/ThreadPanel.d.ts +15 -3
- package/components/ThreadPanel/ThreadPanel.js +30 -6
- package/components/ThreadTabs/ThreadTabs.css +18 -8
- package/components/ThreadTabs/ThreadTabs.d.ts +8 -2
- package/components/ThreadTabs/ThreadTabs.js +24 -5
- package/components/registry.json +9 -1
- package/components/registry.json.d.ts +9 -1
- package/components/registry.json.js +1 -1
- package/fonts/material-symbols.css +2 -1
- package/index.d.ts +1 -0
- package/index.js +11 -0
- package/package.json +43 -2
- package/tokens/presets/coral.css +18 -2
- package/tokens/presets/forest.css +18 -2
- package/tokens/presets/gold.css +18 -2
- package/tokens/presets/mono.css +18 -2
- package/tokens/presets/pink.css +18 -2
- package/tokens/presets/terminal.css +18 -2
- package/tokens/presets/violet.css +18 -2
- package/tokens/presets/warm.css +18 -2
- package/tokens/presets/zest.css +18 -2
- package/tokens/registry.json +6 -0
- package/tokens/registry.json.d.ts +6 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +10 -0
- package/tokens/tokens-light.css +10 -0
package/chat/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rift-ds/chat: the composed chat widget and the engine under it.
|
|
3
|
+
*
|
|
4
|
+
* The widget: ChatProvider (the state), ChatWidget (the surface), ChatLauncher
|
|
5
|
+
* (the floating button and the docked / overlay / floating / takeover panel).
|
|
6
|
+
* The engine, importable on its own under your own markup: useChat, the
|
|
7
|
+
* transports, useThreads, and the event contract. The server half lives in
|
|
8
|
+
* rift-ds/chat/server; a scripted conversation for previews in
|
|
9
|
+
* rift-ds/chat/demo.
|
|
10
|
+
*
|
|
11
|
+
* Hand-written, unlike the component barrels: the registry generator never
|
|
12
|
+
* reaches src/chat, and this file is the one place the widget's public
|
|
13
|
+
* surface is declared (src/chat/registry.json names the same exports for the
|
|
14
|
+
* documentation, and validate-chat-widget.mjs holds the two together).
|
|
15
|
+
*/
|
|
16
|
+
export { ChatProvider, type ChatProviderProps } from './ChatProvider';
|
|
17
|
+
export { ChatWidget, type ChatWidgetProps, type ChatAgentOptions, type RailSlot } from './ChatWidget';
|
|
18
|
+
export { ChatLauncher, CHAT_PANEL_ID, type ChatLauncherProps } from './ChatLauncher';
|
|
19
|
+
export { ChatThreads, type ChatThreadsProps } from './ChatThreads';
|
|
20
|
+
export { ChatTabs, type ChatTabsProps } from './ChatTabs';
|
|
21
|
+
export { ChatAgentPane, type ChatAgentPaneProps } from './ChatAgentPane';
|
|
22
|
+
export { useChatWidget, isInternalPath, createFeedbackPoster, type ChatView, type ChatCardRenderers, type ChatLinkRenderer, type ChatWidgetContextValue, type FeedbackSink, } from './context';
|
|
23
|
+
export { DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH, useTakeoverViewport, } from './queries';
|
|
24
|
+
export { useChat, DEFAULT_PLACEHOLDER_COPY, type ChatState, type ChatPlaceholderCopy, } from './useChat';
|
|
25
|
+
export { useThreads, THREAD_NAMING_DELAY_MS, type ThreadsOptions, type ThreadsState } from './threads';
|
|
26
|
+
export { createFetchTransport, DEFAULT_FETCH_COPY, FIRST_BYTE_TIMEOUT_MS, MAX_TRANSPORT_TURNS, type FetchTransportOptions, type FetchTransportCopy, } from './transport';
|
|
27
|
+
export { scriptChat, createSimTransport, defaultFilesReply, DEFAULT_FALLBACK_REPLY, DEFAULT_SIM_TIMING, type ChatScript, type ChatScriptBuilder, type ScriptNode, type ScriptStep, type ScriptedReply, type SimTiming, } from './sim';
|
|
28
|
+
export { createFollowupsFetcher, followupTarget, pickFallbackFollowups, DEFAULT_FALLBACK_FOLLOWUPS, MAX_FOLLOWUPS, FOLLOWUPS_TIMEOUT_MS, type FollowupsSource, } from './followups';
|
|
29
|
+
export { SUGGESTION_MAX_CHARS, fitsChip } from './suggestions';
|
|
30
|
+
export { STARTER_COUNT, pickStarters, namedStarter, hashString, mulberry32, } from './starters';
|
|
31
|
+
export { greetingFor, readGreeting, serverGreeting, subscribeClock, DEFAULT_GREETING_COPY, type GreetingCopy, } from './greeting';
|
|
32
|
+
export { CHAT_PLACEMENTS, chatPlacement, DOCK_MIN_WIDTH, DOCK_MAX_WIDTH, FLOAT_MAX_SCALE, FLOAT_EDGE_GAP, dockWidthFromDrag, isFloatDragStart, clampFloatPosition, type ChatPlacement, type ChatSide, } from './placement';
|
|
33
|
+
export { CHAT_EVENT_KINDS, type ChatCard, type ChatEvent, type ChatTransport, type ChatTransportMessage, type ChatTurn, type ChatModelOption, type ChatFeedbackVerdict, type LivePhase, type LiveResponse, type Starter, } from './types';
|
|
34
|
+
export { chatWidget, chatWidgetRegistry, WIDGET_COUNT, type ChatWidgetMeta } from './registry';
|
package/chat/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ChatProvider } from "./ChatProvider.js";
|
|
2
|
+
import { ChatWidget } from "./ChatWidget.js";
|
|
3
|
+
import { CHAT_PANEL_ID, ChatLauncher } from "./ChatLauncher.js";
|
|
4
|
+
import { ChatThreads } from "./ChatThreads.js";
|
|
5
|
+
import { ChatTabs } from "./ChatTabs.js";
|
|
6
|
+
import { ChatAgentPane } from "./ChatAgentPane.js";
|
|
7
|
+
import { createFeedbackPoster, isInternalPath, useChatWidget } from "./context.js";
|
|
8
|
+
import { ASIDE_RAIL_MIN_WIDTH, DOCK_QUERY, TAKEOVER_QUERY, THREADS_RAIL_MIN_WIDTH, useTakeoverViewport } from "./queries.js";
|
|
9
|
+
import { DEFAULT_PLACEHOLDER_COPY, useChat } from "./useChat.js";
|
|
10
|
+
import { THREAD_NAMING_DELAY_MS, useThreads } from "./threads.js";
|
|
11
|
+
import { DEFAULT_FETCH_COPY, FIRST_BYTE_TIMEOUT_MS, MAX_TRANSPORT_TURNS, createFetchTransport } from "./transport.js";
|
|
12
|
+
import { DEFAULT_FALLBACK_REPLY, DEFAULT_SIM_TIMING, createSimTransport, defaultFilesReply, scriptChat } from "./sim.js";
|
|
13
|
+
import { DEFAULT_FALLBACK_FOLLOWUPS, FOLLOWUPS_TIMEOUT_MS, MAX_FOLLOWUPS, createFollowupsFetcher, followupTarget, pickFallbackFollowups } from "./followups.js";
|
|
14
|
+
import { SUGGESTION_MAX_CHARS, fitsChip } from "./suggestions.js";
|
|
15
|
+
import { STARTER_COUNT, hashString, mulberry32, namedStarter, pickStarters } from "./starters.js";
|
|
16
|
+
import { DEFAULT_GREETING_COPY, greetingFor, readGreeting, serverGreeting, subscribeClock } from "./greeting.js";
|
|
17
|
+
import { CHAT_PLACEMENTS, DOCK_MAX_WIDTH, DOCK_MIN_WIDTH, FLOAT_EDGE_GAP, FLOAT_MAX_SCALE, chatPlacement, clampFloatPosition, dockWidthFromDrag, isFloatDragStart } from "./placement.js";
|
|
18
|
+
import { CHAT_EVENT_KINDS } from "./types.js";
|
|
19
|
+
import { WIDGET_COUNT, chatWidget, chatWidgetRegistry } from "./registry.js";
|
|
20
|
+
export {
|
|
21
|
+
ASIDE_RAIL_MIN_WIDTH,
|
|
22
|
+
CHAT_EVENT_KINDS,
|
|
23
|
+
CHAT_PANEL_ID,
|
|
24
|
+
CHAT_PLACEMENTS,
|
|
25
|
+
ChatAgentPane,
|
|
26
|
+
ChatLauncher,
|
|
27
|
+
ChatProvider,
|
|
28
|
+
ChatTabs,
|
|
29
|
+
ChatThreads,
|
|
30
|
+
ChatWidget,
|
|
31
|
+
DEFAULT_FALLBACK_FOLLOWUPS,
|
|
32
|
+
DEFAULT_FALLBACK_REPLY,
|
|
33
|
+
DEFAULT_FETCH_COPY,
|
|
34
|
+
DEFAULT_GREETING_COPY,
|
|
35
|
+
DEFAULT_PLACEHOLDER_COPY,
|
|
36
|
+
DEFAULT_SIM_TIMING,
|
|
37
|
+
DOCK_MAX_WIDTH,
|
|
38
|
+
DOCK_MIN_WIDTH,
|
|
39
|
+
DOCK_QUERY,
|
|
40
|
+
FIRST_BYTE_TIMEOUT_MS,
|
|
41
|
+
FLOAT_EDGE_GAP,
|
|
42
|
+
FLOAT_MAX_SCALE,
|
|
43
|
+
FOLLOWUPS_TIMEOUT_MS,
|
|
44
|
+
MAX_FOLLOWUPS,
|
|
45
|
+
MAX_TRANSPORT_TURNS,
|
|
46
|
+
STARTER_COUNT,
|
|
47
|
+
SUGGESTION_MAX_CHARS,
|
|
48
|
+
TAKEOVER_QUERY,
|
|
49
|
+
THREADS_RAIL_MIN_WIDTH,
|
|
50
|
+
THREAD_NAMING_DELAY_MS,
|
|
51
|
+
WIDGET_COUNT,
|
|
52
|
+
chatPlacement,
|
|
53
|
+
chatWidget,
|
|
54
|
+
chatWidgetRegistry,
|
|
55
|
+
clampFloatPosition,
|
|
56
|
+
createFeedbackPoster,
|
|
57
|
+
createFetchTransport,
|
|
58
|
+
createFollowupsFetcher,
|
|
59
|
+
createSimTransport,
|
|
60
|
+
defaultFilesReply,
|
|
61
|
+
dockWidthFromDrag,
|
|
62
|
+
fitsChip,
|
|
63
|
+
followupTarget,
|
|
64
|
+
greetingFor,
|
|
65
|
+
hashString,
|
|
66
|
+
isFloatDragStart,
|
|
67
|
+
isInternalPath,
|
|
68
|
+
mulberry32,
|
|
69
|
+
namedStarter,
|
|
70
|
+
pickFallbackFollowups,
|
|
71
|
+
pickStarters,
|
|
72
|
+
readGreeting,
|
|
73
|
+
scriptChat,
|
|
74
|
+
serverGreeting,
|
|
75
|
+
subscribeClock,
|
|
76
|
+
useChat,
|
|
77
|
+
useChatWidget,
|
|
78
|
+
useTakeoverViewport,
|
|
79
|
+
useThreads
|
|
80
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a chat panel sits: docked to either edge, or popped out as a
|
|
3
|
+
* floating card the visitor moves and sizes. One roster, so the header menu
|
|
4
|
+
* (ChatWidget), the launcher and any host stage all name the same three
|
|
5
|
+
* seats the same way. design.md's ChatWidget spec owns the behaviour each
|
|
6
|
+
* seat promises.
|
|
7
|
+
*
|
|
8
|
+
* No React and no component imports, like the dock query's home in
|
|
9
|
+
* ChatProvider: hosts deep in the tree read it without pulling the widget.
|
|
10
|
+
*/
|
|
11
|
+
/** The edge a docked panel takes, and the edge it overlays from below the dock threshold. */
|
|
12
|
+
export type ChatSide = 'left' | 'right';
|
|
13
|
+
/** A panel's seat: a dock edge, or floating free of both. */
|
|
14
|
+
export type ChatPlacement = ChatSide | 'floating';
|
|
15
|
+
export declare const CHAT_PLACEMENTS: readonly {
|
|
16
|
+
id: ChatPlacement;
|
|
17
|
+
label: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
}[];
|
|
20
|
+
/** The roster entry for a placement: the header trigger wears its icon. */
|
|
21
|
+
export declare const chatPlacement: (id: ChatPlacement) => {
|
|
22
|
+
id: ChatPlacement;
|
|
23
|
+
label: string;
|
|
24
|
+
icon: string;
|
|
25
|
+
};
|
|
26
|
+
export declare const DOCK_MIN_WIDTH = 420;
|
|
27
|
+
export declare const DOCK_MAX_WIDTH = 655;
|
|
28
|
+
/** A docked panel's width after a grip drag: the inner edge is the one
|
|
29
|
+
that moves, and it faces the other way on the left dock. */
|
|
30
|
+
export declare function dockWidthFromDrag(startWidth: number, pointerDelta: number, side: ChatSide): number;
|
|
31
|
+
export declare const FLOAT_MAX_SCALE = 1.2;
|
|
32
|
+
export declare const FLOAT_EDGE_GAP = 8;
|
|
33
|
+
/** Whether a pointerdown on `target` should start moving a floating card. */
|
|
34
|
+
export declare function isFloatDragStart(target: EventTarget | null): boolean;
|
|
35
|
+
/** Holds a moved card inside `bounds`, FLOAT_EDGE_GAP off each edge. A card
|
|
36
|
+
larger than the bounds pins to the leading edge rather than oscillating. */
|
|
37
|
+
export declare function clampFloatPosition(x: number, y: number, size: {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}, bounds: {
|
|
41
|
+
left: number;
|
|
42
|
+
top: number;
|
|
43
|
+
right: number;
|
|
44
|
+
bottom: number;
|
|
45
|
+
}): {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const CHAT_PLACEMENTS = [
|
|
2
|
+
{ id: "right", label: "Dock right", icon: "dock_to_left" },
|
|
3
|
+
{ id: "left", label: "Dock left", icon: "dock_to_right" },
|
|
4
|
+
{ id: "floating", label: "Floating", icon: "picture_in_picture" }
|
|
5
|
+
];
|
|
6
|
+
const chatPlacement = (id) => CHAT_PLACEMENTS.find((placement) => placement.id === id) ?? CHAT_PLACEMENTS[0];
|
|
7
|
+
const DOCK_MIN_WIDTH = 420;
|
|
8
|
+
const DOCK_MAX_WIDTH = 655;
|
|
9
|
+
function dockWidthFromDrag(startWidth, pointerDelta, side) {
|
|
10
|
+
const grown = startWidth + (side === "left" ? pointerDelta : -pointerDelta);
|
|
11
|
+
return Math.round(Math.min(DOCK_MAX_WIDTH, Math.max(DOCK_MIN_WIDTH, grown)));
|
|
12
|
+
}
|
|
13
|
+
const FLOAT_MAX_SCALE = 1.2;
|
|
14
|
+
const FLOAT_EDGE_GAP = 8;
|
|
15
|
+
const FLOAT_DRAG_ZONE = ".ds-chat-header";
|
|
16
|
+
const FLOAT_DRAG_EXEMPT = "button, a, input, textarea, select, [role='button'], [role='menu']";
|
|
17
|
+
function isFloatDragStart(target) {
|
|
18
|
+
if (!(target instanceof Element)) return false;
|
|
19
|
+
return Boolean(target.closest(FLOAT_DRAG_ZONE)) && !target.closest(FLOAT_DRAG_EXEMPT);
|
|
20
|
+
}
|
|
21
|
+
function clampFloatPosition(x, y, size, bounds) {
|
|
22
|
+
const minX = bounds.left + FLOAT_EDGE_GAP;
|
|
23
|
+
const minY = bounds.top + FLOAT_EDGE_GAP;
|
|
24
|
+
return {
|
|
25
|
+
x: Math.round(Math.min(Math.max(minX, bounds.right - size.width - FLOAT_EDGE_GAP), Math.max(minX, x))),
|
|
26
|
+
y: Math.round(Math.min(Math.max(minY, bounds.bottom - size.height - FLOAT_EDGE_GAP), Math.max(minY, y)))
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
CHAT_PLACEMENTS,
|
|
31
|
+
DOCK_MAX_WIDTH,
|
|
32
|
+
DOCK_MIN_WIDTH,
|
|
33
|
+
FLOAT_EDGE_GAP,
|
|
34
|
+
FLOAT_MAX_SCALE,
|
|
35
|
+
chatPlacement,
|
|
36
|
+
clampFloatPosition,
|
|
37
|
+
dockWidthFromDrag,
|
|
38
|
+
isFloatDragStart
|
|
39
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DOCK_QUERY = "(min-width: 1440px)";
|
|
2
|
+
export declare const TAKEOVER_QUERY = "(max-width: 719px)";
|
|
3
|
+
/** The widget's own width thresholds for seating its rails inline. */
|
|
4
|
+
export declare const THREADS_RAIL_MIN_WIDTH = 720;
|
|
5
|
+
export declare const ASIDE_RAIL_MIN_WIDTH = 860;
|
|
6
|
+
/** Whether the viewport is a phone width (TAKEOVER_QUERY), false on the
|
|
7
|
+
server and for the first client render. Shared by the launcher and any
|
|
8
|
+
host stage, which both hand the widget its phone layout. */
|
|
9
|
+
export declare function useTakeoverViewport(): boolean;
|
package/chat/queries.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
const DOCK_QUERY = "(min-width: 1440px)";
|
|
4
|
+
const TAKEOVER_QUERY = "(max-width: 719px)";
|
|
5
|
+
const THREADS_RAIL_MIN_WIDTH = 720;
|
|
6
|
+
const ASIDE_RAIL_MIN_WIDTH = 860;
|
|
7
|
+
const subscribeTakeover = (onChange) => {
|
|
8
|
+
const media = window.matchMedia(TAKEOVER_QUERY);
|
|
9
|
+
media.addEventListener("change", onChange);
|
|
10
|
+
return () => media.removeEventListener("change", onChange);
|
|
11
|
+
};
|
|
12
|
+
const readTakeover = () => window.matchMedia(TAKEOVER_QUERY).matches;
|
|
13
|
+
function useTakeoverViewport() {
|
|
14
|
+
return useSyncExternalStore(subscribeTakeover, readTakeover, () => false);
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
ASIDE_RAIL_MIN_WIDTH,
|
|
18
|
+
DOCK_QUERY,
|
|
19
|
+
TAKEOVER_QUERY,
|
|
20
|
+
THREADS_RAIL_MIN_WIDTH,
|
|
21
|
+
useTakeoverViewport
|
|
22
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** One widget's metadata, as the registry records it. */
|
|
2
|
+
export interface ChatWidgetMeta {
|
|
3
|
+
name: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
/** The component category whose /components section and sidebar group the widget's page joins. */
|
|
8
|
+
category: string;
|
|
9
|
+
/** The init bin's subcommand that scaffolds it. */
|
|
10
|
+
command: string;
|
|
11
|
+
/** Each package subpath the widget occupies, with what it holds. */
|
|
12
|
+
subpaths: Record<string, string>;
|
|
13
|
+
/** The optional peer dependencies each subpath needs. */
|
|
14
|
+
peers: Record<string, string[]>;
|
|
15
|
+
/** The library components the widget composes. */
|
|
16
|
+
parts: string[];
|
|
17
|
+
/** The exported components whose props are documented through docgen. */
|
|
18
|
+
documented: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare const chatWidgetRegistry: {
|
|
21
|
+
widgets: ChatWidgetMeta[];
|
|
22
|
+
};
|
|
23
|
+
/** The one widget, by design. */
|
|
24
|
+
export declare const chatWidget: ChatWidgetMeta;
|
|
25
|
+
export declare const WIDGET_COUNT: number;
|
package/chat/registry.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
"$comment": "The widget registry: the fourth kind of thing the package ships, between the components and the templates. One member by design. `parts` names the library components the widget composes (held to src/components/registry.json by scripts/validate-chat-widget.mjs), `subpaths` the package exports it occupies (held to scripts/package-manifest.mjs), `category` the component category whose section and sidebar group its page joins (held to src/components/registry.json), `peers` the optional peer dependencies each subpath needs (held to package.json). The widget's page under /components, the /docs/chat page, the chat corpus and the MCP tools read this file; never type its facts elsewhere.",
|
|
3
|
+
"widgets": [
|
|
4
|
+
{
|
|
5
|
+
"name": "ChatWidget",
|
|
6
|
+
"slug": "ai-chat",
|
|
7
|
+
"label": "AI chat",
|
|
8
|
+
"category": "ai",
|
|
9
|
+
"description": "The whole AI chat, assembled: the panel, the streaming replies, the history, the cards a reply can carry and the server handlers, set up with props.",
|
|
10
|
+
"command": "init chat",
|
|
11
|
+
"subpaths": {
|
|
12
|
+
"./chat": "The widget, the provider, the launcher and the engine under them.",
|
|
13
|
+
"./chat/server": "The streaming chat handler and its follow-ups and feedback siblings, for any web-standard route.",
|
|
14
|
+
"./chat/upstash": "Rate limits, a daily budget and the exchange log over Upstash Redis, as the handlers' guardrails.",
|
|
15
|
+
"./chat/demo": "A scripted conversation with rich cards and fixtures, for a preview that needs no key.",
|
|
16
|
+
"./chat/chat.css": "The widget's stylesheet."
|
|
17
|
+
},
|
|
18
|
+
"peers": {
|
|
19
|
+
"./chat": [
|
|
20
|
+
"react-markdown",
|
|
21
|
+
"remark-gfm"
|
|
22
|
+
],
|
|
23
|
+
"./chat/server": [
|
|
24
|
+
"@anthropic-ai/sdk"
|
|
25
|
+
],
|
|
26
|
+
"./chat/upstash": [
|
|
27
|
+
"@upstash/ratelimit",
|
|
28
|
+
"@upstash/redis"
|
|
29
|
+
],
|
|
30
|
+
"./chat/demo": [
|
|
31
|
+
"recharts"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"parts": [
|
|
35
|
+
"AgentRail",
|
|
36
|
+
"AgentStatus",
|
|
37
|
+
"AiButton",
|
|
38
|
+
"AttachmentDropZone",
|
|
39
|
+
"AttachmentGroup",
|
|
40
|
+
"ChatHeader",
|
|
41
|
+
"ChatMessage",
|
|
42
|
+
"ChatThread",
|
|
43
|
+
"CircularButton",
|
|
44
|
+
"CodeBlock",
|
|
45
|
+
"Composer",
|
|
46
|
+
"DropdownMenu",
|
|
47
|
+
"Mention",
|
|
48
|
+
"ModelPicker",
|
|
49
|
+
"PixelAvatar",
|
|
50
|
+
"PromptSuggestions",
|
|
51
|
+
"Prose",
|
|
52
|
+
"Reasoning",
|
|
53
|
+
"ThreadPanel",
|
|
54
|
+
"ThreadTabs"
|
|
55
|
+
],
|
|
56
|
+
"documented": [
|
|
57
|
+
"ChatWidget",
|
|
58
|
+
"ChatProvider",
|
|
59
|
+
"ChatLauncher",
|
|
60
|
+
"ChatThreads",
|
|
61
|
+
"ChatTabs",
|
|
62
|
+
"ChatAgentPane"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
;
|
|
68
|
+
|
|
69
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const $comment = "The widget registry: the fourth kind of thing the package ships, between the components and the templates. One member by design. `parts` names the library components the widget composes (held to src/components/registry.json by scripts/validate-chat-widget.mjs), `subpaths` the package exports it occupies (held to scripts/package-manifest.mjs), `category` the component category whose section and sidebar group its page joins (held to src/components/registry.json), `peers` the optional peer dependencies each subpath needs (held to package.json). The widget's page under /components, the /docs/chat page, the chat corpus and the MCP tools read this file; never type its facts elsewhere.";
|
|
2
|
+
const widgets = [{ "name": "ChatWidget", "slug": "ai-chat", "label": "AI chat", "category": "ai", "description": "The whole AI chat, assembled: the panel, the streaming replies, the history, the cards a reply can carry and the server handlers, set up with props.", "command": "init chat", "subpaths": { "./chat": "The widget, the provider, the launcher and the engine under them.", "./chat/server": "The streaming chat handler and its follow-ups and feedback siblings, for any web-standard route.", "./chat/upstash": "Rate limits, a daily budget and the exchange log over Upstash Redis, as the handlers' guardrails.", "./chat/demo": "A scripted conversation with rich cards and fixtures, for a preview that needs no key.", "./chat/chat.css": "The widget's stylesheet." }, "peers": { "./chat": ["react-markdown", "remark-gfm"], "./chat/server": ["@anthropic-ai/sdk"], "./chat/upstash": ["@upstash/ratelimit", "@upstash/redis"], "./chat/demo": ["recharts"] }, "parts": ["AgentRail", "AgentStatus", "AiButton", "AttachmentDropZone", "AttachmentGroup", "ChatHeader", "ChatMessage", "ChatThread", "CircularButton", "CodeBlock", "Composer", "DropdownMenu", "Mention", "ModelPicker", "PixelAvatar", "PromptSuggestions", "Prose", "Reasoning", "ThreadPanel", "ThreadTabs"], "documented": ["ChatWidget", "ChatProvider", "ChatLauncher", "ChatThreads", "ChatTabs", "ChatAgentPane"] }];
|
|
3
|
+
const registry = {
|
|
4
|
+
$comment,
|
|
5
|
+
widgets
|
|
6
|
+
};
|
|
7
|
+
export {
|
|
8
|
+
$comment,
|
|
9
|
+
registry as default,
|
|
10
|
+
widgets
|
|
11
|
+
};
|
package/chat/server.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { default as Anthropic } from '@anthropic-ai/sdk';
|
|
2
|
+
import { ChatCard, ChatFeedbackVerdict, ChatModelOption } from './types';
|
|
3
|
+
/** What a tool hands back: text for the model, and optionally a card for the visitor. */
|
|
4
|
+
export interface ChatToolResult {
|
|
5
|
+
/** The tool result the model reads. */
|
|
6
|
+
text: string;
|
|
7
|
+
/** Rich content streamed to the widget as a `card` event, drawn by the provider's renderers. */
|
|
8
|
+
card?: ChatCard;
|
|
9
|
+
}
|
|
10
|
+
/** One deterministic tool the model may call. */
|
|
11
|
+
export interface ChatTool {
|
|
12
|
+
/** The Anthropic tool definition: name, description, input schema. */
|
|
13
|
+
definition: Anthropic.Messages.Tool;
|
|
14
|
+
/** Runs the tool. Return a string for the model, or a result with a card for the visitor too. Never throw for bad input: return an error string the model can recover from. */
|
|
15
|
+
execute: (input: Record<string, unknown>) => string | ChatToolResult | Promise<string | ChatToolResult>;
|
|
16
|
+
/** The trace point shown while the round runs, e.g. "Checked the Button prop contract". */
|
|
17
|
+
tracePoint?: (input: Record<string, unknown>) => string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* How much of the day's budget remains, as a model policy: `open` means the
|
|
21
|
+
* default model serves, `reduced` means the budget model is the default (the
|
|
22
|
+
* better one stays selectable), `locked` means only the budget model serves.
|
|
23
|
+
*/
|
|
24
|
+
export type ModelTier = 'open' | 'reduced' | 'locked';
|
|
25
|
+
export type GuardrailVerdict = {
|
|
26
|
+
allowed: true;
|
|
27
|
+
tier: ModelTier;
|
|
28
|
+
} | {
|
|
29
|
+
allowed: false;
|
|
30
|
+
notice: string;
|
|
31
|
+
};
|
|
32
|
+
/** Anthropic's usage block, narrowed to the fields a spend estimate needs. */
|
|
33
|
+
export interface TokenUsage {
|
|
34
|
+
input_tokens?: number | null;
|
|
35
|
+
output_tokens?: number | null;
|
|
36
|
+
cache_creation_input_tokens?: number | null;
|
|
37
|
+
cache_read_input_tokens?: number | null;
|
|
38
|
+
}
|
|
39
|
+
/** One logged exchange: what was asked, what came back, and the numbers. */
|
|
40
|
+
export interface ExchangeLog {
|
|
41
|
+
/** Minted per request and streamed to the widget, so a feedback verdict can be joined back to this line. */
|
|
42
|
+
id: string;
|
|
43
|
+
question: string;
|
|
44
|
+
answer: string;
|
|
45
|
+
/** The sanitised context the request carried, or null. */
|
|
46
|
+
path: string | null;
|
|
47
|
+
/** The model id that served, absent on guardrail notices. */
|
|
48
|
+
model?: string;
|
|
49
|
+
usage?: TokenUsage;
|
|
50
|
+
/** Milliseconds from request to the stream closing. */
|
|
51
|
+
latencyMs: number;
|
|
52
|
+
/** Milliseconds to the first response text, what the visitor feels. */
|
|
53
|
+
firstTextMs: number | null;
|
|
54
|
+
/** True when the answer is a guardrail notice, not a model response. */
|
|
55
|
+
notice: boolean;
|
|
56
|
+
/** The opaque per-visitor key. */
|
|
57
|
+
visitor: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The hooks the handlers call around a request. Every member is optional
|
|
61
|
+
* and every one fails open: a missing hook means no limit, no log, no
|
|
62
|
+
* tiering. `rift-ds/chat/upstash` implements the set over Redis.
|
|
63
|
+
*/
|
|
64
|
+
export interface ChatGuardrails {
|
|
65
|
+
/** Rate limits and the daily breaker. Return a notice to answer with instead of calling the model. */
|
|
66
|
+
check?: (request: Request) => Promise<GuardrailVerdict>;
|
|
67
|
+
/** A stable, opaque key for the visitor (a salted hash, never an address). */
|
|
68
|
+
visitorKey?: (request: Request) => Promise<string> | string;
|
|
69
|
+
/** Called with the real token counts after every model call. */
|
|
70
|
+
onUsage?: (usage: TokenUsage, modelId: string) => Promise<void>;
|
|
71
|
+
/** Called once per exchange. Its presence is what makes the handler stream an `exchange` id, and so what shows the thumbs. */
|
|
72
|
+
onExchange?: (entry: ExchangeLog) => Promise<void>;
|
|
73
|
+
/** Per-visitor limit for the follow-ups handler. */
|
|
74
|
+
checkFollowups?: (request: Request) => Promise<boolean>;
|
|
75
|
+
/** Per-visitor limit for the feedback handler. */
|
|
76
|
+
checkFeedback?: (request: Request) => Promise<boolean>;
|
|
77
|
+
/** Stores one verdict per exchange id. */
|
|
78
|
+
onFeedback?: (exchangeId: string, verdict: ChatFeedbackVerdict, visitor: string) => Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
export interface ChatLimits {
|
|
81
|
+
/** Turns kept from the client's history. Older context is dropped. */
|
|
82
|
+
maxTurns: number;
|
|
83
|
+
/** Longest single message accepted, in characters. */
|
|
84
|
+
maxMessageChars: number;
|
|
85
|
+
/** Longest whole conversation accepted, summed across every kept turn. */
|
|
86
|
+
maxTotalChars: number;
|
|
87
|
+
/** Hard ceiling on the raw request body, rejected before it is parsed. */
|
|
88
|
+
maxBodyChars: number;
|
|
89
|
+
/** Trace points shown under the status label before the rest is dropped. */
|
|
90
|
+
maxTracePoints: number;
|
|
91
|
+
}
|
|
92
|
+
export declare const DEFAULT_CHAT_LIMITS: ChatLimits;
|
|
93
|
+
export interface ChatHandlerCopy {
|
|
94
|
+
/** The reply when the chat is switched off or unconfigured. */
|
|
95
|
+
disabled: string;
|
|
96
|
+
/** The reply to a message over `maxMessageChars`. */
|
|
97
|
+
tooLong: string;
|
|
98
|
+
/** The reply when the model refuses and nothing was streamed. */
|
|
99
|
+
refusal: string;
|
|
100
|
+
/** Appended when the answer hit the token cap. */
|
|
101
|
+
cutOff: string;
|
|
102
|
+
/** The first status label, proving the stream opened. */
|
|
103
|
+
reading: string;
|
|
104
|
+
/** The status label while thinking. */
|
|
105
|
+
thinking: string;
|
|
106
|
+
/** The status label while a tool round runs. */
|
|
107
|
+
lookingUp: string;
|
|
108
|
+
/** The error for a busy or overloaded model. */
|
|
109
|
+
busy: string;
|
|
110
|
+
/** The error for a rejected key. */
|
|
111
|
+
unauthorised: string;
|
|
112
|
+
/** The error for anything else before the first text. */
|
|
113
|
+
failed: string;
|
|
114
|
+
}
|
|
115
|
+
export declare const DEFAULT_CHAT_COPY: ChatHandlerCopy;
|
|
116
|
+
export type SystemBlocks = Anthropic.Messages.TextBlockParam[];
|
|
117
|
+
export interface ChatHandlerOptions {
|
|
118
|
+
/** An Anthropic client to reuse, or `apiKey` to build one from. */
|
|
119
|
+
client?: Anthropic;
|
|
120
|
+
apiKey?: string;
|
|
121
|
+
/** The off switch. Defaults to whether a client or key is present. */
|
|
122
|
+
enabled?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* The system prompt, as blocks. A string is one block; a function builds
|
|
125
|
+
* them per request from the sanitised context. Put `cache_control` on the
|
|
126
|
+
* block that holds your corpus, and nothing volatile before it.
|
|
127
|
+
*/
|
|
128
|
+
system: string | SystemBlocks | ((context: {
|
|
129
|
+
path: string | null;
|
|
130
|
+
}) => string | SystemBlocks);
|
|
131
|
+
/**
|
|
132
|
+
* An uncached trailing block built from the request's context, after the
|
|
133
|
+
* cache breakpoint. Defaults to naming the page; return null for none.
|
|
134
|
+
*/
|
|
135
|
+
contextBlock?: (path: string) => string | null;
|
|
136
|
+
/** The models on offer. The client sends a `value`; the handler calls the matching `id`. */
|
|
137
|
+
models: ChatModelOption[];
|
|
138
|
+
/** The wire value that serves when the budget has headroom and the visitor has not chosen. Defaults to the first model. */
|
|
139
|
+
defaultModel?: string;
|
|
140
|
+
/** The wire value that serves once the budget steps down, and the only one past the lock. Defaults to `defaultModel`. */
|
|
141
|
+
budgetModel?: string;
|
|
142
|
+
tools?: ChatTool[];
|
|
143
|
+
/** Model calls per exchange: the first pass plus tool rounds. The last allowed pass withholds the tools. */
|
|
144
|
+
maxModelCalls?: number;
|
|
145
|
+
/** Caps thinking and response text together. */
|
|
146
|
+
maxTokens?: number;
|
|
147
|
+
/** Adaptive thinking's effort dial, for models that take it. */
|
|
148
|
+
effort?: 'low' | 'medium' | 'high';
|
|
149
|
+
guardrails?: ChatGuardrails;
|
|
150
|
+
limits?: Partial<ChatLimits>;
|
|
151
|
+
copy?: Partial<ChatHandlerCopy>;
|
|
152
|
+
/** Turns the request's raw `path` into context, or null to drop it. The default accepts lowercase slug paths only. */
|
|
153
|
+
sanitiseContext?: (value: unknown) => string | null;
|
|
154
|
+
/** Keeps a fire-and-forget promise alive on platforms that end work with the response. */
|
|
155
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The request's context, sanitised as untrusted input: it lands in a system
|
|
159
|
+
* block, a stronger position than the user turn. Lowercase slug segments
|
|
160
|
+
* only, capped short; nothing expressible in that alphabet can carry an
|
|
161
|
+
* instruction. Anything else is dropped, never rejected.
|
|
162
|
+
*/
|
|
163
|
+
export declare function sanitisePath(value: unknown): string | null;
|
|
164
|
+
/**
|
|
165
|
+
* The chat handler: `(request) => Response`, streaming NDJSON `ChatEvent`s.
|
|
166
|
+
*/
|
|
167
|
+
export declare function createChatHandler(options: ChatHandlerOptions): (request: Request) => Promise<Response>;
|
|
168
|
+
export interface FollowupsHandlerOptions {
|
|
169
|
+
client?: Anthropic;
|
|
170
|
+
apiKey?: string;
|
|
171
|
+
enabled?: boolean;
|
|
172
|
+
/** The model id that writes the questions. A small one: the job is three short lines. */
|
|
173
|
+
model: string;
|
|
174
|
+
/**
|
|
175
|
+
* What the chat is about, in a phrase the prompt can use: "the Acme
|
|
176
|
+
* workspace app and its documentation". Ignored when `system` is given.
|
|
177
|
+
*/
|
|
178
|
+
about?: string;
|
|
179
|
+
/** A complete system prompt, replacing the built-in one. */
|
|
180
|
+
system?: string;
|
|
181
|
+
/** Example questions in the voice wanted, shown to the model. */
|
|
182
|
+
examples?: string[];
|
|
183
|
+
guardrails?: Pick<ChatGuardrails, 'checkFollowups' | 'onUsage'>;
|
|
184
|
+
/** Chips shown. */
|
|
185
|
+
wanted?: number;
|
|
186
|
+
/** Candidates asked for, so a few long ones still leave a full row. */
|
|
187
|
+
candidates?: number;
|
|
188
|
+
/** Cap on the upstream call, inside the widget's own wait. */
|
|
189
|
+
timeoutMs?: number;
|
|
190
|
+
}
|
|
191
|
+
/** The built-in follow-ups prompt. */
|
|
192
|
+
export declare function followupsSystemPrompt({ about, candidates, examples, }: {
|
|
193
|
+
about: string;
|
|
194
|
+
candidates: number;
|
|
195
|
+
examples: string[];
|
|
196
|
+
}): string;
|
|
197
|
+
/**
|
|
198
|
+
* The model's lines to the chips that are safe to show: plain text, one
|
|
199
|
+
* short question each, no duplicates, at most `wanted`. Anything that does
|
|
200
|
+
* not fit the shape is dropped rather than repaired.
|
|
201
|
+
*/
|
|
202
|
+
export declare function parseFollowups(text: string, wanted?: number): string[];
|
|
203
|
+
/**
|
|
204
|
+
* The follow-ups handler: POST `{ question, answer }`, respond
|
|
205
|
+
* `{ suggestions: string[] }`, empty on every failure. A second call rather
|
|
206
|
+
* than part of the answer's stream, because the questions are written from
|
|
207
|
+
* the completed answer.
|
|
208
|
+
*/
|
|
209
|
+
export declare function createFollowupsHandler(options: FollowupsHandlerOptions): (request: Request) => Promise<Response>;
|
|
210
|
+
/**
|
|
211
|
+
* The feedback handler: POST `{ id, verdict }`, respond 204. The id is never
|
|
212
|
+
* trusted to exist; a verdict for an id no exchange used is a write nothing
|
|
213
|
+
* will join against, so validation is a shape check, not a lookup.
|
|
214
|
+
*/
|
|
215
|
+
export declare function createFeedbackHandler(options?: {
|
|
216
|
+
guardrails?: Pick<ChatGuardrails, 'checkFeedback' | 'onFeedback' | 'visitorKey'>;
|
|
217
|
+
}): (request: Request) => Promise<Response>;
|