@frankieai/react 0.1.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/dist/frankie.css +2 -0
- package/dist/index.cjs +1065 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +310 -0
- package/dist/index.d.ts +310 -0
- package/dist/index.js +1003 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/src/components/chat/ChatPanel.tsx +147 -0
- package/src/components/chat/Composer.tsx +75 -0
- package/src/components/chat/FrankieInline.tsx +35 -0
- package/src/components/chat/FrankieWidget.tsx +88 -0
- package/src/components/chat/Markdown.tsx +136 -0
- package/src/components/chat/MessageBubble.tsx +70 -0
- package/src/components/chat/SuggestedPrompts.tsx +31 -0
- package/src/components/chat/ToolActivityList.tsx +52 -0
- package/src/components/ui/avatar.tsx +30 -0
- package/src/components/ui/badge.tsx +28 -0
- package/src/components/ui/button.tsx +39 -0
- package/src/components/ui/card.tsx +36 -0
- package/src/components/ui/scroll-area.tsx +15 -0
- package/src/components/ui/separator.tsx +7 -0
- package/src/components/ui/textarea.tsx +20 -0
- package/src/hooks/FrankieProvider.tsx +94 -0
- package/src/hooks/useFrankieChat.ts +148 -0
- package/src/index.ts +34 -0
- package/src/lib/client.ts +217 -0
- package/src/lib/messages.ts +121 -0
- package/src/lib/theme.ts +30 -0
- package/src/lib/utils.ts +7 -0
- package/src/styles/frankie.css +158 -0
- package/src/widget.test.tsx +217 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties, ButtonHTMLAttributes, HTMLAttributes, TextareaHTMLAttributes } from 'react';
|
|
3
|
+
import { PublicWidgetConfig, CreateSessionResponse, FrankieServerEvent, WidgetTheme } from '@frankieai/core';
|
|
4
|
+
export { FrankieServerEvent, PublicWidgetConfig, WidgetTheme } from '@frankieai/core';
|
|
5
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
6
|
+
import { VariantProps } from 'class-variance-authority';
|
|
7
|
+
import { ClassValue } from 'clsx';
|
|
8
|
+
|
|
9
|
+
type PageContext = Record<string, string | number | boolean | null>;
|
|
10
|
+
/**
|
|
11
|
+
* The hosted Frankie data plane. Embedding needs only a publishable key, so
|
|
12
|
+
* `baseUrl` defaults here — override it only when self-hosting the server.
|
|
13
|
+
*/
|
|
14
|
+
declare const FRANKIE_CLOUD_URL = "https://frankie-data-gq5o5syq2q-uc.a.run.app";
|
|
15
|
+
interface FrankieClientOptions {
|
|
16
|
+
/** Where the Frankie server is mounted. Defaults to the hosted cloud. */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/** Safe to ship to the browser; the origin allowlist is what authorises it. */
|
|
19
|
+
publishableKey: string;
|
|
20
|
+
/**
|
|
21
|
+
* Fetches a short-lived JWT for the signed-in user from the host's own
|
|
22
|
+
* backend. Called once per session, and again when a session expires.
|
|
23
|
+
* Omit only for tenants that allow anonymous chat.
|
|
24
|
+
*/
|
|
25
|
+
getUserToken?: () => Promise<string | undefined> | string | undefined;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
/**
|
|
28
|
+
* Remember the session (and, in the hook, the transcript) in localStorage so a
|
|
29
|
+
* page reload resumes the same conversation. On by default in the browser;
|
|
30
|
+
* set false for an ephemeral chat that starts fresh every load.
|
|
31
|
+
*/
|
|
32
|
+
persist?: boolean;
|
|
33
|
+
}
|
|
34
|
+
declare class FrankieApiError extends Error {
|
|
35
|
+
readonly status: number;
|
|
36
|
+
readonly code: string;
|
|
37
|
+
constructor(message: string, status: number, code: string);
|
|
38
|
+
}
|
|
39
|
+
declare class FrankieClient {
|
|
40
|
+
private readonly options;
|
|
41
|
+
private readonly baseUrl;
|
|
42
|
+
private readonly doFetch;
|
|
43
|
+
private session?;
|
|
44
|
+
constructor(options: FrankieClientOptions);
|
|
45
|
+
get sessionId(): string | undefined;
|
|
46
|
+
/** Whether this client persists across reloads (browser only, opt-out via `persist:false`). */
|
|
47
|
+
get persist(): boolean;
|
|
48
|
+
/** localStorage namespace for this tenant, shared with the chat hook. */
|
|
49
|
+
get namespace(): string;
|
|
50
|
+
private readStored;
|
|
51
|
+
private writeStored;
|
|
52
|
+
getConfig(signal?: AbortSignal): Promise<PublicWidgetConfig>;
|
|
53
|
+
/** Opens (or reuses) a chat session. */
|
|
54
|
+
ensureSession(context?: PageContext): Promise<CreateSessionResponse>;
|
|
55
|
+
/** Forget the current session so the next send starts a fresh conversation. */
|
|
56
|
+
resetSession(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Sends a message and invokes `onEvent` for each server event as it arrives.
|
|
59
|
+
* Resolves when the turn ends.
|
|
60
|
+
*/
|
|
61
|
+
send(input: {
|
|
62
|
+
message: string;
|
|
63
|
+
context?: PageContext;
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}, onEvent: (event: FrankieServerEvent) => void): Promise<void>;
|
|
66
|
+
private unwrap;
|
|
67
|
+
}
|
|
68
|
+
/** Parse the server's `data:`-only SSE stream into typed events. */
|
|
69
|
+
declare function readEventStream(body: ReadableStream<Uint8Array>): AsyncGenerator<FrankieServerEvent>;
|
|
70
|
+
|
|
71
|
+
interface FrankieProviderProps extends FrankieClientOptions {
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* Page context sent with every turn — current route, plan tier, whatever the
|
|
75
|
+
* agent should know. Treated as data by the server, never as instructions.
|
|
76
|
+
*/
|
|
77
|
+
context?: PageContext;
|
|
78
|
+
/** Overrides for the server-side theme, so a host can restyle without a redeploy. */
|
|
79
|
+
theme?: WidgetTheme;
|
|
80
|
+
/** Skip the `/v1/config` fetch by supplying the config yourself. */
|
|
81
|
+
config?: PublicWidgetConfig;
|
|
82
|
+
}
|
|
83
|
+
interface FrankieContextValue {
|
|
84
|
+
client: FrankieClient;
|
|
85
|
+
config?: PublicWidgetConfig;
|
|
86
|
+
configError?: string;
|
|
87
|
+
loading: boolean;
|
|
88
|
+
context?: PageContext;
|
|
89
|
+
theme?: WidgetTheme;
|
|
90
|
+
}
|
|
91
|
+
declare function FrankieProvider({ children, context, theme, config: providedConfig, ...clientOptions }: FrankieProviderProps): react.JSX.Element;
|
|
92
|
+
declare function useFrankie(): FrankieContextValue;
|
|
93
|
+
/** The theme actually in effect: provider override wins over server config. */
|
|
94
|
+
declare function useFrankieTheme(): WidgetTheme | undefined;
|
|
95
|
+
|
|
96
|
+
interface ToolActivity {
|
|
97
|
+
id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
label: string;
|
|
100
|
+
status: "running" | "done" | "failed" | "denied";
|
|
101
|
+
detail?: string;
|
|
102
|
+
}
|
|
103
|
+
interface UserChatMessage {
|
|
104
|
+
id: string;
|
|
105
|
+
role: "user";
|
|
106
|
+
text: string;
|
|
107
|
+
createdAt: number;
|
|
108
|
+
}
|
|
109
|
+
interface AssistantChatMessage {
|
|
110
|
+
id: string;
|
|
111
|
+
role: "assistant";
|
|
112
|
+
text: string;
|
|
113
|
+
thinking: string;
|
|
114
|
+
tools: ToolActivity[];
|
|
115
|
+
status: "streaming" | "done" | "error";
|
|
116
|
+
error?: string;
|
|
117
|
+
createdAt: number;
|
|
118
|
+
}
|
|
119
|
+
type ChatMessage = UserChatMessage | AssistantChatMessage;
|
|
120
|
+
/**
|
|
121
|
+
* Fold one server event into the assistant message being built.
|
|
122
|
+
*
|
|
123
|
+
* Kept pure and separate from React so the streaming logic is testable without
|
|
124
|
+
* a DOM, and so a host writing its own UI can reuse it.
|
|
125
|
+
*/
|
|
126
|
+
declare function applyEvent(message: AssistantChatMessage, event: FrankieServerEvent): AssistantChatMessage;
|
|
127
|
+
declare function newAssistantMessage(id: string): AssistantChatMessage;
|
|
128
|
+
declare function newUserMessage(id: string, text: string): UserChatMessage;
|
|
129
|
+
|
|
130
|
+
interface UseFrankieChatResult {
|
|
131
|
+
messages: ChatMessage[];
|
|
132
|
+
/** True from the moment a message is sent until the turn ends. */
|
|
133
|
+
busy: boolean;
|
|
134
|
+
/** Set when the turn failed outright (network, auth, rate limit). */
|
|
135
|
+
error?: string;
|
|
136
|
+
send: (text: string) => Promise<void>;
|
|
137
|
+
/** Abort the in-flight turn. The partial answer stays in the transcript. */
|
|
138
|
+
stop: () => void;
|
|
139
|
+
/** Clear the transcript and start a brand-new server session. */
|
|
140
|
+
reset: () => void;
|
|
141
|
+
}
|
|
142
|
+
declare function useFrankieChat(): UseFrankieChatResult;
|
|
143
|
+
|
|
144
|
+
interface ChatPanelProps {
|
|
145
|
+
className?: string;
|
|
146
|
+
/** Replace the header entirely, or pass `false` to drop it. */
|
|
147
|
+
header?: ReactNode | false;
|
|
148
|
+
/** Rendered under the composer — good for a "powered by" or a privacy note. */
|
|
149
|
+
footer?: ReactNode;
|
|
150
|
+
/** Shown before the first message, in place of the default greeting block. */
|
|
151
|
+
emptyState?: ReactNode;
|
|
152
|
+
/** Take over message rendering completely. */
|
|
153
|
+
renderMessage?: (message: ChatMessage) => ReactNode;
|
|
154
|
+
/** Take over the tool-activity rows. */
|
|
155
|
+
renderTool?: (tool: ToolActivity) => ReactNode;
|
|
156
|
+
placeholder?: string;
|
|
157
|
+
/** Shows a close button in the header when provided. */
|
|
158
|
+
onClose?: () => void;
|
|
159
|
+
autoFocus?: boolean;
|
|
160
|
+
/** Fill the parent instead of the fixed floating size — for full-page use. */
|
|
161
|
+
fill?: boolean;
|
|
162
|
+
}
|
|
163
|
+
declare function ChatPanel({ className, header, footer, emptyState, renderMessage, renderTool, placeholder, onClose, autoFocus, fill, }: ChatPanelProps): react.JSX.Element;
|
|
164
|
+
|
|
165
|
+
interface FrankieWidgetProps extends ChatPanelProps {
|
|
166
|
+
position?: "bottom-right" | "bottom-left";
|
|
167
|
+
/** Distance from the viewport edges, in pixels. */
|
|
168
|
+
offset?: number;
|
|
169
|
+
defaultOpen?: boolean;
|
|
170
|
+
/** Controlled mode: pass both to drive open state from the host app. */
|
|
171
|
+
open?: boolean;
|
|
172
|
+
onOpenChange?: (open: boolean) => void;
|
|
173
|
+
/** Replace the floating launcher button. */
|
|
174
|
+
renderLauncher?: (props: {
|
|
175
|
+
open: boolean;
|
|
176
|
+
toggle: () => void;
|
|
177
|
+
label: string;
|
|
178
|
+
}) => React.ReactNode;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The drop-in embed: a floating launcher plus the chat panel.
|
|
182
|
+
*
|
|
183
|
+
* Renders `position: fixed` at the document level, so it does not care what
|
|
184
|
+
* layout the host page uses.
|
|
185
|
+
*/
|
|
186
|
+
declare function FrankieWidget({ position, offset, defaultOpen, open: controlledOpen, onOpenChange, renderLauncher, ...panelProps }: FrankieWidgetProps): react.JSX.Element;
|
|
187
|
+
|
|
188
|
+
interface FrankieInlineProps extends ChatPanelProps {
|
|
189
|
+
/** Extra styles on the filling wrapper — e.g. a `maxWidth` to centre the column. */
|
|
190
|
+
style?: CSSProperties;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Full-page (or any-container) chat: the panel fills its parent instead of
|
|
194
|
+
* floating in a corner. Drop it into a route or a sized element — it brings its
|
|
195
|
+
* own theme root, so it styles correctly wherever it lands.
|
|
196
|
+
*
|
|
197
|
+
* <div style={{ height: "100dvh" }}>
|
|
198
|
+
* <FrankieProvider …><FrankieInline /></FrankieProvider>
|
|
199
|
+
* </div>
|
|
200
|
+
*/
|
|
201
|
+
declare function FrankieInline({ className, style, ...panelProps }: FrankieInlineProps): react.JSX.Element;
|
|
202
|
+
|
|
203
|
+
interface ComposerProps {
|
|
204
|
+
onSend: (text: string) => void;
|
|
205
|
+
onStop: () => void;
|
|
206
|
+
busy: boolean;
|
|
207
|
+
placeholder?: string;
|
|
208
|
+
disabled?: boolean;
|
|
209
|
+
autoFocus?: boolean;
|
|
210
|
+
}
|
|
211
|
+
declare function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus }: ComposerProps): react.JSX.Element;
|
|
212
|
+
|
|
213
|
+
interface MessageBubbleProps {
|
|
214
|
+
message: ChatMessage;
|
|
215
|
+
assistantName: string;
|
|
216
|
+
avatarUrl?: string;
|
|
217
|
+
showToolActivity: boolean;
|
|
218
|
+
renderTool?: (tool: ToolActivity) => ReactNode;
|
|
219
|
+
}
|
|
220
|
+
declare function MessageBubble({ message, assistantName, avatarUrl, showToolActivity, renderTool, }: MessageBubbleProps): react.JSX.Element;
|
|
221
|
+
declare function TypingDots(): react.JSX.Element;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A deliberately small Markdown subset — paragraphs, lists, fenced code, bold,
|
|
225
|
+
* italic, inline code and links.
|
|
226
|
+
*
|
|
227
|
+
* Everything is rendered as React elements; nothing goes through
|
|
228
|
+
* `dangerouslySetInnerHTML`. Model output lands in someone else's production
|
|
229
|
+
* page, so it is never trusted as markup.
|
|
230
|
+
*/
|
|
231
|
+
declare function Markdown({ text, className }: {
|
|
232
|
+
text: string;
|
|
233
|
+
className?: string;
|
|
234
|
+
}): react.JSX.Element;
|
|
235
|
+
|
|
236
|
+
declare function SuggestedPrompts({ prompts, onPick, disabled, }: {
|
|
237
|
+
prompts: string[];
|
|
238
|
+
onPick: (prompt: string) => void;
|
|
239
|
+
disabled?: boolean;
|
|
240
|
+
}): react.JSX.Element | null;
|
|
241
|
+
|
|
242
|
+
interface ToolActivityListProps {
|
|
243
|
+
tools: ToolActivity[];
|
|
244
|
+
/** Replace the default row rendering entirely. */
|
|
245
|
+
renderTool?: (tool: ToolActivity) => React.ReactNode;
|
|
246
|
+
}
|
|
247
|
+
/** The "what the assistant is doing" strip under a streaming answer. */
|
|
248
|
+
declare function ToolActivityList({ tools, renderTool }: ToolActivityListProps): react.JSX.Element | null;
|
|
249
|
+
declare function ToolActivityRow({ tool }: {
|
|
250
|
+
tool: ToolActivity;
|
|
251
|
+
}): react.JSX.Element;
|
|
252
|
+
|
|
253
|
+
declare const buttonVariants: (props?: ({
|
|
254
|
+
variant?: "link" | "default" | "secondary" | "outline" | "ghost" | null | undefined;
|
|
255
|
+
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
256
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
257
|
+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
258
|
+
asChild?: boolean;
|
|
259
|
+
}
|
|
260
|
+
declare function Button({ className, variant, size, asChild, ...props }: ButtonProps): react.JSX.Element;
|
|
261
|
+
|
|
262
|
+
declare const badgeVariants: (props?: ({
|
|
263
|
+
variant?: "default" | "outline" | "success" | "danger" | null | undefined;
|
|
264
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
265
|
+
declare function Badge({ className, variant, ...props }: HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants>): react.JSX.Element;
|
|
266
|
+
|
|
267
|
+
interface AvatarProps {
|
|
268
|
+
src?: string;
|
|
269
|
+
name: string;
|
|
270
|
+
className?: string;
|
|
271
|
+
}
|
|
272
|
+
/** Image avatar with an initials fallback — no image request when `src` is absent. */
|
|
273
|
+
declare function Avatar({ src, name, className }: AvatarProps): react.JSX.Element;
|
|
274
|
+
|
|
275
|
+
declare function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
276
|
+
declare function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
277
|
+
declare function CardTitle({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
278
|
+
declare function CardDescription({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
279
|
+
declare function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
280
|
+
declare function CardFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* A plain overflow container rather than Radix's ScrollArea: the widget lives
|
|
284
|
+
* inside someone else's page, and a native scroller inherits their scrollbar
|
|
285
|
+
* behaviour instead of fighting it.
|
|
286
|
+
*/
|
|
287
|
+
declare const ScrollArea: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
|
|
288
|
+
|
|
289
|
+
declare function Separator({ className }: {
|
|
290
|
+
className?: string;
|
|
291
|
+
}): react.JSX.Element;
|
|
292
|
+
|
|
293
|
+
/** `forwardRef` rather than React 19's ref-as-prop, so React 18 hosts work too. */
|
|
294
|
+
declare const Textarea: react.ForwardRefExoticComponent<TextareaHTMLAttributes<HTMLTextAreaElement> & react.RefAttributes<HTMLTextAreaElement>>;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Map a theme to the CSS custom properties the widget's styles read.
|
|
298
|
+
*
|
|
299
|
+
* Anything not set falls through to the defaults in `frankie.css`, which are
|
|
300
|
+
* themselves shadcn/ui token names — so a host that already runs shadcn can
|
|
301
|
+
* drop the stylesheet and the widget inherits their design system instead.
|
|
302
|
+
*/
|
|
303
|
+
declare function themeToCssVars(theme: WidgetTheme | undefined): CSSProperties;
|
|
304
|
+
/** Resolve `mode: "system"` against the browser, so the widget can hard-set a class. */
|
|
305
|
+
declare function resolveMode(mode: WidgetTheme["mode"]): "light" | "dark";
|
|
306
|
+
|
|
307
|
+
/** shadcn/ui's class helper: conditional classes with Tailwind conflict resolution. */
|
|
308
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
309
|
+
|
|
310
|
+
export { type AssistantChatMessage, Avatar, Badge, Button, type ButtonProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type ChatMessage, ChatPanel, type ChatPanelProps, Composer, FRANKIE_CLOUD_URL, FrankieApiError, FrankieClient, type FrankieClientOptions, FrankieInline, type FrankieInlineProps, FrankieProvider, type FrankieProviderProps, FrankieWidget, type FrankieWidgetProps, Markdown, MessageBubble, type PageContext, ScrollArea, Separator, SuggestedPrompts, Textarea, type ToolActivity, ToolActivityList, ToolActivityRow, TypingDots, type UseFrankieChatResult, type UserChatMessage, applyEvent, buttonVariants, cn, newAssistantMessage, newUserMessage, readEventStream, resolveMode, themeToCssVars, useFrankie, useFrankieChat, useFrankieTheme };
|