@devicai/ui 0.2.0 → 0.4.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.
Files changed (54) hide show
  1. package/dist/cjs/api/client.js +31 -15
  2. package/dist/cjs/api/client.js.map +1 -1
  3. package/dist/cjs/components/AICommandBar/AICommandBar.js +314 -0
  4. package/dist/cjs/components/AICommandBar/AICommandBar.js.map +1 -0
  5. package/dist/cjs/components/AICommandBar/useAICommandBar.js +595 -0
  6. package/dist/cjs/components/AICommandBar/useAICommandBar.js.map +1 -0
  7. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +73 -5
  8. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  9. package/dist/cjs/components/ChatDrawer/ChatMessages.js +5 -2
  10. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  11. package/dist/cjs/components/Feedback/FeedbackModal.js +87 -0
  12. package/dist/cjs/components/Feedback/FeedbackModal.js.map +1 -0
  13. package/dist/cjs/components/Feedback/MessageActions.js +74 -0
  14. package/dist/cjs/components/Feedback/MessageActions.js.map +1 -0
  15. package/dist/cjs/index.js +9 -0
  16. package/dist/cjs/index.js.map +1 -1
  17. package/dist/cjs/styles.css +1 -1
  18. package/dist/esm/api/client.d.ts +10 -2
  19. package/dist/esm/api/client.js +31 -15
  20. package/dist/esm/api/client.js.map +1 -1
  21. package/dist/esm/api/types.d.ts +25 -0
  22. package/dist/esm/components/AICommandBar/AICommandBar.d.ts +22 -0
  23. package/dist/esm/components/AICommandBar/AICommandBar.js +312 -0
  24. package/dist/esm/components/AICommandBar/AICommandBar.js.map +1 -0
  25. package/dist/esm/components/AICommandBar/AICommandBar.types.d.ts +321 -0
  26. package/dist/esm/components/AICommandBar/index.d.ts +3 -0
  27. package/dist/esm/components/AICommandBar/useAICommandBar.d.ts +57 -0
  28. package/dist/esm/components/AICommandBar/useAICommandBar.js +592 -0
  29. package/dist/esm/components/AICommandBar/useAICommandBar.js.map +1 -0
  30. package/dist/esm/components/AutocompleteInput/AutocompleteInput.d.ts +4 -0
  31. package/dist/esm/components/AutocompleteInput/AutocompleteInput.types.d.ts +50 -0
  32. package/dist/esm/components/AutocompleteInput/index.d.ts +4 -0
  33. package/dist/esm/components/AutocompleteInput/useAutocomplete.d.ts +29 -0
  34. package/dist/esm/components/ChatDrawer/ChatDrawer.d.ts +4 -2
  35. package/dist/esm/components/ChatDrawer/ChatDrawer.js +74 -6
  36. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  37. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +36 -0
  38. package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +2 -1
  39. package/dist/esm/components/ChatDrawer/ChatMessages.js +5 -2
  40. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  41. package/dist/esm/components/ChatDrawer/index.d.ts +1 -1
  42. package/dist/esm/components/Feedback/Feedback.types.d.ts +50 -0
  43. package/dist/esm/components/Feedback/FeedbackModal.d.ts +5 -0
  44. package/dist/esm/components/Feedback/FeedbackModal.js +85 -0
  45. package/dist/esm/components/Feedback/FeedbackModal.js.map +1 -0
  46. package/dist/esm/components/Feedback/MessageActions.d.ts +5 -0
  47. package/dist/esm/components/Feedback/MessageActions.js +72 -0
  48. package/dist/esm/components/Feedback/MessageActions.js.map +1 -0
  49. package/dist/esm/components/Feedback/index.d.ts +3 -0
  50. package/dist/esm/index.d.ts +6 -2
  51. package/dist/esm/index.js +4 -0
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/styles.css +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,595 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ require('react/jsx-runtime');
5
+ var DevicContext = require('../../provider/DevicContext.js');
6
+ var client = require('../../api/client.js');
7
+ var usePolling = require('../../hooks/usePolling.js');
8
+ var useModelInterface = require('../../hooks/useModelInterface.js');
9
+
10
+ /**
11
+ * Parse a shortcut string like "cmd+j" into its components
12
+ */
13
+ function parseShortcut(shortcut) {
14
+ const parts = shortcut.toLowerCase().split('+');
15
+ const key = parts.pop() || '';
16
+ const modifiers = parts;
17
+ return { key, modifiers };
18
+ }
19
+ /**
20
+ * Check if a keyboard event matches a shortcut string
21
+ */
22
+ function matchShortcut(event, shortcut) {
23
+ const { key, modifiers } = parseShortcut(shortcut);
24
+ const keyMatch = event.key.toLowerCase() === key;
25
+ const cmdMatch = modifiers.includes('cmd') === (event.metaKey || event.ctrlKey);
26
+ const shiftMatch = modifiers.includes('shift') === event.shiftKey;
27
+ const altMatch = modifiers.includes('alt') === event.altKey;
28
+ return keyMatch && cmdMatch && shiftMatch && altMatch;
29
+ }
30
+ /**
31
+ * Format a shortcut string for display
32
+ */
33
+ function formatShortcut(shortcut) {
34
+ const isMac = typeof navigator !== 'undefined' && /Mac/.test(navigator.platform);
35
+ return shortcut
36
+ .replace(/cmd/gi, isMac ? '\u2318' : 'Ctrl')
37
+ .replace(/shift/gi, '\u21E7')
38
+ .replace(/alt/gi, isMac ? '\u2325' : 'Alt')
39
+ .replace(/\+/g, ' ')
40
+ .replace(/([a-z])/gi, (match) => match.toUpperCase());
41
+ }
42
+ /**
43
+ * Format tool name to human-readable (fallback when no summary)
44
+ */
45
+ function formatToolName(toolName) {
46
+ // Convert snake_case or camelCase to human-readable
47
+ return toolName
48
+ .replace(/_/g, ' ')
49
+ .replace(/([A-Z])/g, ' $1')
50
+ .trim()
51
+ .toLowerCase()
52
+ .replace(/^./, (c) => c.toUpperCase());
53
+ }
54
+ /**
55
+ * Hook for managing AICommandBar state and behavior
56
+ */
57
+ function useAICommandBar(options) {
58
+ const { assistantId, apiKey: propsApiKey, baseUrl: propsBaseUrl, tenantId, tenantMetadata, options: barOptions = {}, isVisible: controlledVisible, onVisibilityChange, onExecute = 'callback', chatDrawerRef, onResponse, modelInterfaceTools = [], onSubmit, onToolCall, onError, onOpen, onClose, } = options;
59
+ const { shortcut } = barOptions;
60
+ // Get context
61
+ const context = DevicContext.useOptionalDevicContext();
62
+ const apiKey = propsApiKey || context?.apiKey;
63
+ const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';
64
+ const resolvedTenantId = tenantId || context?.tenantId;
65
+ const resolvedTenantMetadata = { ...context?.tenantMetadata, ...tenantMetadata };
66
+ // Visibility state
67
+ const [internalVisible, setInternalVisible] = React.useState(false);
68
+ const isVisible = controlledVisible ?? internalVisible;
69
+ // Input state
70
+ const [inputValue, setInputValue] = React.useState('');
71
+ const inputRef = React.useRef(null);
72
+ // Processing state
73
+ const [isProcessing, setIsProcessing] = React.useState(false);
74
+ const [toolCalls, setToolCalls] = React.useState([]);
75
+ const [currentToolSummary, setCurrentToolSummary] = React.useState(null);
76
+ // Result state
77
+ const [result, setResult] = React.useState(null);
78
+ const [chatUid, setChatUid] = React.useState(null);
79
+ const [error, setError] = React.useState(null);
80
+ // Polling state
81
+ const [shouldPoll, setShouldPoll] = React.useState(false);
82
+ // History state
83
+ const enableHistory = barOptions.enableHistory !== false; // default true
84
+ const maxHistoryItems = barOptions.maxHistoryItems ?? 50;
85
+ const historyStorageKey = barOptions.historyStorageKey ?? 'devic-command-bar-history';
86
+ const showHistoryCommand = barOptions.showHistoryCommand !== false; // default true
87
+ const [history, setHistory] = React.useState(() => {
88
+ if (!enableHistory || typeof window === 'undefined')
89
+ return [];
90
+ try {
91
+ const stored = localStorage.getItem(historyStorageKey);
92
+ return stored ? JSON.parse(stored) : [];
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ });
98
+ const [historyIndex, setHistoryIndex] = React.useState(-1);
99
+ const [showingHistory, setShowingHistory] = React.useState(false);
100
+ const [tempInput, setTempInput] = React.useState(''); // Store current input when navigating history
101
+ // Commands state
102
+ const commands = barOptions.commands ?? [];
103
+ const [selectedCommandIndex, setSelectedCommandIndex] = React.useState(0);
104
+ // Built-in history command
105
+ const historyCommand = React.useMemo(() => ({
106
+ keyword: 'history',
107
+ description: 'Show command history',
108
+ message: '', // Special handling
109
+ }), []);
110
+ // All available commands (user commands + built-in)
111
+ const allCommands = React.useMemo(() => {
112
+ const userCommands = commands;
113
+ // Add history command if enabled and not overwritten
114
+ if (showHistoryCommand && !userCommands.some(c => c.keyword === 'history')) {
115
+ return [...userCommands, historyCommand];
116
+ }
117
+ return userCommands;
118
+ }, [commands, showHistoryCommand, historyCommand]);
119
+ // Detect if showing command suggestions
120
+ const isCommandMode = inputValue.startsWith('/');
121
+ const commandQuery = isCommandMode ? inputValue.slice(1).toLowerCase() : '';
122
+ const showingCommands = isCommandMode && !isProcessing && !result;
123
+ // Filter commands based on query
124
+ const filteredCommands = React.useMemo(() => {
125
+ if (!showingCommands)
126
+ return [];
127
+ if (commandQuery === '')
128
+ return allCommands;
129
+ return allCommands.filter(cmd => cmd.keyword.toLowerCase().includes(commandQuery) ||
130
+ cmd.description.toLowerCase().includes(commandQuery));
131
+ }, [showingCommands, commandQuery, allCommands]);
132
+ // Reset command selection when filtered list changes
133
+ React.useEffect(() => {
134
+ setSelectedCommandIndex(0);
135
+ }, [filteredCommands.length]);
136
+ // Callback refs
137
+ const onErrorRef = React.useRef(onError);
138
+ const onResponseRef = React.useRef(onResponse);
139
+ const onToolCallRef = React.useRef(onToolCall);
140
+ const onSubmitRef = React.useRef(onSubmit);
141
+ const onOpenRef = React.useRef(onOpen);
142
+ const onCloseRef = React.useRef(onClose);
143
+ React.useEffect(() => {
144
+ onErrorRef.current = onError;
145
+ onResponseRef.current = onResponse;
146
+ onToolCallRef.current = onToolCall;
147
+ onSubmitRef.current = onSubmit;
148
+ onOpenRef.current = onOpen;
149
+ onCloseRef.current = onClose;
150
+ });
151
+ // API client
152
+ const clientRef = React.useRef(null);
153
+ if (!clientRef.current && apiKey) {
154
+ clientRef.current = new client.DevicApiClient({ apiKey, baseUrl });
155
+ }
156
+ React.useEffect(() => {
157
+ if (clientRef.current && apiKey) {
158
+ clientRef.current.setConfig({ apiKey, baseUrl });
159
+ }
160
+ }, [apiKey, baseUrl]);
161
+ // Model interface
162
+ const { toolSchemas, handleToolCalls: executeToolCalls, extractPendingToolCalls, } = useModelInterface.useModelInterface({
163
+ tools: modelInterfaceTools,
164
+ onToolExecute: onToolCall,
165
+ });
166
+ // Visibility controls
167
+ const open = React.useCallback(() => {
168
+ setInternalVisible(true);
169
+ onVisibilityChange?.(true);
170
+ onOpenRef.current?.();
171
+ // Focus input after visibility change
172
+ setTimeout(() => inputRef.current?.focus(), 50);
173
+ }, [onVisibilityChange]);
174
+ const close = React.useCallback(() => {
175
+ setInternalVisible(false);
176
+ onVisibilityChange?.(false);
177
+ onCloseRef.current?.();
178
+ }, [onVisibilityChange]);
179
+ const toggle = React.useCallback(() => {
180
+ if (isVisible) {
181
+ close();
182
+ }
183
+ else {
184
+ open();
185
+ }
186
+ }, [isVisible, open, close]);
187
+ const focus = React.useCallback(() => {
188
+ inputRef.current?.focus();
189
+ }, []);
190
+ // Save history to localStorage
191
+ const saveHistory = React.useCallback((newHistory) => {
192
+ if (!enableHistory || typeof window === 'undefined')
193
+ return;
194
+ try {
195
+ localStorage.setItem(historyStorageKey, JSON.stringify(newHistory));
196
+ }
197
+ catch {
198
+ // Ignore localStorage errors
199
+ }
200
+ }, [enableHistory, historyStorageKey]);
201
+ // Add item to history
202
+ const addToHistory = React.useCallback((message) => {
203
+ if (!enableHistory || !message.trim() || message.startsWith('/'))
204
+ return;
205
+ setHistory(prev => {
206
+ // Don't add duplicates at the top
207
+ const filtered = prev.filter(item => item !== message);
208
+ const newHistory = [message, ...filtered].slice(0, maxHistoryItems);
209
+ saveHistory(newHistory);
210
+ return newHistory;
211
+ });
212
+ setHistoryIndex(-1);
213
+ }, [enableHistory, maxHistoryItems, saveHistory]);
214
+ // Clear history
215
+ const clearHistory = React.useCallback(() => {
216
+ setHistory([]);
217
+ setHistoryIndex(-1);
218
+ if (enableHistory && typeof window !== 'undefined') {
219
+ try {
220
+ localStorage.removeItem(historyStorageKey);
221
+ }
222
+ catch {
223
+ // Ignore
224
+ }
225
+ }
226
+ }, [enableHistory, historyStorageKey]);
227
+ // Navigate history
228
+ const navigateHistory = React.useCallback((direction) => {
229
+ if (!enableHistory || history.length === 0)
230
+ return;
231
+ if (direction === 'up') {
232
+ if (historyIndex === -1) {
233
+ // Save current input before navigating
234
+ setTempInput(inputValue);
235
+ setHistoryIndex(0);
236
+ setInputValue(history[0]);
237
+ }
238
+ else if (historyIndex < history.length - 1) {
239
+ const newIndex = historyIndex + 1;
240
+ setHistoryIndex(newIndex);
241
+ setInputValue(history[newIndex]);
242
+ }
243
+ }
244
+ else {
245
+ if (historyIndex > 0) {
246
+ const newIndex = historyIndex - 1;
247
+ setHistoryIndex(newIndex);
248
+ setInputValue(history[newIndex]);
249
+ }
250
+ else if (historyIndex === 0) {
251
+ setHistoryIndex(-1);
252
+ setInputValue(tempInput);
253
+ }
254
+ }
255
+ }, [enableHistory, history, historyIndex, inputValue, tempInput]);
256
+ // Ref to hold submit function (to avoid circular dependency)
257
+ const submitRef = React.useRef();
258
+ // Select a command
259
+ const selectCommand = React.useCallback((command) => {
260
+ if (command.keyword === 'history') {
261
+ // Special handling for history command
262
+ setShowingHistory(true);
263
+ setInputValue('');
264
+ }
265
+ else {
266
+ // Send the command's message
267
+ setInputValue('');
268
+ submitRef.current?.(command.message);
269
+ }
270
+ }, []);
271
+ // Navigate commands
272
+ const navigateCommands = React.useCallback((direction) => {
273
+ if (filteredCommands.length === 0)
274
+ return;
275
+ if (direction === 'down') {
276
+ setSelectedCommandIndex(prev => prev < filteredCommands.length - 1 ? prev + 1 : 0);
277
+ }
278
+ else {
279
+ setSelectedCommandIndex(prev => prev > 0 ? prev - 1 : filteredCommands.length - 1);
280
+ }
281
+ }, [filteredCommands.length]);
282
+ // Register keyboard shortcut
283
+ React.useEffect(() => {
284
+ if (!shortcut)
285
+ return;
286
+ const handler = (e) => {
287
+ if (matchShortcut(e, shortcut)) {
288
+ e.preventDefault();
289
+ toggle();
290
+ }
291
+ };
292
+ window.addEventListener('keydown', handler);
293
+ return () => window.removeEventListener('keydown', handler);
294
+ }, [shortcut, toggle]);
295
+ // Process tool calls from realtime data
296
+ const processToolCalls = React.useCallback((messages) => {
297
+ const summaries = [];
298
+ const toolResponseMap = new Map();
299
+ // Collect tool responses
300
+ for (const msg of messages) {
301
+ if (msg.role === 'tool' && msg.tool_call_id) {
302
+ toolResponseMap.set(msg.tool_call_id, msg.content);
303
+ }
304
+ }
305
+ // Collect tool calls from assistant messages
306
+ for (const msg of messages) {
307
+ if (msg.role === 'assistant' && msg.tool_calls?.length) {
308
+ for (const tc of msg.tool_calls) {
309
+ const hasResponse = toolResponseMap.has(tc.id);
310
+ let input;
311
+ try {
312
+ input = JSON.parse(tc.function.arguments || '{}');
313
+ }
314
+ catch {
315
+ input = {};
316
+ }
317
+ // Use message summary if available, otherwise format tool name
318
+ const summaryText = msg.summary || formatToolName(tc.function.name);
319
+ summaries.push({
320
+ id: tc.id,
321
+ name: tc.function.name,
322
+ status: hasResponse ? 'completed' : 'executing',
323
+ summary: summaryText,
324
+ input,
325
+ output: toolResponseMap.get(tc.id),
326
+ });
327
+ }
328
+ }
329
+ }
330
+ return summaries;
331
+ }, []);
332
+ // Handle pending client-side tool calls
333
+ const handlePendingToolCalls = React.useCallback(async (data) => {
334
+ if (!clientRef.current || !chatUid)
335
+ return;
336
+ const pendingCalls = data.pendingToolCalls || extractPendingToolCalls(data.chatHistory);
337
+ if (pendingCalls.length === 0)
338
+ return;
339
+ try {
340
+ const responses = await executeToolCalls(pendingCalls);
341
+ if (responses.length > 0) {
342
+ await clientRef.current.sendToolResponses(assistantId, chatUid, responses);
343
+ setShouldPoll(true);
344
+ }
345
+ }
346
+ catch (err) {
347
+ const error = err instanceof Error ? err : new Error(String(err));
348
+ setError(error);
349
+ onErrorRef.current?.(error);
350
+ }
351
+ }, [chatUid, assistantId, executeToolCalls, extractPendingToolCalls]);
352
+ // Polling
353
+ usePolling.usePolling(shouldPoll ? chatUid : null, async () => {
354
+ if (!clientRef.current || !chatUid) {
355
+ throw new Error('Cannot poll without client or chatUid');
356
+ }
357
+ return clientRef.current.getRealtimeHistory(assistantId, chatUid);
358
+ }, {
359
+ interval: 1000,
360
+ enabled: shouldPoll,
361
+ stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],
362
+ onUpdate: async (data) => {
363
+ // Update tool calls display
364
+ const summaries = processToolCalls(data.chatHistory);
365
+ setToolCalls(summaries);
366
+ // Update current tool summary
367
+ // Prefer showing an executing tool, but fall back to the last tool (completed or not)
368
+ if (summaries.length > 0) {
369
+ const lastExecuting = summaries.filter(s => s.status === 'executing').pop();
370
+ const lastTool = summaries[summaries.length - 1];
371
+ setCurrentToolSummary(lastExecuting?.summary || lastTool?.summary || null);
372
+ }
373
+ // Handle client-side tool calls
374
+ if (data.status === 'waiting_for_tool_response' || data.pendingToolCalls?.length) {
375
+ await handlePendingToolCalls(data);
376
+ }
377
+ },
378
+ onStop: (data) => {
379
+ setShouldPoll(false);
380
+ if (data?.status === 'error') {
381
+ setIsProcessing(false);
382
+ const err = new Error('Processing failed');
383
+ setError(err);
384
+ onErrorRef.current?.(err);
385
+ return;
386
+ }
387
+ if (data?.status === 'completed') {
388
+ setIsProcessing(false);
389
+ // Extract final assistant message
390
+ const assistantMessages = data.chatHistory.filter(m => m.role === 'assistant');
391
+ const lastAssistantMessage = assistantMessages[assistantMessages.length - 1];
392
+ if (lastAssistantMessage && chatUid) {
393
+ const commandResult = {
394
+ chatUid,
395
+ message: lastAssistantMessage,
396
+ toolCalls: processToolCalls(data.chatHistory),
397
+ };
398
+ setResult(commandResult);
399
+ // Handle execution mode
400
+ if (onExecute === 'openDrawer' && chatDrawerRef?.current) {
401
+ chatDrawerRef.current.setChatUid(chatUid);
402
+ chatDrawerRef.current.open();
403
+ // Close and reset the command bar when handing off to drawer
404
+ setResult(null);
405
+ setToolCalls([]);
406
+ setCurrentToolSummary(null);
407
+ setInternalVisible(false);
408
+ onVisibilityChange?.(false);
409
+ onCloseRef.current?.();
410
+ }
411
+ else {
412
+ onResponseRef.current?.(commandResult);
413
+ }
414
+ }
415
+ }
416
+ },
417
+ onError: (err) => {
418
+ setError(err);
419
+ setIsProcessing(false);
420
+ setShouldPoll(false);
421
+ onErrorRef.current?.(err);
422
+ },
423
+ });
424
+ // Submit message
425
+ const submit = React.useCallback(async (message) => {
426
+ const msg = message ?? inputValue;
427
+ if (!msg.trim())
428
+ return;
429
+ if (!clientRef.current) {
430
+ const err = new Error('API client not configured. Please provide an API key.');
431
+ setError(err);
432
+ onErrorRef.current?.(err);
433
+ return;
434
+ }
435
+ // Add to history before processing
436
+ addToHistory(msg);
437
+ // Clear input and start processing
438
+ setInputValue('');
439
+ setIsProcessing(true);
440
+ setError(null);
441
+ setResult(null);
442
+ setToolCalls([]);
443
+ setCurrentToolSummary(null);
444
+ setShowingHistory(false);
445
+ setHistoryIndex(-1);
446
+ onSubmitRef.current?.(msg);
447
+ try {
448
+ const dto = {
449
+ message: msg,
450
+ chatUid: chatUid || undefined,
451
+ metadata: resolvedTenantMetadata,
452
+ tenantId: resolvedTenantId,
453
+ ...(toolSchemas.length > 0 && { tools: toolSchemas }),
454
+ };
455
+ const response = await clientRef.current.sendMessageAsync(assistantId, dto);
456
+ if (response.chatUid && response.chatUid !== chatUid) {
457
+ setChatUid(response.chatUid);
458
+ }
459
+ setShouldPoll(true);
460
+ }
461
+ catch (err) {
462
+ const error = err instanceof Error ? err : new Error(String(err));
463
+ setError(error);
464
+ setIsProcessing(false);
465
+ onErrorRef.current?.(error);
466
+ }
467
+ }, [inputValue, chatUid, assistantId, resolvedTenantId, resolvedTenantMetadata, toolSchemas, addToHistory]);
468
+ // Update submit ref for use in selectCommand
469
+ submitRef.current = submit;
470
+ // Reset state
471
+ const reset = React.useCallback(() => {
472
+ setInputValue('');
473
+ setIsProcessing(false);
474
+ setToolCalls([]);
475
+ setCurrentToolSummary(null);
476
+ setResult(null);
477
+ setChatUid(null);
478
+ setError(null);
479
+ setShouldPoll(false);
480
+ }, []);
481
+ // Handle keyboard events
482
+ const handleKeyDown = React.useCallback((e) => {
483
+ // Handle Escape
484
+ if (e.key === 'Escape') {
485
+ e.preventDefault();
486
+ if (showingHistory) {
487
+ setShowingHistory(false);
488
+ return;
489
+ }
490
+ if (showingCommands) {
491
+ setInputValue('');
492
+ return;
493
+ }
494
+ // Reset if there's a result or if processing
495
+ if (result || isProcessing) {
496
+ reset();
497
+ }
498
+ close();
499
+ return;
500
+ }
501
+ // Handle arrow keys for commands
502
+ if (showingCommands && filteredCommands.length > 0) {
503
+ if (e.key === 'ArrowDown') {
504
+ e.preventDefault();
505
+ navigateCommands('down');
506
+ return;
507
+ }
508
+ if (e.key === 'ArrowUp') {
509
+ e.preventDefault();
510
+ navigateCommands('up');
511
+ return;
512
+ }
513
+ if (e.key === 'Enter' && !e.shiftKey) {
514
+ e.preventDefault();
515
+ const selectedCommand = filteredCommands[selectedCommandIndex];
516
+ if (selectedCommand) {
517
+ selectCommand(selectedCommand);
518
+ }
519
+ return;
520
+ }
521
+ if (e.key === 'Tab') {
522
+ e.preventDefault();
523
+ const selectedCommand = filteredCommands[selectedCommandIndex];
524
+ if (selectedCommand) {
525
+ setInputValue('/' + selectedCommand.keyword + ' ');
526
+ }
527
+ return;
528
+ }
529
+ }
530
+ // Handle arrow keys for history (only when not in command mode)
531
+ if (!showingCommands && !isProcessing) {
532
+ if (e.key === 'ArrowUp') {
533
+ e.preventDefault();
534
+ navigateHistory('up');
535
+ return;
536
+ }
537
+ if (e.key === 'ArrowDown' && historyIndex >= 0) {
538
+ e.preventDefault();
539
+ navigateHistory('down');
540
+ return;
541
+ }
542
+ }
543
+ // Handle Enter to submit
544
+ if (e.key === 'Enter' && !e.shiftKey && !isProcessing) {
545
+ e.preventDefault();
546
+ submit();
547
+ }
548
+ }, [
549
+ isProcessing,
550
+ result,
551
+ showingCommands,
552
+ showingHistory,
553
+ filteredCommands,
554
+ selectedCommandIndex,
555
+ historyIndex,
556
+ submit,
557
+ reset,
558
+ close,
559
+ navigateCommands,
560
+ navigateHistory,
561
+ selectCommand,
562
+ ]);
563
+ return {
564
+ isVisible,
565
+ open,
566
+ close,
567
+ toggle,
568
+ inputValue,
569
+ setInputValue,
570
+ inputRef,
571
+ focus,
572
+ isProcessing,
573
+ toolCalls,
574
+ currentToolSummary,
575
+ result,
576
+ chatUid,
577
+ error,
578
+ history,
579
+ historyIndex,
580
+ showingHistory,
581
+ setShowingHistory,
582
+ showingCommands,
583
+ filteredCommands,
584
+ selectedCommandIndex,
585
+ selectCommand,
586
+ submit,
587
+ reset,
588
+ handleKeyDown,
589
+ clearHistory,
590
+ };
591
+ }
592
+
593
+ exports.formatShortcut = formatShortcut;
594
+ exports.useAICommandBar = useAICommandBar;
595
+ //# sourceMappingURL=useAICommandBar.js.map