@agent-platform/ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +41 -0
  2. package/dist/components/agent/agent-container.d.ts +3 -0
  3. package/dist/components/agent/agent-container.js +47 -0
  4. package/dist/components/agent/agent-greeting.d.ts +3 -0
  5. package/dist/components/agent/agent-greeting.js +7 -0
  6. package/dist/components/agent/agent-header.d.ts +3 -0
  7. package/dist/components/agent/agent-header.js +9 -0
  8. package/dist/components/agent/agent-home-card.d.ts +3 -0
  9. package/dist/components/agent/agent-home-card.js +7 -0
  10. package/dist/components/agent/agent-home-cards.d.ts +9 -0
  11. package/dist/components/agent/agent-home-cards.js +11 -0
  12. package/dist/components/agent/agent-input.d.ts +3 -0
  13. package/dist/components/agent/agent-input.js +50 -0
  14. package/dist/components/agent/agent-popup-widget.d.ts +15 -0
  15. package/dist/components/agent/agent-popup-widget.js +49 -0
  16. package/dist/components/agent/agent-screen.d.ts +5 -0
  17. package/dist/components/agent/agent-screen.js +7 -0
  18. package/dist/components/agent/defaults.d.ts +11 -0
  19. package/dist/components/agent/defaults.js +23 -0
  20. package/dist/components/agent/index.d.ts +5 -0
  21. package/dist/components/agent/index.js +2 -0
  22. package/dist/components/agent/message/index.d.ts +8 -0
  23. package/dist/components/agent/message/index.js +4 -0
  24. package/dist/components/agent/message/markdown.d.ts +6 -0
  25. package/dist/components/agent/message/markdown.js +66 -0
  26. package/dist/components/agent/message/message-item.d.ts +7 -0
  27. package/dist/components/agent/message/message-item.js +30 -0
  28. package/dist/components/agent/message/message-list.d.ts +8 -0
  29. package/dist/components/agent/message/message-list.js +24 -0
  30. package/dist/components/agent/message/message-loading.d.ts +10 -0
  31. package/dist/components/agent/message/message-loading.js +16 -0
  32. package/dist/components/agent/message/tool-call-card.d.ts +9 -0
  33. package/dist/components/agent/message/tool-call-card.js +25 -0
  34. package/dist/components/agent/provider/agent-context.d.ts +3 -0
  35. package/dist/components/agent/provider/agent-context.js +10 -0
  36. package/dist/components/agent/provider/agent-provider.d.ts +10 -0
  37. package/dist/components/agent/provider/agent-provider.js +22 -0
  38. package/dist/components/agent/provider/index.d.ts +4 -0
  39. package/dist/components/agent/provider/index.js +2 -0
  40. package/dist/components/agent/provider/parse-sse-buffer.d.ts +9 -0
  41. package/dist/components/agent/provider/parse-sse-buffer.js +23 -0
  42. package/dist/components/agent/provider/types.d.ts +58 -0
  43. package/dist/components/agent/provider/types.js +1 -0
  44. package/dist/components/agent/provider/use-agent-chat.d.ts +6 -0
  45. package/dist/components/agent/provider/use-agent-chat.js +452 -0
  46. package/dist/components/agent/types.d.ts +56 -0
  47. package/dist/components/agent/types.js +1 -0
  48. package/dist/components/index.d.ts +7 -0
  49. package/dist/components/index.js +9 -0
  50. package/dist/components/ui/badge.d.ts +9 -0
  51. package/dist/components/ui/badge.js +24 -0
  52. package/dist/components/ui/button.d.ts +10 -0
  53. package/dist/components/ui/button.js +35 -0
  54. package/dist/components/ui/card.d.ts +9 -0
  55. package/dist/components/ui/card.js +24 -0
  56. package/dist/components/ui/input.d.ts +3 -0
  57. package/dist/components/ui/input.js +6 -0
  58. package/dist/components/ui/label.d.ts +4 -0
  59. package/dist/components/ui/label.js +7 -0
  60. package/dist/components/ui/separator.d.ts +4 -0
  61. package/dist/components/ui/separator.js +8 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +1 -0
  64. package/dist/lib/index.d.ts +1 -0
  65. package/dist/lib/index.js +1 -0
  66. package/dist/lib/utils.d.ts +2 -0
  67. package/dist/lib/utils.js +5 -0
  68. package/dist/styles/globals.css +157 -0
  69. package/package.json +59 -0
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { AgentContext } from './agent-context';
4
+ import { useAgentChatInternal } from './use-agent-chat';
5
+ /**
6
+ * エージェントチャット機能を提供する内部Provider
7
+ * 公開APIは AgentScreen / AgentPopupWidget を使用する。
8
+ */
9
+ export function AgentProvider({ children, endpoint, agentId, apiBaseUrl, executeClientTool, onError, authToken, getAuthToken, }) {
10
+ const value = useAgentChatInternal({
11
+ config: {
12
+ endpoint,
13
+ agentId,
14
+ apiBaseUrl,
15
+ executeClientTool,
16
+ onError,
17
+ authToken,
18
+ getAuthToken,
19
+ },
20
+ });
21
+ return _jsx(AgentContext.Provider, { value: value, children: children });
22
+ }
@@ -0,0 +1,4 @@
1
+ export { useAgentContext } from './agent-context';
2
+ export type { AgentProviderProps } from './agent-provider';
3
+ export { AgentProvider } from './agent-provider';
4
+ export type { AgentContextValue, AgentMessage, AgentProviderConfig, AgentStreamEvent, ClientToolExecutor, FinishPayload, MessageContentPart, ToolCallData, ToolCallState, ToolCallStatus, ToolResultData, } from './types';
@@ -0,0 +1,2 @@
1
+ export { useAgentContext } from './agent-context';
2
+ export { AgentProvider } from './agent-provider';
@@ -0,0 +1,9 @@
1
+ import type { AgentStreamEvent } from './types';
2
+ export interface ParseResult {
3
+ events: AgentStreamEvent[];
4
+ remaining: string;
5
+ }
6
+ /**
7
+ * SSEバッファからイベントをパースする
8
+ */
9
+ export declare function parseSSEBuffer(buffer: string): ParseResult;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SSEバッファからイベントをパースする
3
+ */
4
+ export function parseSSEBuffer(buffer) {
5
+ const events = [];
6
+ const lines = buffer.split('\n');
7
+ const remaining = lines.pop() || '';
8
+ for (const line of lines) {
9
+ if (!line.startsWith('data: '))
10
+ continue;
11
+ const jsonStr = line.slice(6);
12
+ if (!jsonStr)
13
+ continue;
14
+ try {
15
+ const event = JSON.parse(jsonStr);
16
+ events.push(event);
17
+ }
18
+ catch (e) {
19
+ console.warn('Failed to parse SSE event:', e);
20
+ }
21
+ }
22
+ return { events, remaining };
23
+ }
@@ -0,0 +1,58 @@
1
+ export type { AgentStreamEvent, ToolCallData, ToolResultData, FinishPayload, MessageContentPart, AgentMessage, } from '@agent-platform/server';
2
+ /**
3
+ * ツール実行状態
4
+ */
5
+ export type ToolCallStatus = 'pending' | 'executing' | 'completed' | 'error';
6
+ /**
7
+ * ツールコール状態(UI表示用)
8
+ */
9
+ export interface ToolCallState {
10
+ toolCall: import('@agent-platform/server').ToolCallData;
11
+ status: ToolCallStatus;
12
+ result?: unknown;
13
+ error?: string;
14
+ }
15
+ /**
16
+ * クライアントツール実行関数型
17
+ */
18
+ export type ClientToolExecutor = (toolCall: import('@agent-platform/server').ToolCallData) => Promise<{
19
+ output: unknown;
20
+ isError?: boolean;
21
+ }>;
22
+ /**
23
+ * AgentProvider設定
24
+ */
25
+ export interface AgentProviderConfig {
26
+ endpoint: string;
27
+ agentId?: string;
28
+ /**
29
+ * クライアント実行ツールが呼び出すAPIのベースURL
30
+ * tool-callのapiEndpoint.pathと組み合わせて使用
31
+ * 例: apiBaseUrl="http://localhost:3001/api" + path="/candidates/{id}"
32
+ */
33
+ apiBaseUrl?: string;
34
+ /**
35
+ * @deprecated apiBaseUrlを使用してください。
36
+ * executeOn: 'client' のツールをブラウザ側で実行するコールバック
37
+ */
38
+ executeClientTool?: ClientToolExecutor;
39
+ onError?: (error: string) => void;
40
+ /** 静的なJWTトークン */
41
+ authToken?: string;
42
+ /** 動的にトークンを取得する関数(authTokenより優先) */
43
+ getAuthToken?: () => string | Promise<string>;
44
+ }
45
+ /**
46
+ * AgentContext値
47
+ */
48
+ export interface AgentContextValue {
49
+ messages: import('@agent-platform/server').AgentMessage[];
50
+ threadId: string | null;
51
+ isLoading: boolean;
52
+ error: string | null;
53
+ pendingToolCalls: ToolCallState[];
54
+ sendMessage: (message: string) => Promise<void>;
55
+ loadThread: (threadId: string) => Promise<void>;
56
+ clearChat: () => void;
57
+ config: AgentProviderConfig;
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { AgentContextValue, AgentProviderConfig } from './types';
2
+ interface UseAgentChatOptions {
3
+ config: AgentProviderConfig;
4
+ }
5
+ export declare function useAgentChatInternal(options: UseAgentChatOptions): AgentContextValue;
6
+ export {};
@@ -0,0 +1,452 @@
1
+ 'use client';
2
+ import { useCallback, useRef, useState } from 'react';
3
+ import { parseSSEBuffer } from './parse-sse-buffer';
4
+ /** クライアントツール自動継続の最大回数 */
5
+ const MAX_CONTINUE_ROUNDS = 10;
6
+ // --- イベントハンドラ ---
7
+ function handleTextDelta(text, accumulatedText, toolCalls, assistantMessageId, setMessages) {
8
+ const newText = accumulatedText + text;
9
+ setMessages((prev) => {
10
+ const updated = [...prev];
11
+ const idx = updated.findIndex((m) => m.id === assistantMessageId);
12
+ const existing = idx >= 0 ? updated[idx] : undefined;
13
+ if (existing) {
14
+ const content = [{ type: 'text', text: newText }];
15
+ for (const tc of toolCalls) {
16
+ content.push({ type: 'tool-call', toolCall: tc });
17
+ }
18
+ updated[idx] = { id: existing.id, role: existing.role, content, isStreaming: true };
19
+ }
20
+ return updated;
21
+ });
22
+ return newText;
23
+ }
24
+ function handleToolCall(tc, accumulatedText, toolCalls, assistantMessageId, setMessages, setPendingToolCalls) {
25
+ toolCalls.push(tc);
26
+ setPendingToolCalls((prev) => {
27
+ const newMap = new Map(prev);
28
+ newMap.set(tc.toolCallId, { toolCall: tc, status: 'pending' });
29
+ return newMap;
30
+ });
31
+ setMessages((prev) => {
32
+ const updated = [...prev];
33
+ const idx = updated.findIndex((m) => m.id === assistantMessageId);
34
+ const existing = idx >= 0 ? updated[idx] : undefined;
35
+ if (existing) {
36
+ const content = [];
37
+ if (accumulatedText) {
38
+ content.push({ type: 'text', text: accumulatedText });
39
+ }
40
+ for (const t of toolCalls) {
41
+ content.push({ type: 'tool-call', toolCall: t });
42
+ }
43
+ updated[idx] = { id: existing.id, role: existing.role, content, isStreaming: true };
44
+ }
45
+ return updated;
46
+ });
47
+ }
48
+ function handleToolResult(tr, serverResolvedToolIds, setPendingToolCalls) {
49
+ serverResolvedToolIds.add(tr.toolCallId);
50
+ setPendingToolCalls((prev) => {
51
+ const newMap = new Map(prev);
52
+ const state = newMap.get(tr.toolCallId);
53
+ if (state) {
54
+ newMap.set(tr.toolCallId, {
55
+ ...state,
56
+ status: tr.isError ? 'error' : 'completed',
57
+ result: tr.result,
58
+ error: tr.isError ? String(tr.result) : undefined,
59
+ });
60
+ }
61
+ return newMap;
62
+ });
63
+ }
64
+ function handleFinish(assistantMessageId, setMessages) {
65
+ setMessages((prev) => {
66
+ const updated = [...prev];
67
+ const idx = updated.findIndex((m) => m.id === assistantMessageId);
68
+ const existing = idx >= 0 ? updated[idx] : undefined;
69
+ if (existing) {
70
+ updated[idx] = { id: existing.id, role: existing.role, content: existing.content, isStreaming: false };
71
+ }
72
+ return updated;
73
+ });
74
+ }
75
+ // --- メインフック ---
76
+ /**
77
+ * apiEndpointからAPIを実行
78
+ */
79
+ async function executeApiEndpoint(toolCall, apiBaseUrl, authHeaders) {
80
+ console.log('[executeApiEndpoint] ─────────────────────────────────────────');
81
+ console.log('[executeApiEndpoint] toolCall:', JSON.stringify(toolCall, null, 2));
82
+ const { apiEndpoint, input } = toolCall;
83
+ if (!apiEndpoint) {
84
+ console.log('[executeApiEndpoint] ERROR: No API endpoint defined');
85
+ return { output: 'No API endpoint defined for this tool', isError: true };
86
+ }
87
+ console.log('[executeApiEndpoint] apiEndpoint:', JSON.stringify(apiEndpoint, null, 2));
88
+ console.log('[executeApiEndpoint] input:', JSON.stringify(input, null, 2));
89
+ const inputData = (input ?? {});
90
+ let path = apiEndpoint.path;
91
+ // パスパラメータを置換 (e.g., /candidates/{id} -> /candidates/123)
92
+ const pathParamRegex = /\{(\w+)\}/g;
93
+ let match;
94
+ const usedParams = new Set();
95
+ // biome-ignore lint/suspicious/noAssignInExpressions: 標準的なパターン
96
+ while ((match = pathParamRegex.exec(apiEndpoint.path)) !== null) {
97
+ const paramName = match[1];
98
+ if (!paramName)
99
+ continue;
100
+ const value = inputData[paramName];
101
+ if (value !== undefined) {
102
+ path = path.replace(`{${paramName}}`, encodeURIComponent(String(value)));
103
+ usedParams.add(paramName);
104
+ }
105
+ }
106
+ // クエリパラメータを構築 (GET/DELETE時)
107
+ let url = `${apiBaseUrl}${path}`;
108
+ const queryParams = {};
109
+ const bodyData = {};
110
+ for (const [key, value] of Object.entries(inputData)) {
111
+ if (usedParams.has(key) || value === undefined)
112
+ continue;
113
+ if (['GET', 'DELETE'].includes(apiEndpoint.method)) {
114
+ queryParams[key] = String(value);
115
+ }
116
+ else {
117
+ bodyData[key] = value;
118
+ }
119
+ }
120
+ const queryString = new URLSearchParams(queryParams).toString();
121
+ if (queryString)
122
+ url += `?${queryString}`;
123
+ console.log('[executeApiEndpoint] Final URL:', url);
124
+ console.log('[executeApiEndpoint] Method:', apiEndpoint.method);
125
+ console.log('[executeApiEndpoint] Query params:', JSON.stringify(queryParams, null, 2));
126
+ console.log('[executeApiEndpoint] Body data:', JSON.stringify(bodyData, null, 2));
127
+ try {
128
+ const fetchOptions = {
129
+ method: apiEndpoint.method,
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ ...authHeaders,
133
+ },
134
+ credentials: 'include',
135
+ };
136
+ if (['POST', 'PUT', 'PATCH'].includes(apiEndpoint.method) && Object.keys(bodyData).length > 0) {
137
+ fetchOptions.body = JSON.stringify(bodyData);
138
+ console.log('[executeApiEndpoint] Request body:', fetchOptions.body);
139
+ }
140
+ console.log('[executeApiEndpoint] Sending request...');
141
+ const response = await fetch(url, fetchOptions);
142
+ const text = await response.text();
143
+ console.log('[executeApiEndpoint] Response status:', response.status);
144
+ console.log('[executeApiEndpoint] Response text:', text.slice(0, 500));
145
+ let data;
146
+ try {
147
+ data = JSON.parse(text);
148
+ }
149
+ catch {
150
+ data = { raw: text, status: response.status };
151
+ }
152
+ if (!response.ok) {
153
+ return { output: data, isError: true };
154
+ }
155
+ return { output: data };
156
+ }
157
+ catch (e) {
158
+ return {
159
+ output: e instanceof Error ? e.message : 'Network error',
160
+ isError: true,
161
+ };
162
+ }
163
+ }
164
+ export function useAgentChatInternal(options) {
165
+ const { config } = options;
166
+ const { endpoint, agentId = 'assistant', apiBaseUrl, executeClientTool, onError, authToken, getAuthToken } = config;
167
+ const [messages, setMessages] = useState([]);
168
+ const [threadId, setThreadId] = useState(null);
169
+ const [isLoading, setIsLoading] = useState(false);
170
+ const [error, setError] = useState(null);
171
+ const [pendingToolCalls, setPendingToolCalls] = useState(new Map());
172
+ const abortControllerRef = useRef(null);
173
+ const buildHeaders = useCallback(async () => {
174
+ const headers = { 'Content-Type': 'application/json' };
175
+ const token = getAuthToken ? await getAuthToken() : authToken;
176
+ if (token) {
177
+ headers['Authorization'] = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
178
+ }
179
+ return headers;
180
+ }, [authToken, getAuthToken]);
181
+ const handleError = useCallback((errorMessage) => {
182
+ setError(errorMessage);
183
+ onError?.(errorMessage);
184
+ }, [onError]);
185
+ const processStream = useCallback(async (response, assistantMessageId, onThreadId) => {
186
+ const reader = response.body?.getReader();
187
+ if (!reader)
188
+ return { clientToolResults: [], clientToolCalls: [] };
189
+ const decoder = new TextDecoder();
190
+ let buffer = '';
191
+ let accumulatedText = '';
192
+ const toolCalls = [];
193
+ const serverResolvedToolIds = new Set();
194
+ const clientToolResults = [];
195
+ try {
196
+ while (true) {
197
+ const { done, value } = await reader.read();
198
+ if (done)
199
+ break;
200
+ buffer += decoder.decode(value, { stream: true });
201
+ const { events, remaining } = parseSSEBuffer(buffer);
202
+ buffer = remaining;
203
+ for (const event of events) {
204
+ switch (event.type) {
205
+ case 'thread-id':
206
+ onThreadId?.(event.payload.threadId);
207
+ break;
208
+ case 'text-delta':
209
+ accumulatedText = handleTextDelta(event.payload.text, accumulatedText, toolCalls, assistantMessageId, setMessages);
210
+ break;
211
+ case 'tool-call':
212
+ console.log('[processStream] Received tool-call event:', JSON.stringify(event.payload, null, 2));
213
+ handleToolCall(event.payload, accumulatedText, toolCalls, assistantMessageId, setMessages, setPendingToolCalls);
214
+ break;
215
+ case 'tool-result':
216
+ handleToolResult(event.payload, serverResolvedToolIds, setPendingToolCalls);
217
+ break;
218
+ case 'finish':
219
+ handleFinish(assistantMessageId, setMessages);
220
+ break;
221
+ case 'error':
222
+ handleError(event.payload.error);
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ }
228
+ finally {
229
+ reader.releaseLock();
230
+ }
231
+ // クライアントツール実行
232
+ // apiBaseUrl + apiEndpointがあれば自動実行、なければ従来のexecuteClientTool
233
+ console.log('[processStream] ═════════════════════════════════════════');
234
+ console.log('[processStream] Client tool execution phase');
235
+ console.log('[processStream] Total tool calls received:', toolCalls.length);
236
+ console.log('[processStream] Server resolved tool IDs:', Array.from(serverResolvedToolIds));
237
+ console.log('[processStream] apiBaseUrl:', apiBaseUrl);
238
+ const executedClientToolCalls = [];
239
+ const unresolvedCalls = toolCalls.filter((tc) => !serverResolvedToolIds.has(tc.toolCallId));
240
+ console.log('[processStream] Unresolved calls (to execute on client):', unresolvedCalls.length);
241
+ for (const tc of unresolvedCalls) {
242
+ console.log('[processStream] - toolName:', tc.toolName, 'toolCallId:', tc.toolCallId);
243
+ console.log('[processStream] input:', JSON.stringify(tc.input, null, 2));
244
+ console.log('[processStream] apiEndpoint:', JSON.stringify(tc.apiEndpoint, null, 2));
245
+ }
246
+ // 認証ヘッダーを構築
247
+ const authHeaders = {};
248
+ const token = getAuthToken ? await getAuthToken() : authToken;
249
+ if (token) {
250
+ authHeaders['Authorization'] = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
251
+ }
252
+ for (const tc of unresolvedCalls) {
253
+ // apiBaseUrlとapiEndpointがある場合は自動実行
254
+ const canAutoExecute = apiBaseUrl && tc.apiEndpoint;
255
+ // 従来のexecuteClientToolがある場合はそれを使用
256
+ const canLegacyExecute = executeClientTool;
257
+ if (!canAutoExecute && !canLegacyExecute) {
258
+ // 実行手段がない場合はスキップ
259
+ continue;
260
+ }
261
+ executedClientToolCalls.push(tc);
262
+ setPendingToolCalls((prev) => {
263
+ const newMap = new Map(prev);
264
+ const state = newMap.get(tc.toolCallId);
265
+ if (state) {
266
+ newMap.set(tc.toolCallId, { ...state, status: 'executing' });
267
+ }
268
+ return newMap;
269
+ });
270
+ try {
271
+ let result;
272
+ if (canAutoExecute) {
273
+ // 新しい自動実行パス
274
+ result = await executeApiEndpoint(tc, apiBaseUrl, authHeaders);
275
+ }
276
+ else if (canLegacyExecute) {
277
+ // 従来のexecuteClientToolを使用(後方互換性)
278
+ result = await executeClientTool(tc);
279
+ }
280
+ else {
281
+ result = { output: 'No execution method available', isError: true };
282
+ }
283
+ const isError = result.isError ?? false;
284
+ setPendingToolCalls((prev) => {
285
+ const newMap = new Map(prev);
286
+ newMap.set(tc.toolCallId, {
287
+ toolCall: tc,
288
+ status: isError ? 'error' : 'completed',
289
+ result: result.output,
290
+ error: isError ? String(result.output) : undefined,
291
+ });
292
+ return newMap;
293
+ });
294
+ clientToolResults.push({ toolCallId: tc.toolCallId, result: result.output, isError });
295
+ }
296
+ catch (e) {
297
+ const errorMsg = e instanceof Error ? e.message : 'Unknown error';
298
+ setPendingToolCalls((prev) => {
299
+ const newMap = new Map(prev);
300
+ newMap.set(tc.toolCallId, { toolCall: tc, status: 'error', error: errorMsg });
301
+ return newMap;
302
+ });
303
+ clientToolResults.push({ toolCallId: tc.toolCallId, result: errorMsg, isError: true });
304
+ }
305
+ }
306
+ return { clientToolResults, clientToolCalls: executedClientToolCalls };
307
+ }, [apiBaseUrl, executeClientTool, authToken, getAuthToken, handleError]);
308
+ const sendMessage = useCallback(async (message) => {
309
+ if (!message.trim() || isLoading)
310
+ return;
311
+ setIsLoading(true);
312
+ setError(null);
313
+ const userMessageId = `user-${Date.now()}`;
314
+ const userMessage = {
315
+ id: userMessageId,
316
+ role: 'user',
317
+ content: [{ type: 'text', text: message }],
318
+ };
319
+ setMessages((prev) => [...prev, userMessage]);
320
+ const assistantMessageId = `assistant-${Date.now()}`;
321
+ const assistantMessage = {
322
+ id: assistantMessageId,
323
+ role: 'assistant',
324
+ content: [],
325
+ isStreaming: true,
326
+ };
327
+ setMessages((prev) => [...prev, assistantMessage]);
328
+ try {
329
+ abortControllerRef.current = new AbortController();
330
+ const headers = await buildHeaders();
331
+ const response = await fetch(`${endpoint}/chat`, {
332
+ method: 'POST',
333
+ headers,
334
+ credentials: 'include',
335
+ body: JSON.stringify({ threadId, message, agentId }),
336
+ signal: abortControllerRef.current.signal,
337
+ });
338
+ if (!response.ok) {
339
+ let errorMessage = 'Failed to send message';
340
+ try {
341
+ const contentType = response.headers.get('content-type');
342
+ if (contentType?.includes('application/json')) {
343
+ const errorData = await response.json();
344
+ errorMessage =
345
+ typeof errorData?.error === 'string'
346
+ ? errorData.error
347
+ : typeof errorData?.message === 'string'
348
+ ? errorData.message
349
+ : errorMessage;
350
+ }
351
+ }
352
+ catch (e) {
353
+ console.warn('Failed to parse error response:', e);
354
+ }
355
+ throw new Error(errorMessage);
356
+ }
357
+ let streamResult = await processStream(response, assistantMessageId, setThreadId);
358
+ // ツール継続ループ
359
+ let continueRound = 0;
360
+ while (streamResult.clientToolResults.length > 0 && continueRound < MAX_CONTINUE_ROUNDS) {
361
+ continueRound++;
362
+ const continueMessageId = `assistant-${Date.now()}-${continueRound}`;
363
+ setMessages((prev) => [
364
+ ...prev,
365
+ { id: continueMessageId, role: 'assistant', content: [], isStreaming: true },
366
+ ]);
367
+ const continueHeaders = await buildHeaders();
368
+ const continueResponse = await fetch(`${endpoint}/chat/continue`, {
369
+ method: 'POST',
370
+ headers: continueHeaders,
371
+ credentials: 'include',
372
+ body: JSON.stringify({
373
+ threadId,
374
+ agentId,
375
+ toolResults: streamResult.clientToolResults,
376
+ toolCalls: streamResult.clientToolCalls.map((tc) => ({
377
+ toolCallId: tc.toolCallId,
378
+ toolName: tc.toolName,
379
+ args: tc.input,
380
+ })),
381
+ }),
382
+ signal: abortControllerRef.current?.signal,
383
+ });
384
+ if (!continueResponse.ok) {
385
+ throw new Error('Failed to continue agent after tool execution');
386
+ }
387
+ streamResult = await processStream(continueResponse, continueMessageId, setThreadId);
388
+ }
389
+ if (continueRound >= MAX_CONTINUE_ROUNDS && streamResult.clientToolResults.length > 0) {
390
+ handleError('Tool execution loop exceeded maximum rounds');
391
+ }
392
+ }
393
+ catch (e) {
394
+ if (e instanceof Error && e.name === 'AbortError')
395
+ return;
396
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
397
+ handleError(errorMessage);
398
+ }
399
+ finally {
400
+ setIsLoading(false);
401
+ abortControllerRef.current = null;
402
+ }
403
+ }, [isLoading, threadId, endpoint, agentId, processStream, handleError, buildHeaders]);
404
+ const loadThread = useCallback(async (loadThreadId) => {
405
+ setIsLoading(true);
406
+ setError(null);
407
+ try {
408
+ const threadHeaders = await buildHeaders();
409
+ const response = await fetch(`${endpoint}/threads/${loadThreadId}/messages`, {
410
+ headers: threadHeaders,
411
+ credentials: 'include',
412
+ });
413
+ if (!response.ok) {
414
+ throw new Error('Failed to load thread');
415
+ }
416
+ const data = await response.json();
417
+ setThreadId(loadThreadId);
418
+ setMessages(data.messages?.map((m) => ({
419
+ id: m.id,
420
+ role: m.role,
421
+ content: m.content,
422
+ isStreaming: false,
423
+ })) || []);
424
+ }
425
+ catch (e) {
426
+ setError(e instanceof Error ? e.message : 'Unknown error');
427
+ }
428
+ finally {
429
+ setIsLoading(false);
430
+ }
431
+ }, [endpoint, buildHeaders]);
432
+ const clearChat = useCallback(() => {
433
+ if (abortControllerRef.current) {
434
+ abortControllerRef.current.abort();
435
+ }
436
+ setMessages([]);
437
+ setThreadId(null);
438
+ setError(null);
439
+ setPendingToolCalls(new Map());
440
+ }, []);
441
+ return {
442
+ messages,
443
+ threadId,
444
+ isLoading,
445
+ error,
446
+ pendingToolCalls: Array.from(pendingToolCalls.values()),
447
+ sendMessage,
448
+ loadThread,
449
+ clearChat,
450
+ config,
451
+ };
452
+ }
@@ -0,0 +1,56 @@
1
+ import type { ReactNode } from "react";
2
+ export interface AgentHeaderProps {
3
+ historyLabel?: string;
4
+ onHistoryClick?: () => void;
5
+ onSettingsClick?: () => void;
6
+ onNewChatClick?: () => void;
7
+ newChatLabel?: string;
8
+ settingsAriaLabel?: string;
9
+ className?: string;
10
+ }
11
+ export interface AgentGreetingProps {
12
+ greeting?: string;
13
+ subtext?: string;
14
+ className?: string;
15
+ }
16
+ export interface AgentHomeCardProps {
17
+ id: string;
18
+ icon: ReactNode;
19
+ title: string;
20
+ description: string;
21
+ onClick?: () => void;
22
+ className?: string;
23
+ }
24
+ export interface AgentInputProps {
25
+ value?: string;
26
+ onChange?: (value: string) => void;
27
+ onSend?: (message: string) => void;
28
+ onAttach?: () => void;
29
+ placeholder?: string;
30
+ disabled?: boolean;
31
+ isLoading?: boolean;
32
+ className?: string;
33
+ }
34
+ export interface AgentContainerProps {
35
+ showHeader?: boolean;
36
+ headerProps?: Omit<AgentHeaderProps, "className">;
37
+ showGreeting?: boolean;
38
+ greetingProps?: AgentGreetingProps;
39
+ showHomeCards?: boolean;
40
+ cards?: AgentHomeCardProps[];
41
+ onCardClick?: (cardId: string) => void;
42
+ /**
43
+ * ツール名→日本語ラベルのマッピング
44
+ * DEFAULT_TOOL_NAME_LABELSとマージされる
45
+ */
46
+ toolNameLabels?: Record<string, string>;
47
+ /**
48
+ * メッセージが存在する場合に自動でMessageList+errorを表示するか
49
+ * デフォルト: true
50
+ */
51
+ autoShowMessages?: boolean;
52
+ showInput?: boolean;
53
+ inputProps?: Omit<AgentInputProps, "className">;
54
+ className?: string;
55
+ children?: ReactNode;
56
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export * from './ui/button';
2
+ export * from './ui/input';
3
+ export * from './ui/label';
4
+ export * from './ui/card';
5
+ export * from './ui/badge';
6
+ export * from './ui/separator';
7
+ export * from './agent';
@@ -0,0 +1,9 @@
1
+ // UI Components
2
+ export * from './ui/button';
3
+ export * from './ui/input';
4
+ export * from './ui/label';
5
+ export * from './ui/card';
6
+ export * from './ui/badge';
7
+ export * from './ui/separator';
8
+ // Agent Components
9
+ export * from './agent';
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const badgeVariants: (props?: ({
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
7
+ asChild?: boolean;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ export { Badge, badgeVariants };