@messenger-box/tailwind-ui-inbox 10.0.3-alpha.122 → 10.0.3-alpha.123
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/CHANGELOG.md +4 -0
- package/lib/components/AIAgent/AIAgent.d.ts +2 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
- package/lib/components/AIAgent/AIAgent.js +42 -26
- package/lib/components/AIAgent/AIAgent.js.map +1 -1
- package/lib/components/InboxMessage/InputComponent.d.ts +4 -1
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -1
- package/lib/components/InboxMessage/InputComponent.js +20 -304
- package/lib/components/InboxMessage/InputComponent.js.map +1 -1
- package/lib/components/InboxMessage/UploadImageButton.js +2 -6
- package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +15 -5
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -1
- package/lib/components/ModelConfigPanel.d.ts +10 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -1
- package/lib/components/ModelConfigPanel.js +551 -2
- package/lib/components/ModelConfigPanel.js.map +1 -1
- package/lib/components/filler-components/RightSiderBar.d.ts +1 -0
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -1
- package/lib/components/filler-components/RightSiderBar.js +174 -140
- package/lib/components/filler-components/RightSiderBar.js.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -1
- package/lib/config/env-config.d.ts +2 -0
- package/lib/config/env-config.d.ts.map +1 -1
- package/lib/config/env-config.js +7 -1
- package/lib/config/env-config.js.map +1 -1
- package/lib/container/AiLandingInput.d.ts.map +1 -1
- package/lib/container/AiLandingInput.js +11 -9
- package/lib/container/AiLandingInput.js.map +1 -1
- package/lib/container/Inbox.js +1 -1
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/InboxAiMessagesLoader.d.ts +1 -0
- package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -1
- package/lib/container/InboxAiMessagesLoader.js +4 -1
- package/lib/container/InboxAiMessagesLoader.js.map +1 -1
- package/lib/container/InboxContainer.d.ts +1 -0
- package/lib/container/InboxContainer.d.ts.map +1 -1
- package/lib/container/InboxContainer.js +1 -6
- package/lib/container/InboxContainer.js.map +1 -1
- package/lib/container/InboxWithAiLoader.d.ts +1 -0
- package/lib/container/InboxWithAiLoader.d.ts.map +1 -1
- package/lib/container/InboxWithAiLoader.js +2 -1
- package/lib/container/InboxWithAiLoader.js.map +1 -1
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/module.js +1 -1
- package/lib/module.js.map +1 -1
- package/lib/templates/InboxWithAi.d.ts +1 -0
- package/lib/templates/InboxWithAi.d.ts.map +1 -1
- package/lib/templates/InboxWithAi.js +9 -6
- package/lib/templates/InboxWithAi.js.map +1 -1
- package/lib/templates/InboxWithAi.tsx +7 -3
- package/lib/xstate/index.d.ts +3 -0
- package/lib/xstate/index.d.ts.map +1 -0
- package/lib/xstate/rightSidebar.machine.d.ts +4 -0
- package/lib/xstate/rightSidebar.machine.d.ts.map +1 -0
- package/lib/xstate/rightSidebar.machine.js +174 -0
- package/lib/xstate/rightSidebar.machine.js.map +1 -0
- package/lib/xstate/rightSidebar.types.d.ts +52 -0
- package/lib/xstate/rightSidebar.types.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/components/AIAgent/AIAgent.tsx +35 -21
- package/src/components/InboxMessage/InputComponent.tsx +23 -375
- package/src/components/InboxMessage/UploadImageButton.tsx +4 -4
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +17 -0
- package/src/components/ModelConfigPanel.tsx +666 -0
- package/src/components/filler-components/RightSiderBar.tsx +189 -150
- package/src/components/slot-fill/right-sidebar-filler.tsx +1 -0
- package/src/config/env-config.ts +3 -1
- package/src/container/AiLandingInput.tsx +11 -111
- package/src/container/InboxAiMessagesLoader.tsx +13 -2
- package/src/container/InboxContainer.tsx +2 -8
- package/src/container/InboxWithAiLoader.tsx +2 -0
- package/src/templates/InboxWithAi.tsx +7 -3
- package/src/xstate/index.ts +2 -0
- package/src/xstate/rightSidebar.machine.ts +139 -0
- package/src/xstate/rightSidebar.types.ts +55 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { UploadImageButton } from './InboxMessage/UploadImageButton';
|
|
4
|
+
import { ModelConfig as PersistentModelConfig } from '../hooks/usePersistentModelConfig';
|
|
2
5
|
import { ModelConfig } from '../hooks/usePersistentModelConfig';
|
|
3
6
|
|
|
4
7
|
interface ModelConfigPanelProps {
|
|
@@ -341,3 +344,666 @@ export const ModelConfigPanel: React.FC<ModelConfigPanelProps> = ({
|
|
|
341
344
|
ModelConfigPanel.displayName = 'ModelConfigPanel';
|
|
342
345
|
|
|
343
346
|
export default ModelConfigPanel;
|
|
347
|
+
|
|
348
|
+
// Toolbar extracted for reuse from InputComponent.tsx (lines 209-394)
|
|
349
|
+
export interface ModelToolbarProps {
|
|
350
|
+
modelConfig?: PersistentModelConfig;
|
|
351
|
+
onModelConfigChange?: (config: PersistentModelConfig) => void;
|
|
352
|
+
sending: boolean;
|
|
353
|
+
canSend: boolean;
|
|
354
|
+
onSend: () => void;
|
|
355
|
+
onUploadImageChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const FloatingDropdown: React.FC<{
|
|
359
|
+
anchorRef: React.RefObject<HTMLElement>;
|
|
360
|
+
open: boolean;
|
|
361
|
+
onClose?: () => void;
|
|
362
|
+
minWidth?: number;
|
|
363
|
+
children: React.ReactNode;
|
|
364
|
+
}> = ({ anchorRef, open, onClose, minWidth = 224, children }) => {
|
|
365
|
+
const [style, setStyle] = useState<{ top: number; left: number; width: number } | null>(null);
|
|
366
|
+
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
if (!open) return;
|
|
369
|
+
const update = () => {
|
|
370
|
+
const el = anchorRef.current as HTMLElement | null;
|
|
371
|
+
if (!el) return;
|
|
372
|
+
const rect = el.getBoundingClientRect();
|
|
373
|
+
const width = Math.max(minWidth, rect.width);
|
|
374
|
+
|
|
375
|
+
// Heuristic expected dropdown height (px). Matches header + max-h-60 (15rem = 240px) + paddings.
|
|
376
|
+
const expectedHeight = 320;
|
|
377
|
+
const viewportHeight = window.innerHeight;
|
|
378
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
379
|
+
const spaceAbove = rect.top;
|
|
380
|
+
|
|
381
|
+
const openAbove = spaceBelow < expectedHeight && spaceAbove > spaceBelow;
|
|
382
|
+
const top = openAbove ? Math.max(8, rect.top - expectedHeight - 8) : rect.bottom + 8;
|
|
383
|
+
const left = Math.min(Math.max(8, rect.left), window.innerWidth - width - 8);
|
|
384
|
+
setStyle({ top, left, width });
|
|
385
|
+
};
|
|
386
|
+
update();
|
|
387
|
+
window.addEventListener('scroll', update, true);
|
|
388
|
+
window.addEventListener('resize', update);
|
|
389
|
+
return () => {
|
|
390
|
+
window.removeEventListener('scroll', update, true);
|
|
391
|
+
window.removeEventListener('resize', update);
|
|
392
|
+
};
|
|
393
|
+
}, [open, anchorRef, minWidth]);
|
|
394
|
+
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
if (!open) return;
|
|
397
|
+
const onKey = (e: KeyboardEvent) => {
|
|
398
|
+
if (e.key === 'Escape') onClose?.();
|
|
399
|
+
};
|
|
400
|
+
document.addEventListener('keydown', onKey);
|
|
401
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
402
|
+
}, [open, onClose]);
|
|
403
|
+
|
|
404
|
+
if (!open || !style) return null;
|
|
405
|
+
return ReactDOM.createPortal(
|
|
406
|
+
<div
|
|
407
|
+
style={{ position: 'fixed', top: style.top, left: style.left, width: style.width, zIndex: 9999 }}
|
|
408
|
+
className="bg-white border border-gray-200 rounded-lg shadow-lg"
|
|
409
|
+
>
|
|
410
|
+
{children}
|
|
411
|
+
</div>,
|
|
412
|
+
document.body,
|
|
413
|
+
);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
export const ModelToolbar: React.FC<ModelToolbarProps> = ({
|
|
417
|
+
modelConfig,
|
|
418
|
+
onModelConfigChange,
|
|
419
|
+
sending,
|
|
420
|
+
canSend,
|
|
421
|
+
onSend,
|
|
422
|
+
onUploadImageChange,
|
|
423
|
+
}) => {
|
|
424
|
+
const [showModelDropdown, setShowModelDropdown] = useState(false);
|
|
425
|
+
const [showToolbarModelDropdown, setShowToolbarModelDropdown] = useState(false);
|
|
426
|
+
const [showTemplateDropdown, setShowTemplateDropdown] = useState(false);
|
|
427
|
+
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
|
428
|
+
const [settingsActiveTab, setSettingsActiveTab] = useState<'model' | 'other_settings'>('model');
|
|
429
|
+
const [templateSearch, setTemplateSearch] = useState('');
|
|
430
|
+
const [modelSearch, setModelSearch] = useState('');
|
|
431
|
+
const [toolbarModelSearch, setToolbarModelSearch] = useState('');
|
|
432
|
+
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
|
433
|
+
const toolbarModelButtonRef = useRef<HTMLButtonElement>(null);
|
|
434
|
+
const templateDropdownRef = useRef<HTMLDivElement>(null);
|
|
435
|
+
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
438
|
+
if (modelDropdownRef.current && !modelDropdownRef.current.contains(event.target as Node)) {
|
|
439
|
+
setShowModelDropdown(false);
|
|
440
|
+
}
|
|
441
|
+
const target = event.target as HTMLElement;
|
|
442
|
+
if (showToolbarModelDropdown) {
|
|
443
|
+
const isClickInsideButton = toolbarModelButtonRef.current?.contains(target);
|
|
444
|
+
const isClickInsideDropdown = target.closest('[data-model-dropdown]');
|
|
445
|
+
if (!isClickInsideButton && !isClickInsideDropdown) {
|
|
446
|
+
setShowToolbarModelDropdown(false);
|
|
447
|
+
setToolbarModelSearch('');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (templateDropdownRef.current && !templateDropdownRef.current.contains(event.target as Node)) {
|
|
451
|
+
setShowTemplateDropdown(false);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
455
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
456
|
+
}, [showToolbarModelDropdown]);
|
|
457
|
+
|
|
458
|
+
const allModelOptions = useMemo(() => {
|
|
459
|
+
return getAllModels();
|
|
460
|
+
}, []);
|
|
461
|
+
|
|
462
|
+
const templateOptionsList = useMemo(() => {
|
|
463
|
+
return templateOptions.map((t) => ({ value: t.value, label: t.label, icon: t.icon }));
|
|
464
|
+
}, []);
|
|
465
|
+
|
|
466
|
+
const filteredTemplates = useMemo(() => {
|
|
467
|
+
if (!templateSearch) return templateOptionsList;
|
|
468
|
+
const q = templateSearch.toLowerCase();
|
|
469
|
+
return templateOptionsList.filter(
|
|
470
|
+
(o) => o.label.toLowerCase().includes(q) || String(o.value).toLowerCase().includes(q),
|
|
471
|
+
);
|
|
472
|
+
}, [templateOptionsList, templateSearch]);
|
|
473
|
+
|
|
474
|
+
const filteredModels = useMemo(() => {
|
|
475
|
+
if (!modelSearch) return allModelOptions;
|
|
476
|
+
const q = modelSearch.toLowerCase();
|
|
477
|
+
return allModelOptions.filter((o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
|
|
478
|
+
}, [allModelOptions, modelSearch]);
|
|
479
|
+
|
|
480
|
+
const filteredToolbarModels = useMemo(() => {
|
|
481
|
+
if (!toolbarModelSearch) return allModelOptions;
|
|
482
|
+
const q = toolbarModelSearch.toLowerCase();
|
|
483
|
+
return allModelOptions.filter((o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
|
|
484
|
+
}, [allModelOptions, toolbarModelSearch]);
|
|
485
|
+
|
|
486
|
+
const handleModelSelect = useCallback(
|
|
487
|
+
(modelValue: string) => {
|
|
488
|
+
if (onModelConfigChange && modelConfig) {
|
|
489
|
+
onModelConfigChange({ ...modelConfig, model: modelValue });
|
|
490
|
+
}
|
|
491
|
+
setShowModelDropdown(false);
|
|
492
|
+
},
|
|
493
|
+
[modelConfig, onModelConfigChange],
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
const handleToolbarModelSelect = useCallback(
|
|
497
|
+
(modelValue: string) => {
|
|
498
|
+
if (onModelConfigChange && modelConfig) {
|
|
499
|
+
onModelConfigChange({ ...modelConfig, model: modelValue });
|
|
500
|
+
}
|
|
501
|
+
setShowToolbarModelDropdown(false);
|
|
502
|
+
setToolbarModelSearch('');
|
|
503
|
+
},
|
|
504
|
+
[modelConfig, onModelConfigChange],
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const handleTemplateSelect = useCallback(
|
|
508
|
+
(templateValue: PersistentModelConfig['template']) => {
|
|
509
|
+
if (onModelConfigChange && modelConfig) {
|
|
510
|
+
onModelConfigChange({ ...modelConfig, template: templateValue });
|
|
511
|
+
}
|
|
512
|
+
setShowTemplateDropdown(false);
|
|
513
|
+
},
|
|
514
|
+
[modelConfig, onModelConfigChange],
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return (
|
|
518
|
+
<>
|
|
519
|
+
<div className="flex items-center gap-2 overflow-x-auto overflow-y-visible whitespace-nowrap py-1 px-2 sm:px-0">
|
|
520
|
+
{/* Template selection moved to Project Settings modal */}
|
|
521
|
+
|
|
522
|
+
{/* 2. Model Selection Dropdown */}
|
|
523
|
+
{/* Model selection moved to Project Settings modal */}
|
|
524
|
+
|
|
525
|
+
<div className="flex-1"></div>
|
|
526
|
+
|
|
527
|
+
{/* Settings Button */}
|
|
528
|
+
<button
|
|
529
|
+
onClick={() => setShowSettingsModal(true)}
|
|
530
|
+
className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors shrink-0"
|
|
531
|
+
>
|
|
532
|
+
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
533
|
+
<path
|
|
534
|
+
strokeLinecap="round"
|
|
535
|
+
strokeLinejoin="round"
|
|
536
|
+
strokeWidth={2}
|
|
537
|
+
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
538
|
+
/>
|
|
539
|
+
<path
|
|
540
|
+
strokeLinecap="round"
|
|
541
|
+
strokeLinejoin="round"
|
|
542
|
+
strokeWidth={2}
|
|
543
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
544
|
+
/>
|
|
545
|
+
</svg>
|
|
546
|
+
</button>
|
|
547
|
+
|
|
548
|
+
{/* Model Selection Button */}
|
|
549
|
+
<div className="relative shrink-0">
|
|
550
|
+
<button
|
|
551
|
+
ref={toolbarModelButtonRef}
|
|
552
|
+
onClick={() => setShowToolbarModelDropdown(!showToolbarModelDropdown)}
|
|
553
|
+
className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors shrink-0"
|
|
554
|
+
title={
|
|
555
|
+
modelConfig?.model
|
|
556
|
+
? allModelOptions.find((o) => o.value === modelConfig.model)?.label || 'Choose a model'
|
|
557
|
+
: 'Choose a model'
|
|
558
|
+
}
|
|
559
|
+
>
|
|
560
|
+
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
561
|
+
{/* Outer square */}
|
|
562
|
+
<rect x="4" y="4" width="16" height="16" rx="1" strokeWidth="2" />
|
|
563
|
+
{/* Inner square */}
|
|
564
|
+
<rect x="8" y="8" width="8" height="8" rx="0.5" strokeWidth="2" />
|
|
565
|
+
{/* Top pins */}
|
|
566
|
+
<line x1="10" y1="4" x2="10" y2="2" strokeWidth="2" />
|
|
567
|
+
<line x1="14" y1="4" x2="14" y2="2" strokeWidth="2" />
|
|
568
|
+
{/* Bottom pins */}
|
|
569
|
+
<line x1="10" y1="20" x2="10" y2="22" strokeWidth="2" />
|
|
570
|
+
<line x1="14" y1="20" x2="14" y2="22" strokeWidth="2" />
|
|
571
|
+
{/* Left pins */}
|
|
572
|
+
<line x1="4" y1="10" x2="2" y2="10" strokeWidth="2" />
|
|
573
|
+
<line x1="4" y1="14" x2="2" y2="14" strokeWidth="2" />
|
|
574
|
+
{/* Right pins */}
|
|
575
|
+
<line x1="20" y1="10" x2="22" y2="10" strokeWidth="2" />
|
|
576
|
+
<line x1="20" y1="14" x2="22" y2="14" strokeWidth="2" />
|
|
577
|
+
</svg>
|
|
578
|
+
</button>
|
|
579
|
+
|
|
580
|
+
<FloatingDropdown
|
|
581
|
+
anchorRef={toolbarModelButtonRef as unknown as React.RefObject<HTMLElement>}
|
|
582
|
+
open={showToolbarModelDropdown}
|
|
583
|
+
onClose={() => {
|
|
584
|
+
setShowToolbarModelDropdown(false);
|
|
585
|
+
setToolbarModelSearch('');
|
|
586
|
+
}}
|
|
587
|
+
minWidth={280}
|
|
588
|
+
>
|
|
589
|
+
<div data-model-dropdown className="bg-white">
|
|
590
|
+
<div className="p-2 border-b border-gray-100">
|
|
591
|
+
<input
|
|
592
|
+
autoFocus
|
|
593
|
+
value={toolbarModelSearch}
|
|
594
|
+
onChange={(e) => setToolbarModelSearch(e.target.value)}
|
|
595
|
+
placeholder="Search model..."
|
|
596
|
+
className="w-full px-2 py-1.5 text-xs border border-gray-200 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
597
|
+
/>
|
|
598
|
+
</div>
|
|
599
|
+
<div className="py-1 max-h-60 overflow-y-auto">
|
|
600
|
+
{filteredToolbarModels.map((option) => (
|
|
601
|
+
<button
|
|
602
|
+
key={option.value}
|
|
603
|
+
onClick={() => handleToolbarModelSelect(option.value)}
|
|
604
|
+
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 transition-colors flex items-center justify-between ${
|
|
605
|
+
modelConfig?.model === option.value ? 'bg-blue-50' : ''
|
|
606
|
+
}`}
|
|
607
|
+
>
|
|
608
|
+
<span className="text-gray-900">{option.label}</span>
|
|
609
|
+
{modelConfig?.model === option.value && (
|
|
610
|
+
<svg
|
|
611
|
+
className="w-4 h-4 text-blue-600"
|
|
612
|
+
fill="none"
|
|
613
|
+
stroke="currentColor"
|
|
614
|
+
viewBox="0 0 24 24"
|
|
615
|
+
>
|
|
616
|
+
<path
|
|
617
|
+
strokeLinecap="round"
|
|
618
|
+
strokeLinejoin="round"
|
|
619
|
+
strokeWidth={2}
|
|
620
|
+
d="M5 13l4 4L19 7"
|
|
621
|
+
/>
|
|
622
|
+
</svg>
|
|
623
|
+
)}
|
|
624
|
+
</button>
|
|
625
|
+
))}
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
</FloatingDropdown>
|
|
629
|
+
</div>
|
|
630
|
+
|
|
631
|
+
<UploadImageButton onChange={onUploadImageChange}>
|
|
632
|
+
<div className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white shrink-0 ml-1">
|
|
633
|
+
<svg className="w-3 h-3 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
634
|
+
<path
|
|
635
|
+
strokeLinecap="round"
|
|
636
|
+
strokeLinejoin="round"
|
|
637
|
+
strokeWidth={2}
|
|
638
|
+
d="M21.44 11.05l-9.19 9.19a6 6 0 11-8.49-8.49l9.19-9.19a4 4 0 115.66 5.66l-9.19 9.19a2 2 0 11-2.83-2.83l8.49-8.49"
|
|
639
|
+
/>
|
|
640
|
+
</svg>
|
|
641
|
+
</div>
|
|
642
|
+
</UploadImageButton>
|
|
643
|
+
|
|
644
|
+
{/* Send Button */}
|
|
645
|
+
<button
|
|
646
|
+
className={`flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border transition-colors shrink-0 ${
|
|
647
|
+
canSend && !sending
|
|
648
|
+
? 'border-blue-500 bg-blue-500 hover:bg-blue-600 text-white'
|
|
649
|
+
: 'border-gray-300 bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
650
|
+
}`}
|
|
651
|
+
onClick={onSend}
|
|
652
|
+
disabled={!canSend || sending}
|
|
653
|
+
type="button"
|
|
654
|
+
>
|
|
655
|
+
{sending ? (
|
|
656
|
+
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
657
|
+
<circle
|
|
658
|
+
className="opacity-25"
|
|
659
|
+
cx="12"
|
|
660
|
+
cy="12"
|
|
661
|
+
r="10"
|
|
662
|
+
stroke="currentColor"
|
|
663
|
+
strokeWidth="4"
|
|
664
|
+
></circle>
|
|
665
|
+
<path
|
|
666
|
+
className="opacity-75"
|
|
667
|
+
fill="currentColor"
|
|
668
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
669
|
+
></path>
|
|
670
|
+
</svg>
|
|
671
|
+
) : (
|
|
672
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
673
|
+
<path
|
|
674
|
+
strokeLinecap="round"
|
|
675
|
+
strokeLinejoin="round"
|
|
676
|
+
strokeWidth={2}
|
|
677
|
+
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
|
|
678
|
+
/>
|
|
679
|
+
</svg>
|
|
680
|
+
)}
|
|
681
|
+
</button>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
{/* Settings Modal - Only API Key and Model + IDs */}
|
|
685
|
+
{showSettingsModal && (
|
|
686
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
|
687
|
+
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
|
|
688
|
+
<div className="p-6 mb-4">
|
|
689
|
+
<div className="flex items-center justify-between mb-4">
|
|
690
|
+
<h3 className="text-lg font-semibold text-gray-900">Project Settings</h3>
|
|
691
|
+
<button
|
|
692
|
+
onClick={() => setShowSettingsModal(false)}
|
|
693
|
+
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
694
|
+
>
|
|
695
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
696
|
+
<path
|
|
697
|
+
strokeLinecap="round"
|
|
698
|
+
strokeLinejoin="round"
|
|
699
|
+
strokeWidth={2}
|
|
700
|
+
d="M6 18L18 6M6 6l12 12"
|
|
701
|
+
/>
|
|
702
|
+
</svg>
|
|
703
|
+
</button>
|
|
704
|
+
</div>
|
|
705
|
+
|
|
706
|
+
{/* Tabs */}
|
|
707
|
+
<div className="mb-4">
|
|
708
|
+
<div className="flex border-b border-gray-200">
|
|
709
|
+
<button
|
|
710
|
+
className={`px-3 py-2 text-sm -mb-px border-b-2 transition-colors ${
|
|
711
|
+
settingsActiveTab === 'model'
|
|
712
|
+
? 'border-blue-500 text-blue-600'
|
|
713
|
+
: 'border-transparent text-gray-600 hover:text-gray-800'
|
|
714
|
+
}`}
|
|
715
|
+
onClick={() => setSettingsActiveTab('model')}
|
|
716
|
+
type="button"
|
|
717
|
+
>
|
|
718
|
+
Configuration
|
|
719
|
+
</button>
|
|
720
|
+
<button
|
|
721
|
+
className={`ml-2 px-3 py-2 text-sm -mb-px border-b-2 transition-colors ${
|
|
722
|
+
settingsActiveTab === 'other_settings'
|
|
723
|
+
? 'border-blue-500 text-blue-600'
|
|
724
|
+
: 'border-transparent text-gray-600 hover:text-gray-800'
|
|
725
|
+
}`}
|
|
726
|
+
onClick={() => setSettingsActiveTab('other_settings')}
|
|
727
|
+
type="button"
|
|
728
|
+
>
|
|
729
|
+
Other Settings
|
|
730
|
+
</button>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
734
|
+
{modelConfig && onModelConfigChange && (
|
|
735
|
+
<div className="space-y-4">
|
|
736
|
+
{settingsActiveTab === 'model' ? (
|
|
737
|
+
<div className="space-y-4">
|
|
738
|
+
<div className="space-y-2">
|
|
739
|
+
<label className="block text-sm font-medium text-gray-700">Model</label>
|
|
740
|
+
<div className="relative" ref={modelDropdownRef}>
|
|
741
|
+
<button
|
|
742
|
+
type="button"
|
|
743
|
+
onClick={() => setShowModelDropdown(!showModelDropdown)}
|
|
744
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors overflow-hidden"
|
|
745
|
+
>
|
|
746
|
+
{(() => {
|
|
747
|
+
if (modelConfig && modelConfig.model) {
|
|
748
|
+
const currentModel = allModelOptions.find(
|
|
749
|
+
(option) => option.value === modelConfig.model,
|
|
750
|
+
);
|
|
751
|
+
return currentModel ? (
|
|
752
|
+
<span className="text-gray-700 truncate">
|
|
753
|
+
{currentModel.label}
|
|
754
|
+
</span>
|
|
755
|
+
) : (
|
|
756
|
+
<span className="text-gray-500 truncate">
|
|
757
|
+
Model
|
|
758
|
+
</span>
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
return (
|
|
762
|
+
<span className="text-gray-500 truncate">Model</span>
|
|
763
|
+
);
|
|
764
|
+
})()}
|
|
765
|
+
<svg
|
|
766
|
+
className="w-4 h-4 text-gray-500 ml-auto"
|
|
767
|
+
fill="none"
|
|
768
|
+
stroke="currentColor"
|
|
769
|
+
viewBox="0 0 24 24"
|
|
770
|
+
>
|
|
771
|
+
<path
|
|
772
|
+
strokeLinecap="round"
|
|
773
|
+
strokeLinejoin="round"
|
|
774
|
+
strokeWidth={2}
|
|
775
|
+
d="M19 9l-7 7-7-7"
|
|
776
|
+
/>
|
|
777
|
+
</svg>
|
|
778
|
+
</button>
|
|
779
|
+
|
|
780
|
+
<FloatingDropdown
|
|
781
|
+
anchorRef={
|
|
782
|
+
modelDropdownRef as unknown as React.RefObject<HTMLElement>
|
|
783
|
+
}
|
|
784
|
+
open={showModelDropdown}
|
|
785
|
+
onClose={() => setShowModelDropdown(false)}
|
|
786
|
+
minWidth={320}
|
|
787
|
+
>
|
|
788
|
+
<div className="p-2 border-b border-gray-100">
|
|
789
|
+
<input
|
|
790
|
+
autoFocus
|
|
791
|
+
value={modelSearch}
|
|
792
|
+
onChange={(e) => setModelSearch(e.target.value)}
|
|
793
|
+
placeholder="Search model..."
|
|
794
|
+
className="w-full px-2 py-1 text-xs border border-gray-200 rounded"
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
797
|
+
<div className="py-1 max-h-60 overflow-y-auto">
|
|
798
|
+
{filteredModels.map((option) => (
|
|
799
|
+
<button
|
|
800
|
+
key={option.value}
|
|
801
|
+
onClick={() => handleModelSelect(option.value)}
|
|
802
|
+
className={`w-full px-3 py-2 text-left text-xs hover:bg-gray-50 transition-colors ${
|
|
803
|
+
modelConfig?.model === option.value
|
|
804
|
+
? 'bg-blue-50'
|
|
805
|
+
: ''
|
|
806
|
+
}`}
|
|
807
|
+
>
|
|
808
|
+
<div className="font-medium text-gray-900 truncate">
|
|
809
|
+
{option.label}
|
|
810
|
+
</div>
|
|
811
|
+
</button>
|
|
812
|
+
))}
|
|
813
|
+
</div>
|
|
814
|
+
</FloatingDropdown>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
|
|
818
|
+
<div className="space-y-2">
|
|
819
|
+
<label className="block text-sm font-medium text-gray-700">
|
|
820
|
+
Template
|
|
821
|
+
</label>
|
|
822
|
+
<div className="relative" ref={templateDropdownRef}>
|
|
823
|
+
<button
|
|
824
|
+
type="button"
|
|
825
|
+
onClick={() => setShowTemplateDropdown(!showTemplateDropdown)}
|
|
826
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors overflow-hidden"
|
|
827
|
+
>
|
|
828
|
+
{(() => {
|
|
829
|
+
if (modelConfig && modelConfig.template) {
|
|
830
|
+
const currentTemplate = templateOptionsList.find(
|
|
831
|
+
(option) => option.value === modelConfig.template,
|
|
832
|
+
);
|
|
833
|
+
return currentTemplate ? (
|
|
834
|
+
<>
|
|
835
|
+
<span className="text-base">
|
|
836
|
+
{currentTemplate.icon}
|
|
837
|
+
</span>
|
|
838
|
+
<span className="text-gray-700 truncate">
|
|
839
|
+
{currentTemplate.label}
|
|
840
|
+
</span>
|
|
841
|
+
</>
|
|
842
|
+
) : (
|
|
843
|
+
<>
|
|
844
|
+
<span className="text-base">📝</span>
|
|
845
|
+
<span className="text-gray-500 truncate">
|
|
846
|
+
Template
|
|
847
|
+
</span>
|
|
848
|
+
</>
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return (
|
|
852
|
+
<>
|
|
853
|
+
<span className="text-base">📝</span>
|
|
854
|
+
<span className="text-gray-500 truncate">
|
|
855
|
+
Template
|
|
856
|
+
</span>
|
|
857
|
+
</>
|
|
858
|
+
);
|
|
859
|
+
})()}
|
|
860
|
+
<svg
|
|
861
|
+
className="w-4 h-4 text-gray-500 ml-auto"
|
|
862
|
+
fill="none"
|
|
863
|
+
stroke="currentColor"
|
|
864
|
+
viewBox="0 0 24 24"
|
|
865
|
+
>
|
|
866
|
+
<path
|
|
867
|
+
strokeLinecap="round"
|
|
868
|
+
strokeLinejoin="round"
|
|
869
|
+
strokeWidth={2}
|
|
870
|
+
d="M19 9l-7 7-7-7"
|
|
871
|
+
/>
|
|
872
|
+
</svg>
|
|
873
|
+
</button>
|
|
874
|
+
|
|
875
|
+
<FloatingDropdown
|
|
876
|
+
anchorRef={
|
|
877
|
+
templateDropdownRef as unknown as React.RefObject<HTMLElement>
|
|
878
|
+
}
|
|
879
|
+
open={showTemplateDropdown}
|
|
880
|
+
onClose={() => setShowTemplateDropdown(false)}
|
|
881
|
+
minWidth={320}
|
|
882
|
+
>
|
|
883
|
+
<div className="p-2 border-b border-gray-100">
|
|
884
|
+
<input
|
|
885
|
+
autoFocus
|
|
886
|
+
value={templateSearch}
|
|
887
|
+
onChange={(e) => setTemplateSearch(e.target.value)}
|
|
888
|
+
placeholder="Search template..."
|
|
889
|
+
className="w-full px-2 py-1 text-xs border border-gray-200 rounded"
|
|
890
|
+
/>
|
|
891
|
+
</div>
|
|
892
|
+
<div className="py-1 max-h-60 overflow-y-auto">
|
|
893
|
+
{filteredTemplates.map((option) => (
|
|
894
|
+
<button
|
|
895
|
+
key={option.value}
|
|
896
|
+
onClick={() => handleTemplateSelect(option.value)}
|
|
897
|
+
className={`w-full px-3 py-2 text-left text-xs hover:bg-gray-50 transition-colors flex items-center gap-2 ${
|
|
898
|
+
modelConfig?.template === option.value
|
|
899
|
+
? 'bg-blue-50'
|
|
900
|
+
: ''
|
|
901
|
+
}`}
|
|
902
|
+
>
|
|
903
|
+
<span className="text-sm">{option.icon}</span>
|
|
904
|
+
<span className="font-medium text-gray-900 truncate">
|
|
905
|
+
{option.label}
|
|
906
|
+
</span>
|
|
907
|
+
</button>
|
|
908
|
+
))}
|
|
909
|
+
</div>
|
|
910
|
+
</FloatingDropdown>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
) : (
|
|
915
|
+
<>
|
|
916
|
+
<div>
|
|
917
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
918
|
+
API Key <span className="text-red-500">*</span>
|
|
919
|
+
</label>
|
|
920
|
+
<input
|
|
921
|
+
type="password"
|
|
922
|
+
value={modelConfig.apiKey}
|
|
923
|
+
onChange={(e) =>
|
|
924
|
+
onModelConfigChange({ ...modelConfig, apiKey: e.target.value })
|
|
925
|
+
}
|
|
926
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
927
|
+
placeholder="Enter your API key"
|
|
928
|
+
/>
|
|
929
|
+
</div>
|
|
930
|
+
|
|
931
|
+
<div>
|
|
932
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
933
|
+
Extension ID <span className="text-red-500">*</span>
|
|
934
|
+
</label>
|
|
935
|
+
<input
|
|
936
|
+
type="text"
|
|
937
|
+
value={modelConfig.extensionId}
|
|
938
|
+
onChange={(e) =>
|
|
939
|
+
onModelConfigChange({
|
|
940
|
+
...modelConfig,
|
|
941
|
+
extensionId: e.target.value,
|
|
942
|
+
})
|
|
943
|
+
}
|
|
944
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
945
|
+
placeholder="Enter your extension ID"
|
|
946
|
+
/>
|
|
947
|
+
</div>
|
|
948
|
+
|
|
949
|
+
<div>
|
|
950
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
951
|
+
Form ID <span className="text-red-500">*</span>
|
|
952
|
+
</label>
|
|
953
|
+
<input
|
|
954
|
+
type="text"
|
|
955
|
+
value={modelConfig.formId || ''}
|
|
956
|
+
onChange={(e) =>
|
|
957
|
+
onModelConfigChange({ ...modelConfig, formId: e.target.value })
|
|
958
|
+
}
|
|
959
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
960
|
+
placeholder="Enter form ID"
|
|
961
|
+
/>
|
|
962
|
+
</div>
|
|
963
|
+
|
|
964
|
+
<div>
|
|
965
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
966
|
+
Function ID <span className="text-red-500">*</span>
|
|
967
|
+
</label>
|
|
968
|
+
<input
|
|
969
|
+
type="text"
|
|
970
|
+
value={modelConfig.functionId || ''}
|
|
971
|
+
onChange={(e) =>
|
|
972
|
+
onModelConfigChange({
|
|
973
|
+
...modelConfig,
|
|
974
|
+
functionId: e.target.value,
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
978
|
+
placeholder="Enter function ID"
|
|
979
|
+
/>
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<div>
|
|
983
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
984
|
+
Step Name <span className="text-gray-500 text-xs">(optional)</span>
|
|
985
|
+
</label>
|
|
986
|
+
<input
|
|
987
|
+
type="text"
|
|
988
|
+
value={modelConfig.stepName || ''}
|
|
989
|
+
onChange={(e) =>
|
|
990
|
+
onModelConfigChange({
|
|
991
|
+
...modelConfig,
|
|
992
|
+
stepName: e.target.value,
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
996
|
+
placeholder="Enter step name (optional)"
|
|
997
|
+
/>
|
|
998
|
+
</div>
|
|
999
|
+
</>
|
|
1000
|
+
)}
|
|
1001
|
+
</div>
|
|
1002
|
+
)}
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
)}
|
|
1007
|
+
</>
|
|
1008
|
+
);
|
|
1009
|
+
};
|