@sqlrooms/ai 0.24.24 → 0.24.25

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 (46) hide show
  1. package/README.md +76 -1
  2. package/dist/AiSettingsSlice.d.ts +148 -0
  3. package/dist/AiSettingsSlice.d.ts.map +1 -0
  4. package/dist/AiSettingsSlice.js +180 -0
  5. package/dist/AiSettingsSlice.js.map +1 -0
  6. package/dist/AiSlice.d.ts +15 -24
  7. package/dist/AiSlice.d.ts.map +1 -1
  8. package/dist/AiSlice.js +99 -32
  9. package/dist/AiSlice.js.map +1 -1
  10. package/dist/analysis.d.ts +1 -3
  11. package/dist/analysis.d.ts.map +1 -1
  12. package/dist/analysis.js +2 -4
  13. package/dist/analysis.js.map +1 -1
  14. package/dist/components/AnalysisResult.js +1 -1
  15. package/dist/components/AnalysisResult.js.map +1 -1
  16. package/dist/components/settings/AiModelParameters.d.ts +6 -0
  17. package/dist/components/settings/AiModelParameters.d.ts.map +1 -0
  18. package/dist/components/settings/AiModelParameters.js +91 -0
  19. package/dist/components/settings/AiModelParameters.js.map +1 -0
  20. package/dist/components/settings/AiModelUsage.d.ts +18 -0
  21. package/dist/components/settings/AiModelUsage.d.ts.map +1 -0
  22. package/dist/components/settings/AiModelUsage.js +60 -0
  23. package/dist/components/settings/AiModelUsage.js.map +1 -0
  24. package/dist/components/settings/AiModelsSettings.d.ts +10 -0
  25. package/dist/components/settings/AiModelsSettings.d.ts.map +1 -0
  26. package/dist/components/settings/AiModelsSettings.js +229 -0
  27. package/dist/components/settings/AiModelsSettings.js.map +1 -0
  28. package/dist/components/settings/AiProvidersSettings.d.ts +3 -0
  29. package/dist/components/settings/AiProvidersSettings.d.ts.map +1 -0
  30. package/dist/components/settings/AiProvidersSettings.js +88 -0
  31. package/dist/components/settings/AiProvidersSettings.js.map +1 -0
  32. package/dist/components/settings/AiSettingsPanel.d.ts +17 -0
  33. package/dist/components/settings/AiSettingsPanel.d.ts.map +1 -0
  34. package/dist/components/settings/AiSettingsPanel.js +20 -0
  35. package/dist/components/settings/AiSettingsPanel.js.map +1 -0
  36. package/dist/components/tools/ToolResultErrorBoundary.d.ts +1 -1
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +7 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/schemas.d.ts +5 -5
  42. package/dist/utils.d.ts +17 -0
  43. package/dist/utils.d.ts.map +1 -0
  44. package/dist/utils.js +31 -0
  45. package/dist/utils.js.map +1 -0
  46. package/package.json +12 -10
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { Sliders, Wrench, FileText, Upload, Eye } from 'lucide-react';
4
+ import { Textarea, Input, Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, useDisclosure, useToast, } from '@sqlrooms/ui';
5
+ import { useStoreWithAiSettings } from '../../AiSettingsSlice';
6
+ export const AiModelParameters = ({ getDefaultInstructions, }) => {
7
+ const maxSteps = useStoreWithAiSettings((s) => s.getAiSettings().modelParameters.maxSteps);
8
+ const setMaxStepsAiChatUi = useStoreWithAiSettings((s) => s.setMaxSteps);
9
+ const additionalInstruction = useStoreWithAiSettings((s) => s.getAiSettings().modelParameters.additionalInstruction);
10
+ const setAdditionalInstruction = useStoreWithAiSettings((s) => s.setAdditionalInstruction);
11
+ const fileInputRef = useRef(null);
12
+ const { toast } = useToast();
13
+ const { isOpen, onOpen, onClose } = useDisclosure();
14
+ const handleMaxStepsChange = (value) => {
15
+ setMaxStepsAiChatUi(value);
16
+ };
17
+ const handleAdditionalInstructionChange = (value) => {
18
+ setAdditionalInstruction(value);
19
+ };
20
+ const handleFileUpload = async (event) => {
21
+ const file = event.target.files?.[0];
22
+ if (!file)
23
+ return;
24
+ // Validate file type
25
+ const allowedTypes = ['text/plain', 'text/markdown', 'application/json'];
26
+ const allowedExtensions = ['.txt', '.md', '.json', '.text'];
27
+ const fileExtension = file.name
28
+ .toLowerCase()
29
+ .slice(file.name.lastIndexOf('.'));
30
+ if (!allowedTypes.includes(file.type) &&
31
+ !allowedExtensions.includes(fileExtension)) {
32
+ toast({
33
+ title: 'Invalid file type',
34
+ description: 'Please select a text file (.txt, .md, .json)',
35
+ variant: 'destructive',
36
+ });
37
+ return;
38
+ }
39
+ // Validate file size (max 1MB)
40
+ const maxSize = 1024 * 1024; // 1MB
41
+ if (file.size > maxSize) {
42
+ toast({
43
+ title: 'File too large',
44
+ description: 'File size must be less than 1MB',
45
+ variant: 'destructive',
46
+ });
47
+ return;
48
+ }
49
+ try {
50
+ const text = await file.text();
51
+ setAdditionalInstruction(text);
52
+ toast({
53
+ title: 'File uploaded successfully',
54
+ description: 'System instructions have been updated from the uploaded file.',
55
+ });
56
+ }
57
+ catch (error) {
58
+ console.error('Error reading file:', error);
59
+ toast({
60
+ title: 'Error reading file',
61
+ description: 'Please try again.',
62
+ variant: 'destructive',
63
+ });
64
+ }
65
+ // Reset the input so the same file can be selected again
66
+ if (fileInputRef.current) {
67
+ fileInputRef.current.value = '';
68
+ }
69
+ };
70
+ const handleUploadButtonClick = () => {
71
+ fileInputRef.current?.click();
72
+ };
73
+ const handleViewFullInstructions = () => {
74
+ onOpen();
75
+ };
76
+ // compose full instructions
77
+ const getFullInstructions = () => {
78
+ if (!getDefaultInstructions) {
79
+ return additionalInstruction || 'No default instructions available.';
80
+ }
81
+ const defaultInstructions = getDefaultInstructions();
82
+ if (additionalInstruction) {
83
+ return `${defaultInstructions}\n\nAdditional Instructions:\n\n${additionalInstruction}`;
84
+ }
85
+ else {
86
+ return `${defaultInstructions}.`;
87
+ }
88
+ };
89
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "text-md flex items-center gap-2 pb-6 font-medium", children: [_jsx(Sliders, { className: "h-4 w-4" }), "Model Parameters"] }), _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(Wrench, { className: "h-4 w-4" }), "Max Tool Steps"] }), _jsx("div", { className: "flex items-center gap-2", children: _jsx(Input, { type: "number", min: "1", max: "20", step: "1", value: maxSteps, onChange: (e) => handleMaxStepsChange(parseInt(e.target.value) || 1), className: "flex-1" }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(FileText, { className: "h-4 w-4" }), "Additional Instructions"] }), _jsx(Textarea, { value: additionalInstruction, onChange: (e) => handleAdditionalInstructionChange(e.target.value), placeholder: "Enter custom system instructions for the AI model...", className: "min-h-[80px] resize-y", autoResize: false }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: handleUploadButtonClick, className: "flex items-center gap-2", children: [_jsx(Upload, { className: "h-4 w-4" }), "Upload File"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleViewFullInstructions, className: "flex items-center gap-2", children: [_jsx(Eye, { className: "h-4 w-4" }), "View Instructions"] })] }), _jsx(Input, { ref: fileInputRef, type: "file", accept: ".txt,.md,.json,.text,text/plain,text/markdown,application/json", onChange: handleFileUpload, style: { display: 'none' } })] })] }), _jsx(Dialog, { open: isOpen, onOpenChange: onClose, children: _jsxs(DialogContent, { className: "flex h-[80vh] max-w-4xl flex-col", children: [_jsxs(DialogHeader, { className: "flex-shrink-0", children: [_jsx(DialogTitle, { children: "Full System Instructions" }), _jsx(DialogDescription, { children: "Complete system instructions that will be sent to the AI model, including default instructions and your additional custom instructions." })] }), _jsx("div", { className: "mt-4 min-h-0 flex-1 overflow-hidden", children: _jsx("div", { className: "bg-muted/50 h-full overflow-auto rounded-lg p-4", children: _jsx("pre", { className: "overflow-wrap-anywhere w-full max-w-full break-words font-mono text-sm leading-relaxed", children: getFullInstructions() }) }) })] }) })] }));
90
+ };
91
+ //# sourceMappingURL=AiModelParameters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelParameters.js","sourceRoot":"","sources":["../../../src/components/settings/AiModelParameters.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAK,MAAM,EAAC,MAAM,OAAO,CAAC;AACjC,OAAO,EAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAC,MAAM,cAAc,CAAC;AACpE,OAAO,EACL,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,QAAQ,GACT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAM7D,MAAM,CAAC,MAAM,iBAAiB,GAA+B,CAAC,EAC5D,sBAAsB,GACvB,EAAE,EAAE;IACH,MAAM,QAAQ,GAAG,sBAAsB,CACrC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,eAAe,CAAC,QAAQ,CAClD,CAAC;IACF,MAAM,mBAAmB,GAAG,sBAAsB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAEzE,MAAM,qBAAqB,GAAG,sBAAsB,CAClD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,eAAe,CAAC,qBAAqB,CAC/D,CAAC;IACF,MAAM,wBAAwB,GAAG,sBAAsB,CACrD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAClC,CAAC;IAEF,MAAM,YAAY,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IACpD,MAAM,EAAC,KAAK,EAAC,GAAG,QAAQ,EAAE,CAAC;IAE3B,MAAM,EAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAC,GAAG,aAAa,EAAE,CAAC;IAElD,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAE,EAAE;QAC7C,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,iCAAiC,GAAG,CAAC,KAAa,EAAE,EAAE;QAC1D,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,KAAK,EAC5B,KAA0C,EAC1C,EAAE;QACF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,qBAAqB;QACrB,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC;QACzE,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI;aAC5B,WAAW,EAAE;aACb,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAErC,IACE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YACjC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,aAAa,CAAC,EAC1C,CAAC;YACD,KAAK,CAAC;gBACJ,KAAK,EAAE,mBAAmB;gBAC1B,WAAW,EAAE,8CAA8C;gBAC3D,OAAO,EAAE,aAAa;aACvB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,+BAA+B;QAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM;QACnC,IAAI,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;YACxB,KAAK,CAAC;gBACJ,KAAK,EAAE,gBAAgB;gBACvB,WAAW,EAAE,iCAAiC;gBAC9C,OAAO,EAAE,aAAa;aACvB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,wBAAwB,CAAC,IAAI,CAAC,CAAC;YAC/B,KAAK,CAAC;gBACJ,KAAK,EAAE,4BAA4B;gBACnC,WAAW,EACT,+DAA+D;aAClE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;YAC5C,KAAK,CAAC;gBACJ,KAAK,EAAE,oBAAoB;gBAC3B,WAAW,EAAE,mBAAmB;gBAChC,OAAO,EAAE,aAAa;aACvB,CAAC,CAAC;QACL,CAAC;QAED,yDAAyD;QACzD,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;YACzB,YAAY,CAAC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;QAClC,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACnC,YAAY,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAChC,CAAC,CAAC;IAEF,MAAM,0BAA0B,GAAG,GAAG,EAAE;QACtC,MAAM,EAAE,CAAC;IACX,CAAC,CAAC;IAEF,4BAA4B;IAC5B,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAC/B,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5B,OAAO,qBAAqB,IAAI,oCAAoC,CAAC;QACvE,CAAC;QAED,MAAM,mBAAmB,GAAG,sBAAsB,EAAE,CAAC;QAErD,IAAI,qBAAqB,EAAE,CAAC;YAC1B,OAAO,GAAG,mBAAmB,mCAAmC,qBAAqB,EAAE,CAAC;QAC1F,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,mBAAmB,GAAG,CAAC;QACnC,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,iBAAO,SAAS,EAAC,kDAAkD,aACjE,KAAC,OAAO,IAAC,SAAS,EAAC,SAAS,GAAG,wBAEzB,EACR,eAAK,SAAS,EAAC,wBAAwB,aAErC,eAAK,SAAS,EAAC,WAAW,aACxB,iBAAO,SAAS,EAAC,6CAA6C,aAC5D,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,GAAG,sBAExB,EACR,cAAK,SAAS,EAAC,yBAAyB,YACtC,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,GAAG,EAAC,GAAG,EACP,GAAG,EAAC,IAAI,EACR,IAAI,EAAC,GAAG,EACR,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACd,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAErD,SAAS,EAAC,QAAQ,GAClB,GACE,IACF,EAGN,eAAK,SAAS,EAAC,WAAW,aACxB,iBAAO,SAAS,EAAC,6CAA6C,aAC5D,KAAC,QAAQ,IAAC,SAAS,EAAC,SAAS,GAAG,+BAE1B,EACR,KAAC,QAAQ,IACP,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iCAAiC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAClE,WAAW,EAAC,sDAAsD,EAClE,SAAS,EAAC,uBAAuB,EACjC,UAAU,EAAE,KAAK,GACjB,EACF,eAAK,SAAS,EAAC,yBAAyB,aACtC,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,uBAAuB,EAChC,SAAS,EAAC,yBAAyB,aAEnC,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,GAAG,mBAEvB,EACT,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,0BAA0B,EACnC,SAAS,EAAC,yBAAyB,aAEnC,KAAC,GAAG,IAAC,SAAS,EAAC,SAAS,GAAG,yBAEpB,IACL,EACN,KAAC,KAAK,IACJ,GAAG,EAAE,YAAY,EACjB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,gEAAgE,EACvE,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,EAAC,OAAO,EAAE,MAAM,EAAC,GACxB,IACE,IACF,EAGN,KAAC,MAAM,IAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,YACzC,MAAC,aAAa,IAAC,SAAS,EAAC,kCAAkC,aACzD,MAAC,YAAY,IAAC,SAAS,EAAC,eAAe,aACrC,KAAC,WAAW,2CAAuC,EACnD,KAAC,iBAAiB,0JAIE,IACP,EACf,cAAK,SAAS,EAAC,qCAAqC,YAClD,cAAK,SAAS,EAAC,iDAAiD,YAC9D,cAAK,SAAS,EAAC,wFAAwF,YACpG,mBAAmB,EAAE,GAClB,GACF,GACF,IACQ,GACT,IACL,CACP,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import {FC, useRef} from 'react';\nimport {Sliders, Wrench, FileText, Upload, Eye} from 'lucide-react';\nimport {\n Textarea,\n Input,\n Button,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n useDisclosure,\n useToast,\n} from '@sqlrooms/ui';\nimport {useStoreWithAiSettings} from '../../AiSettingsSlice';\n\nexport interface AiModelParametersProps {\n getDefaultInstructions?: () => string;\n}\n\nexport const AiModelParameters: FC<AiModelParametersProps> = ({\n getDefaultInstructions,\n}) => {\n const maxSteps = useStoreWithAiSettings(\n (s) => s.getAiSettings().modelParameters.maxSteps,\n );\n const setMaxStepsAiChatUi = useStoreWithAiSettings((s) => s.setMaxSteps);\n\n const additionalInstruction = useStoreWithAiSettings(\n (s) => s.getAiSettings().modelParameters.additionalInstruction,\n );\n const setAdditionalInstruction = useStoreWithAiSettings(\n (s) => s.setAdditionalInstruction,\n );\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n const {toast} = useToast();\n\n const {isOpen, onOpen, onClose} = useDisclosure();\n\n const handleMaxStepsChange = (value: number) => {\n setMaxStepsAiChatUi(value);\n };\n\n const handleAdditionalInstructionChange = (value: string) => {\n setAdditionalInstruction(value);\n };\n\n const handleFileUpload = async (\n event: React.ChangeEvent<HTMLInputElement>,\n ) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n // Validate file type\n const allowedTypes = ['text/plain', 'text/markdown', 'application/json'];\n const allowedExtensions = ['.txt', '.md', '.json', '.text'];\n const fileExtension = file.name\n .toLowerCase()\n .slice(file.name.lastIndexOf('.'));\n\n if (\n !allowedTypes.includes(file.type) &&\n !allowedExtensions.includes(fileExtension)\n ) {\n toast({\n title: 'Invalid file type',\n description: 'Please select a text file (.txt, .md, .json)',\n variant: 'destructive',\n });\n return;\n }\n\n // Validate file size (max 1MB)\n const maxSize = 1024 * 1024; // 1MB\n if (file.size > maxSize) {\n toast({\n title: 'File too large',\n description: 'File size must be less than 1MB',\n variant: 'destructive',\n });\n return;\n }\n\n try {\n const text = await file.text();\n setAdditionalInstruction(text);\n toast({\n title: 'File uploaded successfully',\n description:\n 'System instructions have been updated from the uploaded file.',\n });\n } catch (error) {\n console.error('Error reading file:', error);\n toast({\n title: 'Error reading file',\n description: 'Please try again.',\n variant: 'destructive',\n });\n }\n\n // Reset the input so the same file can be selected again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n const handleUploadButtonClick = () => {\n fileInputRef.current?.click();\n };\n\n const handleViewFullInstructions = () => {\n onOpen();\n };\n\n // compose full instructions\n const getFullInstructions = () => {\n if (!getDefaultInstructions) {\n return additionalInstruction || 'No default instructions available.';\n }\n\n const defaultInstructions = getDefaultInstructions();\n\n if (additionalInstruction) {\n return `${defaultInstructions}\\n\\nAdditional Instructions:\\n\\n${additionalInstruction}`;\n } else {\n return `${defaultInstructions}.`;\n }\n };\n\n return (\n <div className=\"space-y-2\">\n <label className=\"text-md flex items-center gap-2 pb-6 font-medium\">\n <Sliders className=\"h-4 w-4\" />\n Model Parameters\n </label>\n <div className=\"grid grid-cols-1 gap-4\">\n {/* Max Steps */}\n <div className=\"space-y-2\">\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <Wrench className=\"h-4 w-4\" />\n Max Tool Steps\n </label>\n <div className=\"flex items-center gap-2\">\n <Input\n type=\"number\"\n min=\"1\"\n max=\"20\"\n step=\"1\"\n value={maxSteps}\n onChange={(e) =>\n handleMaxStepsChange(parseInt(e.target.value) || 1)\n }\n className=\"flex-1\"\n />\n </div>\n </div>\n\n {/* Additional Instruction */}\n <div className=\"space-y-2\">\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <FileText className=\"h-4 w-4\" />\n Additional Instructions\n </label>\n <Textarea\n value={additionalInstruction}\n onChange={(e) => handleAdditionalInstructionChange(e.target.value)}\n placeholder=\"Enter custom system instructions for the AI model...\"\n className=\"min-h-[80px] resize-y\"\n autoResize={false}\n />\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleUploadButtonClick}\n className=\"flex items-center gap-2\"\n >\n <Upload className=\"h-4 w-4\" />\n Upload File\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleViewFullInstructions}\n className=\"flex items-center gap-2\"\n >\n <Eye className=\"h-4 w-4\" />\n View Instructions\n </Button>\n </div>\n <Input\n ref={fileInputRef}\n type=\"file\"\n accept=\".txt,.md,.json,.text,text/plain,text/markdown,application/json\"\n onChange={handleFileUpload}\n style={{display: 'none'}}\n />\n </div>\n </div>\n\n {/* Full Instructions Modal */}\n <Dialog open={isOpen} onOpenChange={onClose}>\n <DialogContent className=\"flex h-[80vh] max-w-4xl flex-col\">\n <DialogHeader className=\"flex-shrink-0\">\n <DialogTitle>Full System Instructions</DialogTitle>\n <DialogDescription>\n Complete system instructions that will be sent to the AI model,\n including default instructions and your additional custom\n instructions.\n </DialogDescription>\n </DialogHeader>\n <div className=\"mt-4 min-h-0 flex-1 overflow-hidden\">\n <div className=\"bg-muted/50 h-full overflow-auto rounded-lg p-4\">\n <pre className=\"overflow-wrap-anywhere w-full max-w-full break-words font-mono text-sm leading-relaxed\">\n {getFullInstructions()}\n </pre>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n );\n};\n"]}
@@ -0,0 +1,18 @@
1
+ import { FC } from 'react';
2
+ export interface ModelUsageData {
3
+ totalSpend: number;
4
+ maxBudget: number;
5
+ isLoadingSpend: boolean;
6
+ weeklySpend?: Array<{
7
+ date: string;
8
+ spend: number;
9
+ }>;
10
+ isLoadingWeeklySpend?: boolean;
11
+ }
12
+ type AiModelUsageProps = {
13
+ className?: string;
14
+ modelUsage?: ModelUsageData;
15
+ };
16
+ export declare const AiModelUsage: FC<AiModelUsageProps>;
17
+ export {};
18
+ //# sourceMappingURL=AiModelUsage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelUsage.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AiModelUsage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,EAAE,EAAC,MAAM,OAAO,CAAC;AAazB,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACnD,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,KAAK,iBAAiB,GAAG;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B,CAAC;AA6BF,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAgJ9C,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CreditCard, BarChart3, DollarSign } from 'lucide-react';
3
+ import { SkeletonPane } from '@sqlrooms/ui';
4
+ import { ChartContainer, ChartTooltip } from '@sqlrooms/recharts';
5
+ import { Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer, BarChart, } from 'recharts';
6
+ const fillMissingDays = (weeklySpend) => {
7
+ const today = new Date();
8
+ const pastWeek = [];
9
+ // Generate the past 7 days
10
+ for (let i = 6; i >= 0; i--) {
11
+ const date = new Date(today);
12
+ date.setDate(date.getDate() - i);
13
+ const dateString = date.toISOString().split('T')[0]; // YYYY-MM-DD format
14
+ // Find existing data for this date
15
+ const existingData = weeklySpend.find((day) => day.date === dateString);
16
+ if (existingData) {
17
+ pastWeek.push(existingData);
18
+ }
19
+ else {
20
+ // Fill missing day with 0 spend
21
+ pastWeek.push({
22
+ date: dateString || '',
23
+ spend: 0,
24
+ });
25
+ }
26
+ }
27
+ return pastWeek;
28
+ };
29
+ export const AiModelUsage = ({ className = '', modelUsage, }) => {
30
+ if (!modelUsage)
31
+ return null;
32
+ const { totalSpend, maxBudget, isLoadingSpend, weeklySpend, isLoadingWeeklySpend, } = modelUsage;
33
+ const getCurrentMonthRange = () => {
34
+ const now = new Date();
35
+ const month = now.toLocaleDateString('en-US', { month: 'long' });
36
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
37
+ return `${month} 1-${lastDay}`;
38
+ };
39
+ return (_jsxs("div", { className: `space-y-4 ${className}`, children: [_jsxs("h3", { className: "text-md flex items-center gap-2 pb-6 font-medium", children: [_jsx(CreditCard, { className: "h-4 w-4" }), "Billing & Usage"] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-xs", children: [_jsx(DollarSign, { className: "h-4 w-4" }), "Spend ", getCurrentMonthRange()] }), _jsx("div", { className: "bg-muted rounded-lg p-4", children: isLoadingSpend ? (_jsx(SkeletonPane, { n: 2, rowHeight: "32px", className: "space-y-4" })) : (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Total Spend" }), _jsxs("p", { className: "text-2xl font-bold", children: ["$", totalSpend.toFixed(3)] })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Max Budget" }), _jsxs("p", { className: "text-2xl font-bold", children: ["$", maxBudget.toFixed(2)] })] })] })) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "text-muted-foreground flex items-center gap-2 text-xs", children: [_jsx(BarChart3, { className: "h-4 w-4" }), "Daily Spending Trend"] }), _jsx("div", { className: "bg-muted h-32 rounded-lg p-4", style: { position: 'relative' }, children: isLoadingWeeklySpend ? (_jsx(SkeletonPane, { n: 7, rowHeight: "100%", className: "w-full" })) : (_jsx(ChartContainer, { config: {
40
+ spend: {
41
+ label: 'Daily Spend',
42
+ color: 'hsl(var(--primary))',
43
+ },
44
+ }, className: "h-full w-full", style: {
45
+ // Remove any default hover backgrounds
46
+ backgroundColor: 'transparent',
47
+ }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(BarChart, { data: fillMissingDays(weeklySpend || []).map((day) => ({
48
+ ...day,
49
+ dayLabel: new Date(day.date).toLocaleDateString('en-US', {
50
+ weekday: 'short',
51
+ }),
52
+ })), children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "hsl(var(--muted-foreground))", opacity: 0.3 }), _jsx(XAxis, { dataKey: "dayLabel", tick: { fontSize: 10 }, tickLine: false, axisLine: false }), _jsx(YAxis, { tick: { fontSize: 10 }, tickLine: false, axisLine: false, tickFormatter: (value) => `$${value.toFixed(3)}` }), _jsx(ChartTooltip, { content: ({ active, payload }) => {
53
+ if (active && payload && payload.length && payload[0]) {
54
+ const data = payload[0].payload;
55
+ return (_jsxs("div", { className: "bg-background border-border rounded-lg border p-2 shadow-lg", children: [_jsx("p", { className: "text-sm font-medium", children: data.date }), _jsx("p", { className: "text-sm font-medium", children: data.dayLabel }), _jsxs("p", { className: "text-muted-foreground text-sm", children: ["$", data.spend.toFixed(3)] })] }));
56
+ }
57
+ return null;
58
+ }, cursor: false }), _jsx(Bar, { dataKey: "spend", fill: "hsl(var(--primary))", radius: [2, 2, 0, 0] })] }) }) })) }), _jsx("p", { className: "text-muted-foreground text-center text-xs", children: "Last 7 days spending" })] })] })] }));
59
+ };
60
+ //# sourceMappingURL=AiModelUsage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelUsage.js","sourceRoot":"","sources":["../../../src/components/settings/AiModelUsage.tsx"],"names":[],"mappings":"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","sourcesContent":["import {FC} from 'react';\nimport {CreditCard, BarChart3, DollarSign} from 'lucide-react';\nimport {SkeletonPane} from '@sqlrooms/ui';\nimport {ChartContainer, ChartTooltip} from '@sqlrooms/recharts';\nimport {\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n ResponsiveContainer,\n BarChart,\n} from 'recharts';\n\nexport interface ModelUsageData {\n totalSpend: number;\n maxBudget: number;\n isLoadingSpend: boolean;\n weeklySpend?: Array<{date: string; spend: number}>;\n isLoadingWeeklySpend?: boolean;\n}\n\ntype AiModelUsageProps = {\n className?: string;\n modelUsage?: ModelUsageData;\n};\n\nconst fillMissingDays = (weeklySpend: Array<{date: string; spend: number}>) => {\n const today = new Date();\n const pastWeek: Array<{date: string; spend: number}> = [];\n\n // Generate the past 7 days\n for (let i = 6; i >= 0; i--) {\n const date = new Date(today);\n date.setDate(date.getDate() - i);\n const dateString = date.toISOString().split('T')[0]; // YYYY-MM-DD format\n\n // Find existing data for this date\n const existingData = weeklySpend.find((day) => day.date === dateString);\n\n if (existingData) {\n pastWeek.push(existingData);\n } else {\n // Fill missing day with 0 spend\n pastWeek.push({\n date: dateString || '',\n spend: 0,\n });\n }\n }\n\n return pastWeek;\n};\n\nexport const AiModelUsage: FC<AiModelUsageProps> = ({\n className = '',\n modelUsage,\n}) => {\n if (!modelUsage) return null;\n\n const {\n totalSpend,\n maxBudget,\n isLoadingSpend,\n weeklySpend,\n isLoadingWeeklySpend,\n } = modelUsage;\n\n const getCurrentMonthRange = () => {\n const now = new Date();\n const month = now.toLocaleDateString('en-US', {month: 'long'});\n const lastDay = new Date(\n now.getFullYear(),\n now.getMonth() + 1,\n 0,\n ).getDate();\n return `${month} 1-${lastDay}`;\n };\n\n return (\n <div className={`space-y-4 ${className}`}>\n <h3 className=\"text-md flex items-center gap-2 pb-6 font-medium\">\n <CreditCard className=\"h-4 w-4\" />\n Billing & Usage\n </h3>\n <div className=\"flex flex-col gap-4\">\n <p className=\"text-muted-foreground flex items-center gap-2 text-xs\">\n <DollarSign className=\"h-4 w-4\" />\n Spend {getCurrentMonthRange()}\n </p>\n\n {/* Spending Overview */}\n <div className=\"bg-muted rounded-lg p-4\">\n {isLoadingSpend ? (\n <SkeletonPane n={2} rowHeight=\"32px\" className=\"space-y-4\" />\n ) : (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"text-center\">\n <p className=\"text-muted-foreground text-sm\">Total Spend</p>\n <p className=\"text-2xl font-bold\">${totalSpend.toFixed(3)}</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-muted-foreground text-sm\">Max Budget</p>\n <p className=\"text-2xl font-bold\">${maxBudget.toFixed(2)}</p>\n </div>\n </div>\n )}\n </div>\n\n {/* Daily Spending Chart */}\n <div className=\"space-y-2\">\n <label className=\"text-muted-foreground flex items-center gap-2 text-xs\">\n <BarChart3 className=\"h-4 w-4\" />\n Daily Spending Trend\n </label>\n <div\n className=\"bg-muted h-32 rounded-lg p-4\"\n style={{position: 'relative'}}\n >\n {isLoadingWeeklySpend ? (\n <SkeletonPane n={7} rowHeight=\"100%\" className=\"w-full\" />\n ) : (\n <ChartContainer\n config={{\n spend: {\n label: 'Daily Spend',\n color: 'hsl(var(--primary))',\n },\n }}\n className=\"h-full w-full\"\n style={{\n // Remove any default hover backgrounds\n backgroundColor: 'transparent',\n }}\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart\n data={fillMissingDays(weeklySpend || []).map((day) => ({\n ...day,\n dayLabel: new Date(day.date).toLocaleDateString('en-US', {\n weekday: 'short',\n }),\n }))}\n >\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"hsl(var(--muted-foreground))\"\n opacity={0.3}\n />\n <XAxis\n dataKey=\"dayLabel\"\n tick={{fontSize: 10}}\n tickLine={false}\n axisLine={false}\n />\n <YAxis\n tick={{fontSize: 10}}\n tickLine={false}\n axisLine={false}\n tickFormatter={(value) => `$${value.toFixed(3)}`}\n />\n <ChartTooltip\n content={({active, payload}: any) => {\n if (active && payload && payload.length && payload[0]) {\n const data = payload[0].payload;\n return (\n <div className=\"bg-background border-border rounded-lg border p-2 shadow-lg\">\n <p className=\"text-sm font-medium\">{data.date}</p>\n <p className=\"text-sm font-medium\">\n {data.dayLabel}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n ${data.spend.toFixed(3)}\n </p>\n </div>\n );\n }\n return null;\n }}\n cursor={false}\n />\n <Bar\n dataKey=\"spend\"\n fill=\"hsl(var(--primary))\"\n radius={[2, 2, 0, 0]}\n />\n </BarChart>\n </ResponsiveContainer>\n </ChartContainer>\n )}\n </div>\n <p className=\"text-muted-foreground text-center text-xs\">\n Last 7 days spending\n </p>\n </div>\n </div>\n </div>\n );\n};\n"]}
@@ -0,0 +1,10 @@
1
+ import { FC } from 'react';
2
+ export interface AiModelsSettingsProps {
3
+ showProviderModels?: boolean;
4
+ showCustomModels?: boolean;
5
+ allowEditProviderModels?: boolean;
6
+ allowCustomModels?: boolean;
7
+ className?: string;
8
+ }
9
+ export declare const AiModelsSettings: FC<AiModelsSettingsProps>;
10
+ //# sourceMappingURL=AiModelsSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelsSettings.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AiModelsSettings.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAC,EAAE,EAAoB,MAAM,OAAO,CAAC;AA2BnD,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA8pBtD,CAAC"}
@@ -0,0 +1,229 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Button, Input, Label, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger, useDisclosure, useToast, } from '@sqlrooms/ui';
4
+ import { Blocks, CirclePlus, Cpu, Key, Plus, Server, Settings, Trash2, } from 'lucide-react';
5
+ import { useStoreWithAiSettings } from '../../AiSettingsSlice';
6
+ import { useStoreWithAi } from '../../AiSlice';
7
+ export const AiModelsSettings = ({ className = '', allowEditProviderModels = true, allowCustomModels = true, }) => {
8
+ const { toast } = useToast();
9
+ const aiConfig = useStoreWithAiSettings((s) => s.getAiSettings());
10
+ const setAiModel = useStoreWithAi((s) => s.ai.setAiModel);
11
+ const addModelToProvider = useStoreWithAiSettings((s) => s.addModelToProvider);
12
+ const removeModelFromProvider = useStoreWithAiSettings((s) => s.removeModelFromProvider);
13
+ const addCustomModel = useStoreWithAiSettings((s) => s.addCustomModel);
14
+ const updateCustomModel = useStoreWithAiSettings((s) => s.updateCustomModel);
15
+ const removeCustomModel = useStoreWithAiSettings((s) => s.removeCustomModel);
16
+ const providers = useMemo(() => Object.entries(aiConfig.providers), [aiConfig.providers]);
17
+ const customModels = aiConfig.customModels || [];
18
+ // Dialog state for adding a custom model
19
+ const { isOpen: isCustomModelDialogOpen, onOpen: openCustomModelDialog, onClose: closeCustomModelDialog, } = useDisclosure();
20
+ const [customName, setCustomName] = useState('');
21
+ const [customBaseUrl, setCustomBaseUrl] = useState('');
22
+ const [customApiKey, setCustomApiKey] = useState('');
23
+ const [baseUrlError, setBaseUrlError] = useState(false);
24
+ // Dialog state for editing a custom model
25
+ const { isOpen: isEditCustomModelDialogOpen, onOpen: openEditCustomModelDialog, onClose: closeEditCustomModelDialog, } = useDisclosure();
26
+ const [editingModel, setEditingModel] = useState(null);
27
+ const [editBaseUrlError, setEditBaseUrlError] = useState(false);
28
+ // Dialog state for adding a model to a provider
29
+ const { isOpen: isAddProviderModelDialogOpen, onOpen: openAddProviderModelDialog, onClose: closeAddProviderModelDialog, } = useDisclosure();
30
+ const [selectedProviderKey, setSelectedProviderKey] = useState('');
31
+ const [newModelName, setNewModelName] = useState('');
32
+ // Dialog state for deleting a model
33
+ const { isOpen: isDeleteModelDialogOpen, onOpen: openDeleteModelDialog, onClose: closeDeleteModelDialog, } = useDisclosure();
34
+ const [modelToDelete, setModelToDelete] = useState(null);
35
+ const handleAddCustomToSession = () => {
36
+ // Reset error states
37
+ setBaseUrlError(false);
38
+ // Validate required fields
39
+ if (!customName.trim()) {
40
+ toast({
41
+ title: 'Model Name Required',
42
+ description: 'Please enter a model name.',
43
+ variant: 'destructive',
44
+ });
45
+ return;
46
+ }
47
+ if (!customBaseUrl.trim()) {
48
+ setBaseUrlError(true);
49
+ toast({
50
+ title: 'Base URL Required',
51
+ description: 'Please enter a base URL for the model.',
52
+ variant: 'destructive',
53
+ });
54
+ return;
55
+ }
56
+ const trimmedName = customName.trim();
57
+ // Add the custom model to the config
58
+ addCustomModel(customBaseUrl.trim(), customApiKey.trim(), trimmedName);
59
+ // Update the current session to use this custom model
60
+ setAiModel('custom', trimmedName);
61
+ // Show success message
62
+ toast({
63
+ title: 'Custom Model Added',
64
+ description: `Successfully added "${trimmedName}" to your custom models.`,
65
+ variant: 'default',
66
+ });
67
+ // Reset form
68
+ setCustomName('');
69
+ setCustomBaseUrl('');
70
+ setCustomApiKey('');
71
+ setBaseUrlError(false);
72
+ closeCustomModelDialog();
73
+ };
74
+ const handleEditCustomModel = (modelName) => {
75
+ const customModel = customModels.find((cm) => cm.modelName === modelName);
76
+ if (customModel) {
77
+ setEditingModel({
78
+ oldModelName: modelName,
79
+ modelName: modelName,
80
+ baseUrl: customModel.baseUrl,
81
+ apiKey: customModel.apiKey,
82
+ });
83
+ setEditBaseUrlError(false);
84
+ openEditCustomModelDialog();
85
+ }
86
+ };
87
+ const handleUpdateCustomModel = () => {
88
+ if (!editingModel)
89
+ return;
90
+ // Reset error states
91
+ setEditBaseUrlError(false);
92
+ // Validate required fields
93
+ if (!editingModel.modelName.trim()) {
94
+ toast({
95
+ title: 'Model Name Required',
96
+ description: 'Please enter a model name.',
97
+ variant: 'destructive',
98
+ });
99
+ return;
100
+ }
101
+ if (!editingModel.baseUrl.trim()) {
102
+ setEditBaseUrlError(true);
103
+ toast({
104
+ title: 'Base URL Required',
105
+ description: 'Please enter a base URL for the model.',
106
+ variant: 'destructive',
107
+ });
108
+ return;
109
+ }
110
+ const trimmedName = editingModel.modelName.trim();
111
+ const trimmedBaseUrl = editingModel.baseUrl.trim();
112
+ const trimmedApiKey = editingModel.apiKey.trim();
113
+ // Check for duplicate model names (excluding the current model being edited)
114
+ const duplicateModel = customModels.find((cm) => cm.modelName.toLowerCase() === trimmedName.toLowerCase() &&
115
+ cm.modelName !== editingModel.oldModelName);
116
+ if (duplicateModel) {
117
+ toast({
118
+ title: 'Model Name Already Exists',
119
+ description: `A custom model with the name "${trimmedName}" already exists.`,
120
+ variant: 'destructive',
121
+ });
122
+ return;
123
+ }
124
+ // Update the custom model
125
+ updateCustomModel(editingModel.oldModelName, trimmedBaseUrl, trimmedApiKey, trimmedName);
126
+ // Show success message
127
+ toast({
128
+ title: 'Custom Model Updated',
129
+ description: `Successfully updated "${trimmedName}".`,
130
+ variant: 'default',
131
+ });
132
+ // Reset form and close dialog
133
+ setEditingModel(null);
134
+ setEditBaseUrlError(false);
135
+ closeEditCustomModelDialog();
136
+ };
137
+ const handleOpenAddProviderModelDialog = (providerKey) => {
138
+ setSelectedProviderKey(providerKey);
139
+ setNewModelName('');
140
+ openAddProviderModelDialog();
141
+ };
142
+ const handleAddModelToProvider = () => {
143
+ if (!selectedProviderKey || !newModelName.trim())
144
+ return;
145
+ // Check for duplicates
146
+ const provider = aiConfig.providers[selectedProviderKey];
147
+ const modelExists = provider?.models.some((model) => model.modelName.toLowerCase() === newModelName.trim().toLowerCase());
148
+ if (modelExists) {
149
+ toast({
150
+ title: 'Model Already Exists',
151
+ description: `The model "${newModelName.trim()}" already exists in this provider.`,
152
+ variant: 'destructive',
153
+ });
154
+ return;
155
+ }
156
+ addModelToProvider(selectedProviderKey, newModelName.trim());
157
+ setNewModelName('');
158
+ closeAddProviderModelDialog();
159
+ };
160
+ const handleDeleteModel = (type, providerKey, modelName) => {
161
+ setModelToDelete({ type, providerKey, modelName });
162
+ openDeleteModelDialog();
163
+ };
164
+ const confirmDeleteModel = () => {
165
+ if (!modelToDelete)
166
+ return;
167
+ if (modelToDelete.type === 'provider' && modelToDelete.providerKey) {
168
+ removeModelFromProvider(modelToDelete.providerKey, modelToDelete.modelName);
169
+ toast({
170
+ title: 'Model Deleted',
171
+ description: `Successfully removed "${modelToDelete.modelName}" from the provider.`,
172
+ variant: 'default',
173
+ });
174
+ }
175
+ else if (modelToDelete.type === 'custom') {
176
+ removeCustomModel(modelToDelete.modelName);
177
+ toast({
178
+ title: 'Custom Model Deleted',
179
+ description: `Successfully removed "${modelToDelete.modelName}" from custom models.`,
180
+ variant: 'default',
181
+ });
182
+ }
183
+ setModelToDelete(null);
184
+ closeDeleteModelDialog();
185
+ };
186
+ return (_jsxs("div", { className: `space-y-2 ${className}`, children: [_jsxs("label", { className: "text-md flex items-center gap-2 pb-4 font-medium", children: [_jsx(Blocks, { className: "h-4 w-4" }), "Models"] }), _jsxs("div", { className: "w-full space-y-4", children: [providers.map(([providerKey, provider]) => (_jsxs("div", { className: "flex w-full items-start gap-4", children: [_jsx("div", { className: "flex w-20 items-start justify-start text-sm font-medium", children: providerKey.charAt(0).toUpperCase() + providerKey.slice(1) }), _jsxs("div", { className: "flex flex-1 flex-col items-start gap-2", children: [provider.models.map((m) => (_jsxs("div", { className: "flex w-full items-center justify-between gap-2 border-b p-1 text-xs", children: [_jsx("span", { className: "text-foreground/90", children: m.modelName }), allowEditProviderModels && (_jsx(Button, { size: "xs", variant: "ghost", onClick: () => handleDeleteModel('provider', providerKey, m.modelName), className: "h-6 w-6 p-0 text-gray-500 hover:bg-gray-100 hover:text-gray-700", children: _jsx(Trash2, { className: "h-3 w-3" }) }))] }, m.modelName))), allowEditProviderModels && (_jsxs(Button, { size: "xs", variant: "secondary", onClick: () => handleOpenAddProviderModelDialog(providerKey), children: [_jsx(CirclePlus, { className: "h-3 w-3" }), "Add"] }))] }), _jsx("div", { className: "flex w-12 items-start justify-end" })] }, providerKey))), allowCustomModels && (_jsxs("div", { className: "flex w-full items-start gap-4", children: [_jsx("div", { className: "flex w-20 items-start justify-start text-sm font-medium", children: "Custom" }), _jsxs("div", { className: "flex flex-1 flex-col items-start gap-2", children: [customModels.map((cm) => (_jsxs("div", { className: "flex w-full items-center justify-between gap-2 border-b p-1 text-xs", children: [_jsx("span", { className: "text-foreground/90", children: cm.modelName }), _jsxs("div", { className: "flex w-full items-center justify-end", children: [_jsx(Button, { size: "xs", variant: "ghost", onClick: () => handleDeleteModel('custom', undefined, cm.modelName), className: "h-6 w-6 p-0 text-gray-500 hover:bg-gray-100 hover:text-gray-700", children: _jsx(Trash2, { className: "h-3 w-3" }) }), _jsx(Button, { size: "xs", variant: "ghost", onClick: () => handleEditCustomModel(cm.modelName), children: _jsx(Settings, { className: "h-3 w-3" }) })] })] }, cm.modelName))), _jsxs(Button, { size: "xs", variant: "secondary", onClick: () => {
187
+ setCustomName('');
188
+ setCustomBaseUrl('');
189
+ setCustomApiKey('');
190
+ setBaseUrlError(false);
191
+ openCustomModelDialog();
192
+ }, children: [_jsx(CirclePlus, { className: "h-3 w-3" }), "Add"] })] }), _jsx("div", { className: "flex w-12 items-start justify-end" })] }))] }), allowCustomModels && (_jsxs(Dialog, { open: isCustomModelDialogOpen, onOpenChange: (open) => {
193
+ if (open) {
194
+ openCustomModelDialog();
195
+ }
196
+ else {
197
+ setBaseUrlError(false);
198
+ closeCustomModelDialog();
199
+ }
200
+ }, children: [_jsx(DialogTrigger, { asChild: true }), _jsxs(DialogContent, { className: "border-0 p-5", children: [_jsxs(DialogHeader, { className: "mb-1", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Blocks, { className: "h-4 w-4" }), " Add Custom Model"] }), _jsx(DialogDescription, { className: "text-xs", children: "Provide connection details for your model provider." })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "custom-name", className: "w-20 text-sm", children: "Name" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Cpu, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "custom-name", value: customName, onChange: (e) => setCustomName(e.target.value), placeholder: "e.g., My cool model", className: "pl-8" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "custom-baseUrl", className: "w-20 text-sm", children: "baseUrl" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Server, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "custom-baseUrl", value: customBaseUrl, onChange: (e) => {
201
+ setCustomBaseUrl(e.target.value);
202
+ if (baseUrlError && e.target.value.trim()) {
203
+ setBaseUrlError(false);
204
+ }
205
+ }, placeholder: "https://api.example.com", className: `pl-8 ${baseUrlError ? 'border-red-500 focus:border-red-500' : ''}` })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "custom-apiKey", className: "w-20 text-sm", children: "API key" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Key, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "custom-apiKey", type: "password", value: customApiKey, onChange: (e) => setCustomApiKey(e.target.value), placeholder: "Optional", className: "pl-8" })] })] }), _jsx("div", { className: "flex justify-end pt-1", children: _jsxs(Button, { size: "sm", onClick: handleAddCustomToSession, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Add"] }) })] })] })] })), _jsxs(Dialog, { open: isAddProviderModelDialogOpen, onOpenChange: (open) => open ? openAddProviderModelDialog() : closeAddProviderModelDialog(), children: [_jsx(DialogTrigger, { asChild: true }), _jsxs(DialogContent, { className: "border-0 p-5", children: [_jsxs(DialogHeader, { className: "mb-1", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Blocks, { className: "h-4 w-4" }), " Add Model to Provider"] }), _jsxs(DialogDescription, { className: "text-xs", children: ["Add a new model to", ' ', selectedProviderKey &&
206
+ selectedProviderKey.charAt(0).toUpperCase() +
207
+ selectedProviderKey.slice(1)] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "new-model-name", className: "w-24 text-sm", children: "Model Name" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Cpu, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "new-model-name", value: newModelName, onChange: (e) => setNewModelName(e.target.value), placeholder: "e.g., gpt-4-turbo", className: "pl-8" })] })] }), _jsx("div", { className: "flex justify-end pt-1", children: _jsxs(Button, { size: "sm", onClick: handleAddModelToProvider, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Add Model"] }) })] })] })] }), allowCustomModels && (_jsxs(Dialog, { open: isEditCustomModelDialogOpen, onOpenChange: (open) => {
208
+ if (open) {
209
+ openEditCustomModelDialog();
210
+ }
211
+ else {
212
+ setEditBaseUrlError(false);
213
+ closeEditCustomModelDialog();
214
+ }
215
+ }, children: [_jsx(DialogTrigger, { asChild: true }), _jsxs(DialogContent, { className: "border-0 p-5", children: [_jsxs(DialogHeader, { className: "mb-1", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Settings, { className: "h-4 w-4" }), " Edit Custom Model"] }), _jsx(DialogDescription, { className: "text-xs", children: "Update the connection details for your custom model." })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "edit-model-name", className: "w-20 text-sm", children: "Name" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Cpu, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "edit-model-name", value: editingModel?.modelName || '', onChange: (e) => setEditingModel((prev) => prev ? { ...prev, modelName: e.target.value } : null), placeholder: "e.g., My cool model", className: "pl-8" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "edit-model-baseUrl", className: "w-20 text-sm", children: "baseUrl" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Server, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "edit-model-baseUrl", value: editingModel?.baseUrl || '', onChange: (e) => {
216
+ setEditingModel((prev) => prev ? { ...prev, baseUrl: e.target.value } : null);
217
+ if (editBaseUrlError && e.target.value.trim()) {
218
+ setEditBaseUrlError(false);
219
+ }
220
+ }, placeholder: "https://api.example.com", className: `pl-8 ${editBaseUrlError ? 'border-red-500 focus:border-red-500' : ''}` })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Label, { htmlFor: "edit-model-apiKey", className: "w-20 text-sm", children: "API key" }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Key, { className: "absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2" }), _jsx(Input, { id: "edit-model-apiKey", type: "password", value: editingModel?.apiKey || '', onChange: (e) => setEditingModel((prev) => prev ? { ...prev, apiKey: e.target.value } : null), placeholder: "Optional", className: "pl-8" })] })] }), _jsxs("div", { className: "flex justify-end gap-2 pt-1", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => {
221
+ setEditingModel(null);
222
+ setEditBaseUrlError(false);
223
+ closeEditCustomModelDialog();
224
+ }, children: "Cancel" }), _jsxs(Button, { size: "sm", onClick: handleUpdateCustomModel, children: [_jsx(Settings, { className: "mr-2 h-4 w-4" }), " Update"] })] })] })] })] })), (allowEditProviderModels || allowCustomModels) && (_jsxs(Dialog, { open: isDeleteModelDialogOpen, onOpenChange: (open) => open ? openDeleteModelDialog() : closeDeleteModelDialog(), children: [_jsx(DialogTrigger, { asChild: true }), _jsxs(DialogContent, { className: "border-0 p-5", children: [_jsxs(DialogHeader, { className: "mb-1", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Trash2, { className: "h-4 w-4 text-red-500" }), " Delete Model"] }), _jsxs(DialogDescription, { className: "text-xs", children: ["Are you sure you want to delete \"", modelToDelete?.modelName, "\"? This action cannot be undone."] })] }), _jsxs("div", { className: "flex justify-end gap-2 pt-1", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => {
225
+ setModelToDelete(null);
226
+ closeDeleteModelDialog();
227
+ }, children: "Cancel" }), _jsxs(Button, { size: "sm", variant: "destructive", onClick: confirmDeleteModel, children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), " Delete"] })] })] })] }))] }));
228
+ };
229
+ //# sourceMappingURL=AiModelsSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiModelsSettings.js","sourceRoot":"","sources":["../../../src/components/settings/AiModelsSettings.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {FC, useMemo, useState} from 'react';\nimport {\n Button,\n Input,\n Label,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogTrigger,\n useDisclosure,\n useToast,\n} from '@sqlrooms/ui';\nimport {\n Blocks,\n CirclePlus,\n Cpu,\n Key,\n Plus,\n Server,\n Settings,\n Trash2,\n} from 'lucide-react';\nimport {useStoreWithAiSettings} from '../../AiSettingsSlice';\nimport {useStoreWithAi} from '../../AiSlice';\n\nexport interface AiModelsSettingsProps {\n showProviderModels?: boolean;\n showCustomModels?: boolean;\n allowEditProviderModels?: boolean;\n allowCustomModels?: boolean;\n className?: string;\n}\n\nexport const AiModelsSettings: FC<AiModelsSettingsProps> = ({\n className = '',\n allowEditProviderModels = true,\n allowCustomModels = true,\n}) => {\n const {toast} = useToast();\n const aiConfig = useStoreWithAiSettings((s) => s.getAiSettings());\n const setAiModel = useStoreWithAi((s) => s.ai.setAiModel);\n const addModelToProvider = useStoreWithAiSettings(\n (s) => s.addModelToProvider,\n );\n const removeModelFromProvider = useStoreWithAiSettings(\n (s) => s.removeModelFromProvider,\n );\n const addCustomModel = useStoreWithAiSettings((s) => s.addCustomModel);\n const updateCustomModel = useStoreWithAiSettings((s) => s.updateCustomModel);\n const removeCustomModel = useStoreWithAiSettings((s) => s.removeCustomModel);\n\n const providers = useMemo(\n () => Object.entries(aiConfig.providers),\n [aiConfig.providers],\n );\n const customModels = aiConfig.customModels || [];\n\n // Dialog state for adding a custom model\n const {\n isOpen: isCustomModelDialogOpen,\n onOpen: openCustomModelDialog,\n onClose: closeCustomModelDialog,\n } = useDisclosure();\n const [customName, setCustomName] = useState('');\n const [customBaseUrl, setCustomBaseUrl] = useState('');\n const [customApiKey, setCustomApiKey] = useState('');\n const [baseUrlError, setBaseUrlError] = useState(false);\n\n // Dialog state for editing a custom model\n const {\n isOpen: isEditCustomModelDialogOpen,\n onOpen: openEditCustomModelDialog,\n onClose: closeEditCustomModelDialog,\n } = useDisclosure();\n const [editingModel, setEditingModel] = useState<{\n oldModelName: string;\n modelName: string;\n baseUrl: string;\n apiKey: string;\n } | null>(null);\n const [editBaseUrlError, setEditBaseUrlError] = useState(false);\n\n // Dialog state for adding a model to a provider\n const {\n isOpen: isAddProviderModelDialogOpen,\n onOpen: openAddProviderModelDialog,\n onClose: closeAddProviderModelDialog,\n } = useDisclosure();\n const [selectedProviderKey, setSelectedProviderKey] = useState('');\n const [newModelName, setNewModelName] = useState('');\n\n // Dialog state for deleting a model\n const {\n isOpen: isDeleteModelDialogOpen,\n onOpen: openDeleteModelDialog,\n onClose: closeDeleteModelDialog,\n } = useDisclosure();\n const [modelToDelete, setModelToDelete] = useState<{\n type: 'provider' | 'custom';\n providerKey?: string;\n modelName: string;\n } | null>(null);\n\n const handleAddCustomToSession = () => {\n // Reset error states\n setBaseUrlError(false);\n\n // Validate required fields\n if (!customName.trim()) {\n toast({\n title: 'Model Name Required',\n description: 'Please enter a model name.',\n variant: 'destructive',\n });\n return;\n }\n\n if (!customBaseUrl.trim()) {\n setBaseUrlError(true);\n toast({\n title: 'Base URL Required',\n description: 'Please enter a base URL for the model.',\n variant: 'destructive',\n });\n return;\n }\n\n const trimmedName = customName.trim();\n\n // Add the custom model to the config\n addCustomModel(customBaseUrl.trim(), customApiKey.trim(), trimmedName);\n\n // Update the current session to use this custom model\n setAiModel('custom', trimmedName);\n\n // Show success message\n toast({\n title: 'Custom Model Added',\n description: `Successfully added \"${trimmedName}\" to your custom models.`,\n variant: 'default',\n });\n\n // Reset form\n setCustomName('');\n setCustomBaseUrl('');\n setCustomApiKey('');\n setBaseUrlError(false);\n closeCustomModelDialog();\n };\n\n const handleEditCustomModel = (modelName: string) => {\n const customModel = customModels.find((cm) => cm.modelName === modelName);\n if (customModel) {\n setEditingModel({\n oldModelName: modelName,\n modelName: modelName,\n baseUrl: customModel.baseUrl,\n apiKey: customModel.apiKey,\n });\n setEditBaseUrlError(false);\n openEditCustomModelDialog();\n }\n };\n\n const handleUpdateCustomModel = () => {\n if (!editingModel) return;\n\n // Reset error states\n setEditBaseUrlError(false);\n\n // Validate required fields\n if (!editingModel.modelName.trim()) {\n toast({\n title: 'Model Name Required',\n description: 'Please enter a model name.',\n variant: 'destructive',\n });\n return;\n }\n\n if (!editingModel.baseUrl.trim()) {\n setEditBaseUrlError(true);\n toast({\n title: 'Base URL Required',\n description: 'Please enter a base URL for the model.',\n variant: 'destructive',\n });\n return;\n }\n\n const trimmedName = editingModel.modelName.trim();\n const trimmedBaseUrl = editingModel.baseUrl.trim();\n const trimmedApiKey = editingModel.apiKey.trim();\n\n // Check for duplicate model names (excluding the current model being edited)\n const duplicateModel = customModels.find(\n (cm) =>\n cm.modelName.toLowerCase() === trimmedName.toLowerCase() &&\n cm.modelName !== editingModel.oldModelName,\n );\n\n if (duplicateModel) {\n toast({\n title: 'Model Name Already Exists',\n description: `A custom model with the name \"${trimmedName}\" already exists.`,\n variant: 'destructive',\n });\n return;\n }\n\n // Update the custom model\n updateCustomModel(\n editingModel.oldModelName,\n trimmedBaseUrl,\n trimmedApiKey,\n trimmedName,\n );\n\n // Show success message\n toast({\n title: 'Custom Model Updated',\n description: `Successfully updated \"${trimmedName}\".`,\n variant: 'default',\n });\n\n // Reset form and close dialog\n setEditingModel(null);\n setEditBaseUrlError(false);\n closeEditCustomModelDialog();\n };\n\n const handleOpenAddProviderModelDialog = (providerKey: string) => {\n setSelectedProviderKey(providerKey);\n setNewModelName('');\n openAddProviderModelDialog();\n };\n\n const handleAddModelToProvider = () => {\n if (!selectedProviderKey || !newModelName.trim()) return;\n\n // Check for duplicates\n const provider = aiConfig.providers[selectedProviderKey];\n const modelExists = provider?.models.some(\n (model) =>\n model.modelName.toLowerCase() === newModelName.trim().toLowerCase(),\n );\n\n if (modelExists) {\n toast({\n title: 'Model Already Exists',\n description: `The model \"${newModelName.trim()}\" already exists in this provider.`,\n variant: 'destructive',\n });\n return;\n }\n\n addModelToProvider(selectedProviderKey, newModelName.trim());\n setNewModelName('');\n closeAddProviderModelDialog();\n };\n\n const handleDeleteModel = (\n type: 'provider' | 'custom',\n providerKey: string | undefined,\n modelName: string,\n ) => {\n setModelToDelete({type, providerKey, modelName});\n openDeleteModelDialog();\n };\n\n const confirmDeleteModel = () => {\n if (!modelToDelete) return;\n\n if (modelToDelete.type === 'provider' && modelToDelete.providerKey) {\n removeModelFromProvider(\n modelToDelete.providerKey,\n modelToDelete.modelName,\n );\n toast({\n title: 'Model Deleted',\n description: `Successfully removed \"${modelToDelete.modelName}\" from the provider.`,\n variant: 'default',\n });\n } else if (modelToDelete.type === 'custom') {\n removeCustomModel(modelToDelete.modelName);\n toast({\n title: 'Custom Model Deleted',\n description: `Successfully removed \"${modelToDelete.modelName}\" from custom models.`,\n variant: 'default',\n });\n }\n\n setModelToDelete(null);\n closeDeleteModelDialog();\n };\n\n return (\n <div className={`space-y-2 ${className}`}>\n <label className=\"text-md flex items-center gap-2 pb-4 font-medium\">\n <Blocks className=\"h-4 w-4\" />\n Models\n </label>\n\n {/* Providers and their models */}\n <div className=\"w-full space-y-4\">\n {providers.map(([providerKey, provider]) => (\n <div key={providerKey} className=\"flex w-full items-start gap-4\">\n {/* Provider name column - 30% */}\n <div className=\"flex w-20 items-start justify-start text-sm font-medium\">\n {providerKey.charAt(0).toUpperCase() + providerKey.slice(1)}\n </div>\n\n {/* Models column - 65% (fills the rest) */}\n <div className=\"flex flex-1 flex-col items-start gap-2\">\n {provider.models.map((m) => (\n <div\n key={m.modelName}\n className=\"flex w-full items-center justify-between gap-2 border-b p-1 text-xs\"\n >\n <span className=\"text-foreground/90\">{m.modelName}</span>\n {allowEditProviderModels && (\n <Button\n size=\"xs\"\n variant=\"ghost\"\n onClick={() =>\n handleDeleteModel('provider', providerKey, m.modelName)\n }\n className=\"h-6 w-6 p-0 text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n >\n <Trash2 className=\"h-3 w-3\" />\n </Button>\n )}\n </div>\n ))}\n {allowEditProviderModels && (\n <Button\n size=\"xs\"\n variant=\"secondary\"\n onClick={() => handleOpenAddProviderModelDialog(providerKey)}\n >\n <CirclePlus className=\"h-3 w-3\" />\n Add\n </Button>\n )}\n </div>\n\n {/* add model button column - 5% */}\n <div className=\"flex w-12 items-start justify-end\"></div>\n </div>\n ))}\n\n {/* Custom models */}\n {allowCustomModels && (\n <div className=\"flex w-full items-start gap-4\">\n <div className=\"flex w-20 items-start justify-start text-sm font-medium\">\n Custom\n </div>\n <div className=\"flex flex-1 flex-col items-start gap-2\">\n {customModels.map((cm) => (\n <div\n key={cm.modelName}\n className=\"flex w-full items-center justify-between gap-2 border-b p-1 text-xs\"\n >\n <span className=\"text-foreground/90\">{cm.modelName}</span>\n <div className=\"flex w-full items-center justify-end\">\n <Button\n size=\"xs\"\n variant=\"ghost\"\n onClick={() =>\n handleDeleteModel('custom', undefined, cm.modelName)\n }\n className=\"h-6 w-6 p-0 text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n >\n <Trash2 className=\"h-3 w-3\" />\n </Button>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n onClick={() => handleEditCustomModel(cm.modelName)}\n >\n <Settings className=\"h-3 w-3\" />\n </Button>\n </div>\n </div>\n ))}\n <Button\n size=\"xs\"\n variant=\"secondary\"\n onClick={() => {\n setCustomName('');\n setCustomBaseUrl('');\n setCustomApiKey('');\n setBaseUrlError(false);\n openCustomModelDialog();\n }}\n >\n <CirclePlus className=\"h-3 w-3\" />\n Add\n </Button>\n </div>\n <div className=\"flex w-12 items-start justify-end\"></div>\n </div>\n )}\n </div>\n\n {/* Add Custom Model Dialog */}\n {allowCustomModels && (\n <Dialog\n open={isCustomModelDialogOpen}\n onOpenChange={(open) => {\n if (open) {\n openCustomModelDialog();\n } else {\n setBaseUrlError(false);\n closeCustomModelDialog();\n }\n }}\n >\n <DialogTrigger asChild>\n {/* handled via onOpen button above */}\n </DialogTrigger>\n <DialogContent className=\"border-0 p-5\">\n <DialogHeader className=\"mb-1\">\n <DialogTitle className=\"flex items-center gap-2 text-base\">\n <Blocks className=\"h-4 w-4\" /> Add Custom Model\n </DialogTitle>\n <DialogDescription className=\"text-xs\">\n Provide connection details for your model provider.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"custom-name\" className=\"w-20 text-sm\">\n Name\n </Label>\n <div className=\"relative flex-1\">\n <Cpu className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"custom-name\"\n value={customName}\n onChange={(e) => setCustomName(e.target.value)}\n placeholder=\"e.g., My cool model\"\n className=\"pl-8\"\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"custom-baseUrl\" className=\"w-20 text-sm\">\n baseUrl\n </Label>\n <div className=\"relative flex-1\">\n <Server className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"custom-baseUrl\"\n value={customBaseUrl}\n onChange={(e) => {\n setCustomBaseUrl(e.target.value);\n if (baseUrlError && e.target.value.trim()) {\n setBaseUrlError(false);\n }\n }}\n placeholder=\"https://api.example.com\"\n className={`pl-8 ${baseUrlError ? 'border-red-500 focus:border-red-500' : ''}`}\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"custom-apiKey\" className=\"w-20 text-sm\">\n API key\n </Label>\n <div className=\"relative flex-1\">\n <Key className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"custom-apiKey\"\n type=\"password\"\n value={customApiKey}\n onChange={(e) => setCustomApiKey(e.target.value)}\n placeholder=\"Optional\"\n className=\"pl-8\"\n />\n </div>\n </div>\n\n <div className=\"flex justify-end pt-1\">\n <Button size=\"sm\" onClick={handleAddCustomToSession}>\n <Plus className=\"mr-2 h-4 w-4\" /> Add\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )}\n\n {/* Add Model to Provider Dialog */}\n <Dialog\n open={isAddProviderModelDialogOpen}\n onOpenChange={(open) =>\n open ? openAddProviderModelDialog() : closeAddProviderModelDialog()\n }\n >\n <DialogTrigger asChild>\n {/* handled via onOpen button above */}\n </DialogTrigger>\n <DialogContent className=\"border-0 p-5\">\n <DialogHeader className=\"mb-1\">\n <DialogTitle className=\"flex items-center gap-2 text-base\">\n <Blocks className=\"h-4 w-4\" /> Add Model to Provider\n </DialogTitle>\n <DialogDescription className=\"text-xs\">\n Add a new model to{' '}\n {selectedProviderKey &&\n selectedProviderKey.charAt(0).toUpperCase() +\n selectedProviderKey.slice(1)}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"new-model-name\" className=\"w-24 text-sm\">\n Model Name\n </Label>\n <div className=\"relative flex-1\">\n <Cpu className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"new-model-name\"\n value={newModelName}\n onChange={(e) => setNewModelName(e.target.value)}\n placeholder=\"e.g., gpt-4-turbo\"\n className=\"pl-8\"\n />\n </div>\n </div>\n\n <div className=\"flex justify-end pt-1\">\n <Button size=\"sm\" onClick={handleAddModelToProvider}>\n <Plus className=\"mr-2 h-4 w-4\" /> Add Model\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n\n {/* Edit Custom Model Dialog */}\n {allowCustomModels && (\n <Dialog\n open={isEditCustomModelDialogOpen}\n onOpenChange={(open) => {\n if (open) {\n openEditCustomModelDialog();\n } else {\n setEditBaseUrlError(false);\n closeEditCustomModelDialog();\n }\n }}\n >\n <DialogTrigger asChild>\n {/* handled via onOpen button above */}\n </DialogTrigger>\n <DialogContent className=\"border-0 p-5\">\n <DialogHeader className=\"mb-1\">\n <DialogTitle className=\"flex items-center gap-2 text-base\">\n <Settings className=\"h-4 w-4\" /> Edit Custom Model\n </DialogTitle>\n <DialogDescription className=\"text-xs\">\n Update the connection details for your custom model.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"edit-model-name\" className=\"w-20 text-sm\">\n Name\n </Label>\n <div className=\"relative flex-1\">\n <Cpu className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"edit-model-name\"\n value={editingModel?.modelName || ''}\n onChange={(e) =>\n setEditingModel((prev) =>\n prev ? {...prev, modelName: e.target.value} : null,\n )\n }\n placeholder=\"e.g., My cool model\"\n className=\"pl-8\"\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"edit-model-baseUrl\" className=\"w-20 text-sm\">\n baseUrl\n </Label>\n <div className=\"relative flex-1\">\n <Server className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"edit-model-baseUrl\"\n value={editingModel?.baseUrl || ''}\n onChange={(e) => {\n setEditingModel((prev) =>\n prev ? {...prev, baseUrl: e.target.value} : null,\n );\n if (editBaseUrlError && e.target.value.trim()) {\n setEditBaseUrlError(false);\n }\n }}\n placeholder=\"https://api.example.com\"\n className={`pl-8 ${editBaseUrlError ? 'border-red-500 focus:border-red-500' : ''}`}\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"edit-model-apiKey\" className=\"w-20 text-sm\">\n API key\n </Label>\n <div className=\"relative flex-1\">\n <Key className=\"absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2\" />\n <Input\n id=\"edit-model-apiKey\"\n type=\"password\"\n value={editingModel?.apiKey || ''}\n onChange={(e) =>\n setEditingModel((prev) =>\n prev ? {...prev, apiKey: e.target.value} : null,\n )\n }\n placeholder=\"Optional\"\n className=\"pl-8\"\n />\n </div>\n </div>\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setEditingModel(null);\n setEditBaseUrlError(false);\n closeEditCustomModelDialog();\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={handleUpdateCustomModel}>\n <Settings className=\"mr-2 h-4 w-4\" /> Update\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )}\n\n {/* Delete Model Confirmation Dialog */}\n {(allowEditProviderModels || allowCustomModels) && (\n <Dialog\n open={isDeleteModelDialogOpen}\n onOpenChange={(open) =>\n open ? openDeleteModelDialog() : closeDeleteModelDialog()\n }\n >\n <DialogTrigger asChild>\n {/* handled via onOpen button above */}\n </DialogTrigger>\n <DialogContent className=\"border-0 p-5\">\n <DialogHeader className=\"mb-1\">\n <DialogTitle className=\"flex items-center gap-2 text-base\">\n <Trash2 className=\"h-4 w-4 text-red-500\" /> Delete Model\n </DialogTitle>\n <DialogDescription className=\"text-xs\">\n Are you sure you want to delete &quot;{modelToDelete?.modelName}\n &quot;? This action cannot be undone.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setModelToDelete(null);\n closeDeleteModelDialog();\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={confirmDeleteModel}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )}\n </div>\n );\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import { FC } from 'react';
2
+ export declare const AiProvidersSettings: FC;
3
+ //# sourceMappingURL=AiProvidersSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiProvidersSettings.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AiProvidersSettings.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAC,EAAE,EAAW,MAAM,OAAO,CAAC;AA0B1C,eAAO,MAAM,mBAAmB,EAAE,EA8TjC,CAAC"}