@chaaskit/client 0.1.1 → 0.1.2
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/LICENSE +21 -0
- package/dist/lib/index.js +970 -80
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/routes/AdminPromoCodesRoute.js +19 -0
- package/dist/lib/routes/AdminPromoCodesRoute.js.map +1 -0
- package/dist/lib/routes/AdminWaitlistRoute.js +19 -0
- package/dist/lib/routes/AdminWaitlistRoute.js.map +1 -0
- package/dist/lib/routes.js +47 -37
- package/dist/lib/routes.js.map +1 -1
- package/dist/lib/ssr-utils.js +36 -16
- package/dist/lib/ssr-utils.js.map +1 -1
- package/dist/lib/styles.css +37 -0
- package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -1
- package/package.json +20 -12
- package/src/components/MessageItem.tsx +35 -4
- package/src/components/MessageList.tsx +51 -5
- package/src/components/OAuthAppsSection.tsx +1 -1
- package/src/components/Sidebar.tsx +1 -3
- package/src/components/ToolCallDisplay.tsx +102 -11
- package/src/components/tool-renderers/DocumentListRenderer.tsx +44 -0
- package/src/components/tool-renderers/DocumentReadRenderer.tsx +33 -0
- package/src/components/tool-renderers/DocumentSaveRenderer.tsx +32 -0
- package/src/components/tool-renderers/DocumentSearchRenderer.tsx +33 -0
- package/src/components/tool-renderers/index.ts +36 -0
- package/src/components/tool-renderers/utils.ts +7 -0
- package/src/contexts/AuthContext.tsx +16 -6
- package/src/contexts/ConfigContext.tsx +22 -28
- package/src/extensions/registry.ts +2 -1
- package/src/hooks/__tests__/basePath.test.ts +42 -0
- package/src/index.tsx +5 -2
- package/src/pages/AdminDashboardPage.tsx +15 -1
- package/src/pages/AdminPromoCodesPage.tsx +378 -0
- package/src/pages/AdminTeamPage.tsx +29 -1
- package/src/pages/AdminTeamsPage.tsx +15 -1
- package/src/pages/AdminUsersPage.tsx +15 -1
- package/src/pages/AdminWaitlistPage.tsx +156 -0
- package/src/pages/RegisterPage.tsx +91 -9
- package/src/routes/AdminPromoCodesRoute.tsx +24 -0
- package/src/routes/AdminWaitlistRoute.tsx +24 -0
- package/src/routes/index.ts +2 -0
- package/src/ssr-utils.tsx +32 -12
- package/src/stores/chatStore.ts +5 -0
- package/dist/favicon.svg +0 -11
- package/dist/index.html +0 -17
- package/dist/logo.svg +0 -12
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useRef, useEffect } from 'react';
|
|
2
|
-
import type { Message, MCPContent, UIResource } from '@chaaskit/shared';
|
|
1
|
+
import { useMemo, useRef, useEffect } from 'react';
|
|
2
|
+
import type { Message, MCPContent, UIResource, ToolCall, ToolResult } from '@chaaskit/shared';
|
|
3
3
|
import { Bot } from 'lucide-react';
|
|
4
4
|
import { useTheme } from '../contexts/ThemeContext';
|
|
5
5
|
import { useConfig } from '../contexts/ConfigContext';
|
|
6
|
+
import { useExtensionTools } from '../extensions/useExtensions';
|
|
6
7
|
import MessageItem from './MessageItem';
|
|
7
8
|
import ToolCallDisplay, { UIResourceWidget } from './ToolCallDisplay';
|
|
8
9
|
|
|
@@ -17,6 +18,7 @@ interface CompletedToolCall extends PendingToolCall {
|
|
|
17
18
|
result: MCPContent[];
|
|
18
19
|
isError?: boolean;
|
|
19
20
|
uiResource?: UIResource;
|
|
21
|
+
structuredContent?: Record<string, unknown>;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
interface MessageListProps {
|
|
@@ -35,6 +37,12 @@ export default function MessageList({
|
|
|
35
37
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
36
38
|
const { theme } = useTheme();
|
|
37
39
|
const config = useConfig();
|
|
40
|
+
const extensionTools = useExtensionTools();
|
|
41
|
+
const toolRendererMap = useMemo(() => {
|
|
42
|
+
const map = new Map<string, typeof extensionTools[number]>();
|
|
43
|
+
extensionTools.forEach((tool) => map.set(tool.name, tool));
|
|
44
|
+
return map;
|
|
45
|
+
}, [extensionTools]);
|
|
38
46
|
const showToolCalls = config.mcp?.showToolCalls !== false;
|
|
39
47
|
|
|
40
48
|
useEffect(() => {
|
|
@@ -45,8 +53,34 @@ export default function MessageList({
|
|
|
45
53
|
const isStreaming = Boolean(streamingContent) || hasToolActivity;
|
|
46
54
|
|
|
47
55
|
// Get UI resources from completed tool calls for separate rendering
|
|
56
|
+
const renderedToolCalls = completedToolCalls
|
|
57
|
+
.filter((call) =>
|
|
58
|
+
call.serverId === 'native' &&
|
|
59
|
+
!!toolRendererMap.get(call.name)?.resultRenderer
|
|
60
|
+
)
|
|
61
|
+
.map((call) => {
|
|
62
|
+
const renderer = toolRendererMap.get(call.name)!.resultRenderer!;
|
|
63
|
+
const toolCall: ToolCall = {
|
|
64
|
+
id: call.id,
|
|
65
|
+
serverId: call.serverId,
|
|
66
|
+
toolName: call.name,
|
|
67
|
+
arguments: call.input,
|
|
68
|
+
status: call.isError ? 'error' : 'completed',
|
|
69
|
+
};
|
|
70
|
+
const toolResult: ToolResult = {
|
|
71
|
+
toolCallId: call.id,
|
|
72
|
+
content: call.result,
|
|
73
|
+
isError: call.isError,
|
|
74
|
+
uiResource: call.uiResource,
|
|
75
|
+
structuredContent: call.structuredContent,
|
|
76
|
+
};
|
|
77
|
+
return { toolCall, toolResult, Renderer: renderer };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const renderedToolCallIds = new Set(renderedToolCalls.map((entry) => entry.toolCall.id));
|
|
81
|
+
|
|
48
82
|
const uiResources = completedToolCalls
|
|
49
|
-
.filter((tc) => tc.uiResource?.text)
|
|
83
|
+
.filter((tc) => tc.uiResource?.text && !renderedToolCallIds.has(tc.id))
|
|
50
84
|
.map((tc) => tc.uiResource!);
|
|
51
85
|
|
|
52
86
|
// Debug logging
|
|
@@ -96,6 +130,7 @@ export default function MessageList({
|
|
|
96
130
|
toolCallId: call.id,
|
|
97
131
|
content: call.result,
|
|
98
132
|
isError: call.isError,
|
|
133
|
+
structuredContent: call.structuredContent,
|
|
99
134
|
}}
|
|
100
135
|
hideUiResource
|
|
101
136
|
/>
|
|
@@ -118,7 +153,18 @@ export default function MessageList({
|
|
|
118
153
|
</div>
|
|
119
154
|
)}
|
|
120
155
|
|
|
121
|
-
{/* 2.
|
|
156
|
+
{/* 2. Tool Renderers (native tools) */}
|
|
157
|
+
{renderedToolCalls.length > 0 && (
|
|
158
|
+
<div className="space-y-3">
|
|
159
|
+
{renderedToolCalls.map(({ toolCall, toolResult, Renderer }) => (
|
|
160
|
+
<div key={toolCall.id} className="rounded-lg border border-border bg-background-secondary/40 p-3">
|
|
161
|
+
<Renderer toolCall={toolCall} toolResult={toolResult} />
|
|
162
|
+
</div>
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{/* 3. UI Resource Widgets (outside bubble, full width) */}
|
|
122
168
|
{uiResources.length > 0 && (
|
|
123
169
|
<div className="space-y-3">
|
|
124
170
|
{uiResources.map((uiResource, index) => (
|
|
@@ -127,7 +173,7 @@ export default function MessageList({
|
|
|
127
173
|
</div>
|
|
128
174
|
)}
|
|
129
175
|
|
|
130
|
-
{/*
|
|
176
|
+
{/* 4. Text Response Bubble (with avatar) */}
|
|
131
177
|
{streamingContent && (
|
|
132
178
|
<div className="group flex gap-3">
|
|
133
179
|
{/* Avatar */}
|
|
@@ -18,7 +18,7 @@ export default function OAuthAppsSection() {
|
|
|
18
18
|
const [revokingId, setRevokingId] = useState<string | null>(null);
|
|
19
19
|
|
|
20
20
|
// Check if OAuth is enabled
|
|
21
|
-
const oauthEnabled = config.mcp?.server
|
|
21
|
+
const oauthEnabled = config.mcp?.servers?.some((server) => server.authMode === 'user-oauth');
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
if (oauthEnabled) {
|
|
@@ -373,9 +373,7 @@ export default function Sidebar({ onClose, onOpenSearch }: SidebarProps) {
|
|
|
373
373
|
)}
|
|
374
374
|
|
|
375
375
|
{/* Admin Dashboard - show for site admins (config or database flag) */}
|
|
376
|
-
{(user?.isAdmin || config.
|
|
377
|
-
(email: string) => email.toLowerCase() === user?.email?.toLowerCase()
|
|
378
|
-
)) && (
|
|
376
|
+
{(user?.isAdmin || config.auth?.isAdmin) && (
|
|
379
377
|
<Link
|
|
380
378
|
to={appPath('/admin')}
|
|
381
379
|
className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"
|
|
@@ -19,6 +19,8 @@ const AUTO_APPROVE_LABELS: Record<AutoApproveReason, string> = {
|
|
|
19
19
|
thread_allowed: 'Allowed for this chat',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const TOOL_UI_MESSAGE_SOURCE = 'chaaskit-tool-ui';
|
|
23
|
+
|
|
22
24
|
// Generate the window.openai initialization script for OpenAI format resources
|
|
23
25
|
function generateOpenAiScript(
|
|
24
26
|
toolInput: Record<string, unknown>,
|
|
@@ -68,6 +70,46 @@ function generateOpenAiScript(
|
|
|
68
70
|
</style>
|
|
69
71
|
<script>
|
|
70
72
|
(function() {
|
|
73
|
+
const MESSAGE_SOURCE = '${TOOL_UI_MESSAGE_SOURCE}';
|
|
74
|
+
let requestId = 0;
|
|
75
|
+
const pending = new Map();
|
|
76
|
+
|
|
77
|
+
function resolvePending(id, data) {
|
|
78
|
+
const entry = pending.get(id);
|
|
79
|
+
if (!entry) return;
|
|
80
|
+
pending.delete(id);
|
|
81
|
+
if (data && data.ok) {
|
|
82
|
+
entry.resolve(data.result);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
entry.resolve({ error: (data && data.error) || 'Tool UI bridge error' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
window.addEventListener('message', (event) => {
|
|
89
|
+
const data = event.data || {};
|
|
90
|
+
if (data.source !== MESSAGE_SOURCE || !data.id) return;
|
|
91
|
+
resolvePending(data.id, data);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
function sendToParent(type, payload, timeoutMs = 15000) {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const id = String(Date.now()) + '-' + String(requestId++);
|
|
97
|
+
pending.set(id, { resolve });
|
|
98
|
+
try {
|
|
99
|
+
window.parent.postMessage({ source: MESSAGE_SOURCE, type, id, payload }, '*');
|
|
100
|
+
} catch (err) {
|
|
101
|
+
pending.delete(id);
|
|
102
|
+
resolve({ error: 'Unable to reach host window' });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (!pending.has(id)) return;
|
|
107
|
+
pending.delete(id);
|
|
108
|
+
resolve({ error: 'Tool UI request timed out' });
|
|
109
|
+
}, timeoutMs);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
71
113
|
// Initialize window.openai with the OpenAI Apps SDK spec
|
|
72
114
|
window.openai = {
|
|
73
115
|
// Core globals
|
|
@@ -92,17 +134,20 @@ function generateOpenAiScript(
|
|
|
92
134
|
// API methods
|
|
93
135
|
callTool: async (name, args) => {
|
|
94
136
|
console.log('window.openai.callTool called:', { name, args });
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
137
|
+
const response = await sendToParent('callTool', { name, args });
|
|
138
|
+
if (response && response.error) {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: 'text', text: response.error }],
|
|
141
|
+
isError: true
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return response || { content: [{ type: 'text', text: 'No response from host' }], isError: true };
|
|
100
145
|
},
|
|
101
146
|
|
|
102
147
|
sendFollowUpMessage: async (args) => {
|
|
103
148
|
console.log('window.openai.sendFollowUpMessage called:', args);
|
|
104
|
-
|
|
105
|
-
return {};
|
|
149
|
+
const response = await sendToParent('sendFollowUpMessage', args);
|
|
150
|
+
return response && response.error ? { error: response.error } : (response || {});
|
|
106
151
|
},
|
|
107
152
|
|
|
108
153
|
openExternal: (payload) => {
|
|
@@ -114,27 +159,41 @@ function generateOpenAiScript(
|
|
|
114
159
|
|
|
115
160
|
requestDisplayMode: async (args) => {
|
|
116
161
|
console.log('window.openai.requestDisplayMode called:', args);
|
|
117
|
-
|
|
162
|
+
const response = await sendToParent('requestDisplayMode', args);
|
|
163
|
+
if (response && response.error) {
|
|
164
|
+
return { mode: args.mode };
|
|
165
|
+
}
|
|
166
|
+
return response || { mode: args.mode };
|
|
118
167
|
},
|
|
119
168
|
|
|
120
169
|
setWidgetState: async (state) => {
|
|
121
170
|
console.log('window.openai.setWidgetState called:', state);
|
|
122
171
|
window.openai.widgetState = state;
|
|
123
|
-
|
|
172
|
+
const response = await sendToParent('setWidgetState', state);
|
|
173
|
+
return response && response.error ? {} : (response || {});
|
|
124
174
|
},
|
|
125
175
|
|
|
126
176
|
requestClose: () => {
|
|
127
177
|
console.log('window.openai.requestClose called');
|
|
178
|
+
sendToParent('requestClose', {});
|
|
128
179
|
},
|
|
129
180
|
|
|
130
181
|
getFileDownloadUrl: async ({ fileId }) => {
|
|
131
182
|
console.log('window.openai.getFileDownloadUrl called:', fileId);
|
|
132
|
-
|
|
183
|
+
const response = await sendToParent('getFileDownloadUrl', { fileId });
|
|
184
|
+
if (response && response.error) {
|
|
185
|
+
return { url: '' };
|
|
186
|
+
}
|
|
187
|
+
return response || { url: '' };
|
|
133
188
|
},
|
|
134
189
|
|
|
135
190
|
uploadFile: async (file) => {
|
|
136
191
|
console.log('window.openai.uploadFile called:', file);
|
|
137
|
-
|
|
192
|
+
const response = await sendToParent('uploadFile', { file });
|
|
193
|
+
if (response && response.error) {
|
|
194
|
+
return { fileId: '' };
|
|
195
|
+
}
|
|
196
|
+
return response || { fileId: '' };
|
|
138
197
|
}
|
|
139
198
|
};
|
|
140
199
|
|
|
@@ -199,6 +258,38 @@ export function UIResourceWidget({ uiResource, theme }: { uiResource: UIResource
|
|
|
199
258
|
}
|
|
200
259
|
|
|
201
260
|
export default function ToolCallDisplay({ toolCall, toolResult, isPending, uiResource, hideUiResource, autoApproveReason }: ToolCallDisplayProps) {
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
async function handleMessage(event: MessageEvent) {
|
|
263
|
+
const data = event.data || {};
|
|
264
|
+
if (data.source !== TOOL_UI_MESSAGE_SOURCE || !data.id) return;
|
|
265
|
+
|
|
266
|
+
const targetOrigin = event.origin && event.origin !== 'null' ? event.origin : '*';
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const handler = (window as unknown as { chaaskitToolUiHandler?: (payload: unknown) => Promise<unknown> }).chaaskitToolUiHandler;
|
|
270
|
+
const result = typeof handler === 'function'
|
|
271
|
+
? await handler({ type: data.type, payload: data.payload })
|
|
272
|
+
: { error: 'Tool UI bridge not configured' };
|
|
273
|
+
|
|
274
|
+
const sourceWindow = event.source as Window | null;
|
|
275
|
+
sourceWindow?.postMessage(
|
|
276
|
+
{ source: TOOL_UI_MESSAGE_SOURCE, id: data.id, ok: true, result },
|
|
277
|
+
targetOrigin
|
|
278
|
+
);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
const message = err instanceof Error ? err.message : 'Tool UI bridge error';
|
|
281
|
+
const sourceWindow = event.source as Window | null;
|
|
282
|
+
sourceWindow?.postMessage(
|
|
283
|
+
{ source: TOOL_UI_MESSAGE_SOURCE, id: data.id, ok: false, error: message },
|
|
284
|
+
targetOrigin
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
window.addEventListener('message', handleMessage);
|
|
290
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
291
|
+
}, []);
|
|
292
|
+
|
|
202
293
|
// Check if we have HTML content to render
|
|
203
294
|
const hasHtmlResource = !hideUiResource && uiResource?.text &&
|
|
204
295
|
(uiResource.mimeType?.includes('html') || uiResource.text.trim().startsWith('<'));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ToolCall, ToolResult } from '@chaaskit/shared';
|
|
2
|
+
import { getTextContent } from './utils';
|
|
3
|
+
|
|
4
|
+
interface DocumentSummary {
|
|
5
|
+
id?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
mimeType?: string;
|
|
9
|
+
charCount?: number;
|
|
10
|
+
teamId?: string | null;
|
|
11
|
+
projectId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function DocumentListRenderer({ toolCall, toolResult }: { toolCall: ToolCall; toolResult: ToolResult }) {
|
|
15
|
+
const structured = toolResult.structuredContent as { documents?: DocumentSummary[] } | undefined;
|
|
16
|
+
const documents = structured?.documents ?? [];
|
|
17
|
+
const fallback = getTextContent(toolResult.content);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-2">
|
|
21
|
+
<div className="text-sm font-semibold text-text-primary">Documents</div>
|
|
22
|
+
{documents.length > 0 ? (
|
|
23
|
+
<ul className="space-y-2">
|
|
24
|
+
{documents.map((doc, index) => (
|
|
25
|
+
<li key={doc.id ?? doc.path ?? index} className="rounded-md border border-border bg-background px-3 py-2">
|
|
26
|
+
<div className="text-sm font-medium text-text-primary">{doc.path ?? doc.name ?? 'Untitled document'}</div>
|
|
27
|
+
<div className="text-xs text-text-muted">
|
|
28
|
+
{doc.mimeType ?? 'text/plain'}
|
|
29
|
+
{typeof doc.charCount === 'number' ? ` • ${doc.charCount} chars` : ''}
|
|
30
|
+
{doc.teamId ? ' • team' : ''}
|
|
31
|
+
{doc.projectId ? ' • project' : ''}
|
|
32
|
+
</div>
|
|
33
|
+
</li>
|
|
34
|
+
))}
|
|
35
|
+
</ul>
|
|
36
|
+
) : (
|
|
37
|
+
<div className="text-sm text-text-secondary">
|
|
38
|
+
{fallback ?? 'No documents found.'}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<div className="text-xs text-text-muted">Tool: {toolCall.toolName}</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolCall, ToolResult } from '@chaaskit/shared';
|
|
2
|
+
import { getTextContent } from './utils';
|
|
3
|
+
|
|
4
|
+
interface ReadSummary {
|
|
5
|
+
path?: string;
|
|
6
|
+
offset?: number;
|
|
7
|
+
linesReturned?: number;
|
|
8
|
+
totalLines?: number;
|
|
9
|
+
truncated?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function DocumentReadRenderer({ toolCall, toolResult }: { toolCall: ToolCall; toolResult: ToolResult }) {
|
|
13
|
+
const structured = toolResult.structuredContent as ReadSummary | undefined;
|
|
14
|
+
const fallback = getTextContent(toolResult.content);
|
|
15
|
+
const rangeText = structured
|
|
16
|
+
? `Lines ${Number(structured.offset ?? 0) + 1}-${Number(structured.offset ?? 0) + Number(structured.linesReturned ?? 0)} of ${structured.totalLines ?? 'unknown'}`
|
|
17
|
+
: null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-2">
|
|
21
|
+
<div className="text-sm font-semibold text-text-primary">Document Preview</div>
|
|
22
|
+
{structured ? (
|
|
23
|
+
<div className="rounded-md border border-border bg-background px-3 py-2 text-sm text-text-secondary">
|
|
24
|
+
<div className="font-medium text-text-primary">{structured.path ?? 'Document'}</div>
|
|
25
|
+
{rangeText && <div className="text-xs text-text-muted">{rangeText}{structured.truncated ? ' (truncated)' : ''}</div>}
|
|
26
|
+
</div>
|
|
27
|
+
) : (
|
|
28
|
+
<div className="text-sm text-text-secondary">{fallback ?? 'No structured document details.'}</div>
|
|
29
|
+
)}
|
|
30
|
+
<div className="text-xs text-text-muted">Tool: {toolCall.toolName}</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ToolCall, ToolResult } from '@chaaskit/shared';
|
|
2
|
+
import { getTextContent } from './utils';
|
|
3
|
+
|
|
4
|
+
interface SaveSummary {
|
|
5
|
+
success?: boolean;
|
|
6
|
+
path?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
charCount?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function DocumentSaveRenderer({ toolCall, toolResult }: { toolCall: ToolCall; toolResult: ToolResult }) {
|
|
12
|
+
const structured = toolResult.structuredContent as SaveSummary | undefined;
|
|
13
|
+
const fallback = getTextContent(toolResult.content);
|
|
14
|
+
const success = structured?.success !== false && !toolResult.isError;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="space-y-2">
|
|
18
|
+
<div className="text-sm font-semibold text-text-primary">Save Document</div>
|
|
19
|
+
{structured ? (
|
|
20
|
+
<div className={`rounded-md border px-3 py-2 text-sm ${success ? 'border-success/30 bg-success/10 text-success' : 'border-error/30 bg-error/10 text-error'}`}>
|
|
21
|
+
<div className="font-medium">{success ? 'Saved' : 'Failed'}</div>
|
|
22
|
+
{structured.path && <div className="text-xs">Path: {structured.path}</div>}
|
|
23
|
+
{structured.id && <div className="text-xs">ID: {structured.id}</div>}
|
|
24
|
+
{typeof structured.charCount === 'number' && <div className="text-xs">Size: {structured.charCount} chars</div>}
|
|
25
|
+
</div>
|
|
26
|
+
) : (
|
|
27
|
+
<div className="text-sm text-text-secondary">{fallback ?? 'No structured save data.'}</div>
|
|
28
|
+
)}
|
|
29
|
+
<div className="text-xs text-text-muted">Tool: {toolCall.toolName}</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolCall, ToolResult } from '@chaaskit/shared';
|
|
2
|
+
import { getTextContent } from './utils';
|
|
3
|
+
|
|
4
|
+
interface SearchSummary {
|
|
5
|
+
path?: string;
|
|
6
|
+
query?: string;
|
|
7
|
+
matchCount?: number;
|
|
8
|
+
matchLines?: number[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function DocumentSearchRenderer({ toolCall, toolResult }: { toolCall: ToolCall; toolResult: ToolResult }) {
|
|
12
|
+
const structured = toolResult.structuredContent as SearchSummary | undefined;
|
|
13
|
+
const fallback = getTextContent(toolResult.content);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-2">
|
|
17
|
+
<div className="text-sm font-semibold text-text-primary">Document Search</div>
|
|
18
|
+
{structured ? (
|
|
19
|
+
<div className="rounded-md border border-border bg-background px-3 py-2 text-sm text-text-secondary">
|
|
20
|
+
<div className="font-medium text-text-primary">{structured.path ?? 'Document'}</div>
|
|
21
|
+
<div className="text-xs text-text-muted">Query: {structured.query ?? 'unknown'}</div>
|
|
22
|
+
<div className="text-xs text-text-muted">Matches: {structured.matchCount ?? 0}</div>
|
|
23
|
+
{structured.matchLines && structured.matchLines.length > 0 && (
|
|
24
|
+
<div className="text-xs text-text-muted">Lines: {structured.matchLines.join(', ')}</div>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
) : (
|
|
28
|
+
<div className="text-sm text-text-secondary">{fallback ?? 'No structured search data.'}</div>
|
|
29
|
+
)}
|
|
30
|
+
<div className="text-xs text-text-muted">Tool: {toolCall.toolName}</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { clientRegistry } from '../../extensions/registry';
|
|
2
|
+
import DocumentListRenderer from './DocumentListRenderer';
|
|
3
|
+
import DocumentReadRenderer from './DocumentReadRenderer';
|
|
4
|
+
import DocumentSearchRenderer from './DocumentSearchRenderer';
|
|
5
|
+
import DocumentSaveRenderer from './DocumentSaveRenderer';
|
|
6
|
+
|
|
7
|
+
clientRegistry.registerTool({
|
|
8
|
+
name: 'list_documents',
|
|
9
|
+
description: 'Render document list results',
|
|
10
|
+
resultRenderer: DocumentListRenderer,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
clientRegistry.registerTool({
|
|
14
|
+
name: 'read_document',
|
|
15
|
+
description: 'Render document read results',
|
|
16
|
+
resultRenderer: DocumentReadRenderer,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
clientRegistry.registerTool({
|
|
20
|
+
name: 'search_in_document',
|
|
21
|
+
description: 'Render document search results',
|
|
22
|
+
resultRenderer: DocumentSearchRenderer,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
clientRegistry.registerTool({
|
|
26
|
+
name: 'save_document',
|
|
27
|
+
description: 'Render document save results',
|
|
28
|
+
resultRenderer: DocumentSaveRenderer,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
DocumentListRenderer,
|
|
33
|
+
DocumentReadRenderer,
|
|
34
|
+
DocumentSearchRenderer,
|
|
35
|
+
DocumentSaveRenderer,
|
|
36
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MCPContent } from '@chaaskit/shared';
|
|
2
|
+
|
|
3
|
+
export function getTextContent(content?: MCPContent[]): string | null {
|
|
4
|
+
if (!content) return null;
|
|
5
|
+
const firstText = content.find((item) => item.type === 'text' && typeof item.text === 'string');
|
|
6
|
+
return firstText?.text ?? null;
|
|
7
|
+
}
|
|
@@ -20,9 +20,14 @@ interface AuthContextType {
|
|
|
20
20
|
user: UserSession | null;
|
|
21
21
|
isLoading: boolean;
|
|
22
22
|
login: (email: string, password: string) => Promise<LoginResult>;
|
|
23
|
-
register: (
|
|
23
|
+
register: (
|
|
24
|
+
email: string,
|
|
25
|
+
password: string,
|
|
26
|
+
name?: string,
|
|
27
|
+
options?: { inviteToken?: string; referralCode?: string }
|
|
28
|
+
) => Promise<RegisterResult>;
|
|
24
29
|
logout: () => Promise<void>;
|
|
25
|
-
sendMagicLink: (email: string) => Promise<void>;
|
|
30
|
+
sendMagicLink: (email: string, inviteToken?: string) => Promise<void>;
|
|
26
31
|
verifyEmail: (code: string) => Promise<void>;
|
|
27
32
|
resendVerification: () => Promise<void>;
|
|
28
33
|
}
|
|
@@ -59,12 +64,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
59
64
|
return { requiresVerification: response.requiresVerification ?? false };
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
async function register(
|
|
67
|
+
async function register(
|
|
68
|
+
email: string,
|
|
69
|
+
password: string,
|
|
70
|
+
name?: string,
|
|
71
|
+
options?: { inviteToken?: string; referralCode?: string }
|
|
72
|
+
): Promise<RegisterResult> {
|
|
63
73
|
const response = await api.post<{
|
|
64
74
|
user: UserSession;
|
|
65
75
|
token: string;
|
|
66
76
|
requiresVerification?: boolean;
|
|
67
|
-
}>('/api/auth/register', { email, password, name });
|
|
77
|
+
}>('/api/auth/register', { email, password, name, ...options });
|
|
68
78
|
setUser(response.user);
|
|
69
79
|
return { requiresVerification: response.requiresVerification ?? false };
|
|
70
80
|
}
|
|
@@ -74,8 +84,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
74
84
|
setUser(null);
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
async function sendMagicLink(email: string) {
|
|
78
|
-
await api.post('/api/auth/magic-link', { email });
|
|
87
|
+
async function sendMagicLink(email: string, inviteToken?: string) {
|
|
88
|
+
await api.post('/api/auth/magic-link', { email, inviteToken });
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
async function verifyEmail(code: string) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PublicAppConfig } from '@chaaskit/shared';
|
|
3
3
|
|
|
4
4
|
// Declare global window property for SSR-injected config
|
|
5
5
|
declare global {
|
|
6
6
|
interface Window {
|
|
7
|
-
__CHAASKIT_CONFIG__?:
|
|
7
|
+
__CHAASKIT_CONFIG__?: PublicAppConfig;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ declare global {
|
|
|
13
13
|
* This allows the config to be available immediately on page load,
|
|
14
14
|
* avoiding flash of default values.
|
|
15
15
|
*/
|
|
16
|
-
function getInjectedConfig():
|
|
16
|
+
function getInjectedConfig(): PublicAppConfig | undefined {
|
|
17
17
|
if (typeof window !== 'undefined' && window.__CHAASKIT_CONFIG__) {
|
|
18
18
|
return window.__CHAASKIT_CONFIG__;
|
|
19
19
|
}
|
|
@@ -21,11 +21,12 @@ function getInjectedConfig(): AppConfig | undefined {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Default config - used as fallback while loading
|
|
24
|
-
const defaultConfig:
|
|
24
|
+
const defaultConfig: PublicAppConfig = {
|
|
25
25
|
app: {
|
|
26
26
|
name: 'AI Chat',
|
|
27
27
|
description: 'Your AI assistant',
|
|
28
28
|
url: 'http://localhost:5173',
|
|
29
|
+
basePath: '/chat',
|
|
29
30
|
},
|
|
30
31
|
ui: {
|
|
31
32
|
welcomeTitle: 'Welcome to AI Chat',
|
|
@@ -109,27 +110,15 @@ const defaultConfig: AppConfig = {
|
|
|
109
110
|
enabled: true,
|
|
110
111
|
expiresInMinutes: 15,
|
|
111
112
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
systemPrompt: 'You are a helpful AI assistant.',
|
|
118
|
-
maxTokens: 4096,
|
|
113
|
+
gating: {
|
|
114
|
+
mode: 'open',
|
|
115
|
+
waitlistEnabled: false,
|
|
116
|
+
},
|
|
117
|
+
isAdmin: false,
|
|
119
118
|
},
|
|
120
119
|
payments: {
|
|
121
120
|
enabled: false,
|
|
122
121
|
provider: 'stripe',
|
|
123
|
-
plans: [
|
|
124
|
-
{
|
|
125
|
-
id: 'free',
|
|
126
|
-
name: 'Free',
|
|
127
|
-
type: 'free',
|
|
128
|
-
params: {
|
|
129
|
-
monthlyMessageLimit: 20,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
122
|
},
|
|
134
123
|
legal: {
|
|
135
124
|
privacyPolicyUrl: '/privacy',
|
|
@@ -158,7 +147,6 @@ const defaultConfig: AppConfig = {
|
|
|
158
147
|
},
|
|
159
148
|
promptTemplates: {
|
|
160
149
|
enabled: true,
|
|
161
|
-
builtIn: [],
|
|
162
150
|
allowUserTemplates: true,
|
|
163
151
|
},
|
|
164
152
|
teams: {
|
|
@@ -166,9 +154,6 @@ const defaultConfig: AppConfig = {
|
|
|
166
154
|
},
|
|
167
155
|
documents: {
|
|
168
156
|
enabled: false,
|
|
169
|
-
storage: {
|
|
170
|
-
provider: 'database',
|
|
171
|
-
},
|
|
172
157
|
maxFileSizeMB: 10,
|
|
173
158
|
hybridThreshold: 1000,
|
|
174
159
|
acceptedTypes: ['text/plain', 'text/markdown', 'application/json'],
|
|
@@ -180,10 +165,19 @@ const defaultConfig: AppConfig = {
|
|
|
180
165
|
api: {
|
|
181
166
|
enabled: false,
|
|
182
167
|
},
|
|
168
|
+
credits: {
|
|
169
|
+
enabled: false,
|
|
170
|
+
expiryEnabled: false,
|
|
171
|
+
promoEnabled: false,
|
|
172
|
+
},
|
|
173
|
+
metering: {
|
|
174
|
+
enabled: false,
|
|
175
|
+
recordPromptCompletion: true,
|
|
176
|
+
},
|
|
183
177
|
};
|
|
184
178
|
|
|
185
179
|
interface ConfigContextValue {
|
|
186
|
-
config:
|
|
180
|
+
config: PublicAppConfig;
|
|
187
181
|
configLoaded: boolean;
|
|
188
182
|
}
|
|
189
183
|
|
|
@@ -199,7 +193,7 @@ interface ConfigProviderProps {
|
|
|
199
193
|
* If provided, the config will not be fetched from /api/config.
|
|
200
194
|
* Useful when config is available from SSR loaders.
|
|
201
195
|
*/
|
|
202
|
-
initialConfig?:
|
|
196
|
+
initialConfig?: PublicAppConfig;
|
|
203
197
|
}
|
|
204
198
|
|
|
205
199
|
export function ConfigProvider({ children, initialConfig }: ConfigProviderProps) {
|
|
@@ -207,7 +201,7 @@ export function ConfigProvider({ children, initialConfig }: ConfigProviderProps)
|
|
|
207
201
|
const injectedConfig = getInjectedConfig();
|
|
208
202
|
const preloadedConfig = initialConfig || injectedConfig;
|
|
209
203
|
|
|
210
|
-
const [config, setConfig] = useState<
|
|
204
|
+
const [config, setConfig] = useState<PublicAppConfig>(
|
|
211
205
|
preloadedConfig ? { ...defaultConfig, ...preloadedConfig } : defaultConfig
|
|
212
206
|
);
|
|
213
207
|
const [configLoaded, setConfigLoaded] = useState(!!preloadedConfig);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
|
+
import type { ToolCall, ToolResult } from '@chaaskit/shared';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Page extension configuration
|
|
@@ -31,7 +32,7 @@ export interface ToolExtension {
|
|
|
31
32
|
/** Description of the tool */
|
|
32
33
|
description: string;
|
|
33
34
|
/** Custom renderer for tool results */
|
|
34
|
-
resultRenderer?: ComponentType<{
|
|
35
|
+
resultRenderer?: ComponentType<{ toolCall: ToolCall; toolResult: ToolResult }>;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|