Spaces:
Running
Running
Ashhar
commited on
Commit
·
c2783a5
1
Parent(s):
c0a9bce
sync Dockerfile as well
Browse files- .gitattributes +0 -35
- Dockerfile +48 -50
- README.md +2 -3
- app/components/chat/ApiKeyWarning.tsx +0 -23
- app/components/settings/Settings.module.scss +0 -63
- app/components/settings/SettingsWindow.tsx +0 -128
- app/components/settings/connections/ConnectionsTab.tsx +0 -151
- app/components/settings/data/DataTab.tsx +0 -388
- app/components/settings/debug/DebugTab.tsx +0 -639
- app/components/settings/event-logs/EventLogsTab.tsx +0 -219
- app/components/settings/features/FeaturesTab.tsx +0 -107
- app/components/settings/providers/ProvidersTab.tsx +0 -147
- app/lib/hooks/useSettings.tsx +0 -229
- docker-compose.yaml +56 -35
.gitattributes
DELETED
@@ -1,35 +0,0 @@
|
|
1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
CHANGED
@@ -1,15 +1,15 @@
|
|
1 |
ARG BASE=node:20.18.0
|
2 |
FROM ${BASE} AS base
|
3 |
|
4 |
-
# Ensure root user
|
5 |
-
USER root
|
6 |
-
|
7 |
WORKDIR /app
|
8 |
|
9 |
# Install dependencies (this step is cached as long as the dependencies don't change)
|
10 |
COPY package.json pnpm-lock.yaml ./
|
11 |
|
12 |
-
RUN
|
|
|
|
|
|
|
13 |
|
14 |
# Copy the rest of your app's source code
|
15 |
COPY . .
|
@@ -17,54 +17,51 @@ COPY . .
|
|
17 |
# Expose the port the app runs on
|
18 |
EXPOSE 5173
|
19 |
|
20 |
-
#
|
21 |
-
|
22 |
-
|
23 |
-
#
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
#
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
|
62 |
# Development image
|
63 |
FROM base AS bolt-ai-development
|
64 |
|
65 |
-
# Ensure root user
|
66 |
-
USER root
|
67 |
-
|
68 |
# Define the same environment variables for development
|
69 |
ARG GROQ_API_KEY
|
70 |
ARG HuggingFace
|
@@ -89,9 +86,10 @@ ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
|
89 |
XAI_API_KEY=${XAI_API_KEY} \
|
90 |
TOGETHER_API_KEY=${TOGETHER_API_KEY} \
|
91 |
TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL} \
|
|
|
92 |
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
|
93 |
-
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX}
|
|
|
94 |
|
95 |
RUN mkdir -p ${WORKDIR}/run
|
96 |
-
RUN chmod -R 777 /app
|
97 |
CMD pnpm run dev --host
|
|
|
1 |
ARG BASE=node:20.18.0
|
2 |
FROM ${BASE} AS base
|
3 |
|
|
|
|
|
|
|
4 |
WORKDIR /app
|
5 |
|
6 |
# Install dependencies (this step is cached as long as the dependencies don't change)
|
7 |
COPY package.json pnpm-lock.yaml ./
|
8 |
|
9 |
+
#RUN npm install -g corepack@latest
|
10 |
+
|
11 |
+
#RUN corepack enable pnpm && pnpm install
|
12 |
+
RUN npm install -g pnpm && pnpm install
|
13 |
|
14 |
# Copy the rest of your app's source code
|
15 |
COPY . .
|
|
|
17 |
# Expose the port the app runs on
|
18 |
EXPOSE 5173
|
19 |
|
20 |
+
# Production image
|
21 |
+
FROM base AS bolt-ai-production
|
22 |
+
|
23 |
+
# Define environment variables with default values or let them be overridden
|
24 |
+
ARG GROQ_API_KEY
|
25 |
+
ARG HuggingFace_API_KEY
|
26 |
+
ARG OPENAI_API_KEY
|
27 |
+
ARG ANTHROPIC_API_KEY
|
28 |
+
ARG OPEN_ROUTER_API_KEY
|
29 |
+
ARG GOOGLE_GENERATIVE_AI_API_KEY
|
30 |
+
ARG OLLAMA_API_BASE_URL
|
31 |
+
ARG XAI_API_KEY
|
32 |
+
ARG TOGETHER_API_KEY
|
33 |
+
ARG TOGETHER_API_BASE_URL
|
34 |
+
ARG AWS_BEDROCK_CONFIG
|
35 |
+
ARG VITE_LOG_LEVEL=debug
|
36 |
+
ARG DEFAULT_NUM_CTX
|
37 |
+
|
38 |
+
ENV WRANGLER_SEND_METRICS=false \
|
39 |
+
GROQ_API_KEY=${GROQ_API_KEY} \
|
40 |
+
HuggingFace_KEY=${HuggingFace_API_KEY} \
|
41 |
+
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
42 |
+
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
43 |
+
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
44 |
+
GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY} \
|
45 |
+
OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL} \
|
46 |
+
XAI_API_KEY=${XAI_API_KEY} \
|
47 |
+
TOGETHER_API_KEY=${TOGETHER_API_KEY} \
|
48 |
+
TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL} \
|
49 |
+
AWS_BEDROCK_CONFIG=${AWS_BEDROCK_CONFIG} \
|
50 |
+
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
|
51 |
+
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX}\
|
52 |
+
RUNNING_IN_DOCKER=true
|
53 |
+
|
54 |
+
# Pre-configure wrangler to disable metrics
|
55 |
+
RUN mkdir -p /root/.config/.wrangler && \
|
56 |
+
echo '{"enabled":false}' > /root/.config/.wrangler/metrics.json
|
57 |
+
|
58 |
+
RUN pnpm run build
|
59 |
+
|
60 |
+
CMD [ "pnpm", "run", "dockerstart"]
|
61 |
|
62 |
# Development image
|
63 |
FROM base AS bolt-ai-development
|
64 |
|
|
|
|
|
|
|
65 |
# Define the same environment variables for development
|
66 |
ARG GROQ_API_KEY
|
67 |
ARG HuggingFace
|
|
|
86 |
XAI_API_KEY=${XAI_API_KEY} \
|
87 |
TOGETHER_API_KEY=${TOGETHER_API_KEY} \
|
88 |
TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL} \
|
89 |
+
AWS_BEDROCK_CONFIG=${AWS_BEDROCK_CONFIG} \
|
90 |
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
|
91 |
+
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX}\
|
92 |
+
RUNNING_IN_DOCKER=true
|
93 |
|
94 |
RUN mkdir -p ${WORKDIR}/run
|
|
|
95 |
CMD pnpm run dev --host
|
README.md
CHANGED
@@ -89,6 +89,7 @@ project, please check the [project management guide](./PROJECT.md) to get starte
|
|
89 |
- ✅ Add Starter Template Options (@thecodacus)
|
90 |
- ✅ Perplexity Integration (@meetpateltech)
|
91 |
- ✅ AWS Bedrock Integration (@kunjabijukchhe)
|
|
|
92 |
- ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)
|
93 |
- ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
|
94 |
- ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call
|
@@ -174,9 +175,7 @@ You have two options for running Bolt.DIY: directly on your machine or using Doc
|
|
174 |
```bash
|
175 |
pnpm run dev
|
176 |
```
|
177 |
-
|
178 |
-
**Important Note**: If you're using Google Chrome, you'll need Chrome Canary for local development. [Download it here](https://www.google.com/chrome/canary/)
|
179 |
-
|
180 |
### Option 2: Using Docker
|
181 |
|
182 |
This option requires some familiarity with Docker but provides a more isolated environment.
|
|
|
89 |
- ✅ Add Starter Template Options (@thecodacus)
|
90 |
- ✅ Perplexity Integration (@meetpateltech)
|
91 |
- ✅ AWS Bedrock Integration (@kunjabijukchhe)
|
92 |
+
- ✅ Add a "Diff View" to see the changes (@toddyclipsgg)
|
93 |
- ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)
|
94 |
- ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
|
95 |
- ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call
|
|
|
175 |
```bash
|
176 |
pnpm run dev
|
177 |
```
|
178 |
+
|
|
|
|
|
179 |
### Option 2: Using Docker
|
180 |
|
181 |
This option requires some familiarity with Docker but provides a more isolated environment.
|
app/components/chat/ApiKeyWarning.tsx
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
export const ApiKeyWarning: React.FC<ApiKeyWarningProps> = ({ provider, apiKeys }) => {
|
2 |
-
// Add a null check for provider before accessing its name
|
3 |
-
const isApiKeyMissing = !provider || !apiKeys[provider.name];
|
4 |
-
|
5 |
-
if (!isApiKeyMissing) return null;
|
6 |
-
|
7 |
-
return (
|
8 |
-
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4">
|
9 |
-
<div className="flex">
|
10 |
-
<div className="flex-shrink-0">
|
11 |
-
<div className="i-ph:warning-circle text-yellow-400 text-2xl" />
|
12 |
-
</div>
|
13 |
-
<div className="ml-3">
|
14 |
-
<p className="text-sm text-yellow-700">
|
15 |
-
{provider
|
16 |
-
? `API key is missing for ${provider.name}. Please add an API key in the settings to send messages.`
|
17 |
-
: 'No provider selected. Please select a provider and add an API key.'}
|
18 |
-
</p>
|
19 |
-
</div>
|
20 |
-
</div>
|
21 |
-
</div>
|
22 |
-
);
|
23 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/Settings.module.scss
DELETED
@@ -1,63 +0,0 @@
|
|
1 |
-
.settings-tabs {
|
2 |
-
button {
|
3 |
-
width: 100%;
|
4 |
-
display: flex;
|
5 |
-
align-items: center;
|
6 |
-
gap: 0.5rem;
|
7 |
-
padding: 0.75rem 1rem;
|
8 |
-
border-radius: 0.5rem;
|
9 |
-
text-align: left;
|
10 |
-
font-size: 0.875rem;
|
11 |
-
transition: all 0.2s;
|
12 |
-
margin-bottom: 0.5rem;
|
13 |
-
|
14 |
-
&.active {
|
15 |
-
background: var(--bolt-elements-button-primary-background);
|
16 |
-
color: var(--bolt-elements-textPrimary);
|
17 |
-
}
|
18 |
-
|
19 |
-
&:not(.active) {
|
20 |
-
background: var(--bolt-elements-bg-depth-3);
|
21 |
-
color: var(--bolt-elements-textPrimary);
|
22 |
-
|
23 |
-
&:hover {
|
24 |
-
background: var(--bolt-elements-button-primary-backgroundHover);
|
25 |
-
}
|
26 |
-
}
|
27 |
-
}
|
28 |
-
}
|
29 |
-
|
30 |
-
.settings-button {
|
31 |
-
background-color: var(--bolt-elements-button-primary-background);
|
32 |
-
color: var(--bolt-elements-textPrimary);
|
33 |
-
border-radius: 0.5rem;
|
34 |
-
padding: 0.5rem 1rem;
|
35 |
-
transition: background-color 0.2s;
|
36 |
-
|
37 |
-
&:hover {
|
38 |
-
background-color: var(--bolt-elements-button-primary-backgroundHover);
|
39 |
-
}
|
40 |
-
}
|
41 |
-
|
42 |
-
.settings-danger-area {
|
43 |
-
background-color: transparent;
|
44 |
-
color: var(--bolt-elements-textPrimary);
|
45 |
-
border-radius: 0.5rem;
|
46 |
-
padding: 1rem;
|
47 |
-
margin-bottom: 1rem;
|
48 |
-
border-style: solid;
|
49 |
-
border-color: var(--bolt-elements-button-danger-backgroundHover);
|
50 |
-
border-width: thin;
|
51 |
-
|
52 |
-
button {
|
53 |
-
background-color: var(--bolt-elements-button-danger-background);
|
54 |
-
color: var(--bolt-elements-button-danger-text);
|
55 |
-
border-radius: 0.5rem;
|
56 |
-
padding: 0.5rem 1rem;
|
57 |
-
transition: background-color 0.2s;
|
58 |
-
|
59 |
-
&:hover {
|
60 |
-
background-color: var(--bolt-elements-button-danger-backgroundHover);
|
61 |
-
}
|
62 |
-
}
|
63 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/SettingsWindow.tsx
DELETED
@@ -1,128 +0,0 @@
|
|
1 |
-
import * as RadixDialog from '@radix-ui/react-dialog';
|
2 |
-
import { motion } from 'framer-motion';
|
3 |
-
import { useState, type ReactElement } from 'react';
|
4 |
-
import { classNames } from '~/utils/classNames';
|
5 |
-
import { DialogTitle, dialogVariants, dialogBackdropVariants } from '~/components/ui/Dialog';
|
6 |
-
import { IconButton } from '~/components/ui/IconButton';
|
7 |
-
import styles from './Settings.module.scss';
|
8 |
-
import ProvidersTab from './providers/ProvidersTab';
|
9 |
-
import { useSettings } from '~/lib/hooks/useSettings';
|
10 |
-
import FeaturesTab from './features/FeaturesTab';
|
11 |
-
import DebugTab from './debug/DebugTab';
|
12 |
-
import EventLogsTab from './event-logs/EventLogsTab';
|
13 |
-
import ConnectionsTab from './connections/ConnectionsTab';
|
14 |
-
import DataTab from './data/DataTab';
|
15 |
-
|
16 |
-
interface SettingsProps {
|
17 |
-
open: boolean;
|
18 |
-
onClose: () => void;
|
19 |
-
}
|
20 |
-
|
21 |
-
type TabType = 'data' | 'providers' | 'features' | 'debug' | 'event-logs' | 'connection';
|
22 |
-
|
23 |
-
export const SettingsWindow = ({ open, onClose }: SettingsProps) => {
|
24 |
-
const { debug, eventLogs } = useSettings();
|
25 |
-
const [activeTab, setActiveTab] = useState<TabType>('data');
|
26 |
-
|
27 |
-
const tabs: { id: TabType; label: string; icon: string; component?: ReactElement }[] = [
|
28 |
-
{ id: 'data', label: 'Data', icon: 'i-ph:database', component: <DataTab /> },
|
29 |
-
{ id: 'providers', label: 'Providers', icon: 'i-ph:key', component: <ProvidersTab /> },
|
30 |
-
{ id: 'connection', label: 'Connection', icon: 'i-ph:link', component: <ConnectionsTab /> },
|
31 |
-
{ id: 'features', label: 'Features', icon: 'i-ph:star', component: <FeaturesTab /> },
|
32 |
-
...(debug
|
33 |
-
? [
|
34 |
-
{
|
35 |
-
id: 'debug' as TabType,
|
36 |
-
label: 'Debug Tab',
|
37 |
-
icon: 'i-ph:bug',
|
38 |
-
component: <DebugTab />,
|
39 |
-
},
|
40 |
-
]
|
41 |
-
: []),
|
42 |
-
...(eventLogs
|
43 |
-
? [
|
44 |
-
{
|
45 |
-
id: 'event-logs' as TabType,
|
46 |
-
label: 'Event Logs',
|
47 |
-
icon: 'i-ph:list-bullets',
|
48 |
-
component: <EventLogsTab />,
|
49 |
-
},
|
50 |
-
]
|
51 |
-
: []),
|
52 |
-
];
|
53 |
-
|
54 |
-
return (
|
55 |
-
<RadixDialog.Root open={open}>
|
56 |
-
<RadixDialog.Portal>
|
57 |
-
<RadixDialog.Overlay asChild onClick={onClose}>
|
58 |
-
<motion.div
|
59 |
-
className="bg-black/50 fixed inset-0 z-max backdrop-blur-sm"
|
60 |
-
initial="closed"
|
61 |
-
animate="open"
|
62 |
-
exit="closed"
|
63 |
-
variants={dialogBackdropVariants}
|
64 |
-
/>
|
65 |
-
</RadixDialog.Overlay>
|
66 |
-
<RadixDialog.Content aria-describedby={undefined} asChild>
|
67 |
-
<motion.div
|
68 |
-
className="fixed top-[50%] left-[50%] z-max h-[85vh] w-[90vw] max-w-[900px] translate-x-[-50%] translate-y-[-50%] border border-bolt-elements-borderColor rounded-lg shadow-lg focus:outline-none overflow-hidden"
|
69 |
-
initial="closed"
|
70 |
-
animate="open"
|
71 |
-
exit="closed"
|
72 |
-
variants={dialogVariants}
|
73 |
-
>
|
74 |
-
<div className="flex h-full">
|
75 |
-
<div
|
76 |
-
className={classNames(
|
77 |
-
'w-48 border-r border-bolt-elements-borderColor bg-bolt-elements-background-depth-1 p-4 flex flex-col justify-between',
|
78 |
-
styles['settings-tabs'],
|
79 |
-
)}
|
80 |
-
>
|
81 |
-
<DialogTitle className="flex-shrink-0 text-lg font-semibold text-bolt-elements-textPrimary mb-2">
|
82 |
-
Settings
|
83 |
-
</DialogTitle>
|
84 |
-
{tabs.map((tab) => (
|
85 |
-
<button
|
86 |
-
key={tab.id}
|
87 |
-
onClick={() => setActiveTab(tab.id)}
|
88 |
-
className={classNames(activeTab === tab.id ? styles.active : '')}
|
89 |
-
>
|
90 |
-
<div className={tab.icon} />
|
91 |
-
{tab.label}
|
92 |
-
</button>
|
93 |
-
))}
|
94 |
-
<div className="mt-auto flex flex-col gap-2">
|
95 |
-
<a
|
96 |
-
href="https://github.com/stackblitz-labs/bolt.diy"
|
97 |
-
target="_blank"
|
98 |
-
rel="noopener noreferrer"
|
99 |
-
className={classNames(styles['settings-button'], 'flex items-center gap-2')}
|
100 |
-
>
|
101 |
-
<div className="i-ph:github-logo" />
|
102 |
-
GitHub
|
103 |
-
</a>
|
104 |
-
<a
|
105 |
-
href="https://stackblitz-labs.github.io/bolt.diy/"
|
106 |
-
target="_blank"
|
107 |
-
rel="noopener noreferrer"
|
108 |
-
className={classNames(styles['settings-button'], 'flex items-center gap-2')}
|
109 |
-
>
|
110 |
-
<div className="i-ph:book" />
|
111 |
-
Docs
|
112 |
-
</a>
|
113 |
-
</div>
|
114 |
-
</div>
|
115 |
-
|
116 |
-
<div className="flex-1 flex flex-col p-8 pt-10 bg-bolt-elements-background-depth-2">
|
117 |
-
<div className="flex-1 overflow-y-auto">{tabs.find((tab) => tab.id === activeTab)?.component}</div>
|
118 |
-
</div>
|
119 |
-
</div>
|
120 |
-
<RadixDialog.Close asChild onClick={onClose}>
|
121 |
-
<IconButton icon="i-ph:x" className="absolute top-[10px] right-[10px]" />
|
122 |
-
</RadixDialog.Close>
|
123 |
-
</motion.div>
|
124 |
-
</RadixDialog.Content>
|
125 |
-
</RadixDialog.Portal>
|
126 |
-
</RadixDialog.Root>
|
127 |
-
);
|
128 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/connections/ConnectionsTab.tsx
DELETED
@@ -1,151 +0,0 @@
|
|
1 |
-
import React, { useState, useEffect } from 'react';
|
2 |
-
import { toast } from 'react-toastify';
|
3 |
-
import Cookies from 'js-cookie';
|
4 |
-
import { logStore } from '~/lib/stores/logs';
|
5 |
-
|
6 |
-
interface GitHubUserResponse {
|
7 |
-
login: string;
|
8 |
-
id: number;
|
9 |
-
[key: string]: any; // for other properties we don't explicitly need
|
10 |
-
}
|
11 |
-
|
12 |
-
export default function ConnectionsTab() {
|
13 |
-
const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
|
14 |
-
const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
|
15 |
-
const [isConnected, setIsConnected] = useState(false);
|
16 |
-
const [isVerifying, setIsVerifying] = useState(false);
|
17 |
-
|
18 |
-
useEffect(() => {
|
19 |
-
// Check if credentials exist and verify them
|
20 |
-
if (githubUsername && githubToken) {
|
21 |
-
verifyGitHubCredentials();
|
22 |
-
}
|
23 |
-
}, []);
|
24 |
-
|
25 |
-
const verifyGitHubCredentials = async () => {
|
26 |
-
setIsVerifying(true);
|
27 |
-
|
28 |
-
try {
|
29 |
-
const response = await fetch('https://api.github.com/user', {
|
30 |
-
headers: {
|
31 |
-
Authorization: `Bearer ${githubToken}`,
|
32 |
-
},
|
33 |
-
});
|
34 |
-
|
35 |
-
if (response.ok) {
|
36 |
-
const data = (await response.json()) as GitHubUserResponse;
|
37 |
-
|
38 |
-
if (data.login === githubUsername) {
|
39 |
-
setIsConnected(true);
|
40 |
-
return true;
|
41 |
-
}
|
42 |
-
}
|
43 |
-
|
44 |
-
setIsConnected(false);
|
45 |
-
|
46 |
-
return false;
|
47 |
-
} catch (error) {
|
48 |
-
console.error('Error verifying GitHub credentials:', error);
|
49 |
-
setIsConnected(false);
|
50 |
-
|
51 |
-
return false;
|
52 |
-
} finally {
|
53 |
-
setIsVerifying(false);
|
54 |
-
}
|
55 |
-
};
|
56 |
-
|
57 |
-
const handleSaveConnection = async () => {
|
58 |
-
if (!githubUsername || !githubToken) {
|
59 |
-
toast.error('Please provide both GitHub username and token');
|
60 |
-
return;
|
61 |
-
}
|
62 |
-
|
63 |
-
setIsVerifying(true);
|
64 |
-
|
65 |
-
const isValid = await verifyGitHubCredentials();
|
66 |
-
|
67 |
-
if (isValid) {
|
68 |
-
Cookies.set('githubUsername', githubUsername);
|
69 |
-
Cookies.set('githubToken', githubToken);
|
70 |
-
logStore.logSystem('GitHub connection settings updated', {
|
71 |
-
username: githubUsername,
|
72 |
-
hasToken: !!githubToken,
|
73 |
-
});
|
74 |
-
toast.success('GitHub credentials verified and saved successfully!');
|
75 |
-
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
|
76 |
-
setIsConnected(true);
|
77 |
-
} else {
|
78 |
-
toast.error('Invalid GitHub credentials. Please check your username and token.');
|
79 |
-
}
|
80 |
-
};
|
81 |
-
|
82 |
-
const handleDisconnect = () => {
|
83 |
-
Cookies.remove('githubUsername');
|
84 |
-
Cookies.remove('githubToken');
|
85 |
-
Cookies.remove('git:github.com');
|
86 |
-
setGithubUsername('');
|
87 |
-
setGithubToken('');
|
88 |
-
setIsConnected(false);
|
89 |
-
logStore.logSystem('GitHub connection removed');
|
90 |
-
toast.success('GitHub connection removed successfully!');
|
91 |
-
};
|
92 |
-
|
93 |
-
return (
|
94 |
-
<div className="p-4 mb-4 border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-3">
|
95 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">GitHub Connection</h3>
|
96 |
-
<div className="flex mb-4">
|
97 |
-
<div className="flex-1 mr-2">
|
98 |
-
<label className="block text-sm text-bolt-elements-textSecondary mb-1">GitHub Username:</label>
|
99 |
-
<input
|
100 |
-
type="text"
|
101 |
-
value={githubUsername}
|
102 |
-
onChange={(e) => setGithubUsername(e.target.value)}
|
103 |
-
disabled={isVerifying}
|
104 |
-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
|
105 |
-
/>
|
106 |
-
</div>
|
107 |
-
<div className="flex-1">
|
108 |
-
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Personal Access Token:</label>
|
109 |
-
<input
|
110 |
-
type="password"
|
111 |
-
value={githubToken}
|
112 |
-
onChange={(e) => setGithubToken(e.target.value)}
|
113 |
-
disabled={isVerifying}
|
114 |
-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
|
115 |
-
/>
|
116 |
-
</div>
|
117 |
-
</div>
|
118 |
-
<div className="flex mb-4 items-center">
|
119 |
-
{!isConnected ? (
|
120 |
-
<button
|
121 |
-
onClick={handleSaveConnection}
|
122 |
-
disabled={isVerifying || !githubUsername || !githubToken}
|
123 |
-
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
|
124 |
-
>
|
125 |
-
{isVerifying ? (
|
126 |
-
<>
|
127 |
-
<div className="i-ph:spinner animate-spin mr-2" />
|
128 |
-
Verifying...
|
129 |
-
</>
|
130 |
-
) : (
|
131 |
-
'Connect'
|
132 |
-
)}
|
133 |
-
</button>
|
134 |
-
) : (
|
135 |
-
<button
|
136 |
-
onClick={handleDisconnect}
|
137 |
-
className="bg-bolt-elements-button-danger-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text"
|
138 |
-
>
|
139 |
-
Disconnect
|
140 |
-
</button>
|
141 |
-
)}
|
142 |
-
{isConnected && (
|
143 |
-
<span className="text-sm text-green-600 flex items-center">
|
144 |
-
<div className="i-ph:check-circle mr-1" />
|
145 |
-
Connected to GitHub
|
146 |
-
</span>
|
147 |
-
)}
|
148 |
-
</div>
|
149 |
-
</div>
|
150 |
-
);
|
151 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/data/DataTab.tsx
DELETED
@@ -1,388 +0,0 @@
|
|
1 |
-
import React, { useState } from 'react';
|
2 |
-
import { useNavigate } from '@remix-run/react';
|
3 |
-
import Cookies from 'js-cookie';
|
4 |
-
import { toast } from 'react-toastify';
|
5 |
-
import { db, deleteById, getAll, setMessages } from '~/lib/persistence';
|
6 |
-
import { logStore } from '~/lib/stores/logs';
|
7 |
-
import { classNames } from '~/utils/classNames';
|
8 |
-
import type { Message } from 'ai';
|
9 |
-
|
10 |
-
// List of supported providers that can have API keys
|
11 |
-
const API_KEY_PROVIDERS = [
|
12 |
-
'Anthropic',
|
13 |
-
'OpenAI',
|
14 |
-
'Google',
|
15 |
-
'Groq',
|
16 |
-
'HuggingFace',
|
17 |
-
'OpenRouter',
|
18 |
-
'Deepseek',
|
19 |
-
'Mistral',
|
20 |
-
'OpenAILike',
|
21 |
-
'Together',
|
22 |
-
'xAI',
|
23 |
-
'Perplexity',
|
24 |
-
'Cohere',
|
25 |
-
'AzureOpenAI',
|
26 |
-
'AmazonBedrock',
|
27 |
-
] as const;
|
28 |
-
|
29 |
-
interface ApiKeys {
|
30 |
-
[key: string]: string;
|
31 |
-
}
|
32 |
-
|
33 |
-
export default function DataTab() {
|
34 |
-
const navigate = useNavigate();
|
35 |
-
const [isDeleting, setIsDeleting] = useState(false);
|
36 |
-
|
37 |
-
const downloadAsJson = (data: any, filename: string) => {
|
38 |
-
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
39 |
-
const url = URL.createObjectURL(blob);
|
40 |
-
const link = document.createElement('a');
|
41 |
-
link.href = url;
|
42 |
-
link.download = filename;
|
43 |
-
document.body.appendChild(link);
|
44 |
-
link.click();
|
45 |
-
document.body.removeChild(link);
|
46 |
-
URL.revokeObjectURL(url);
|
47 |
-
};
|
48 |
-
|
49 |
-
const handleExportAllChats = async () => {
|
50 |
-
if (!db) {
|
51 |
-
const error = new Error('Database is not available');
|
52 |
-
logStore.logError('Failed to export chats - DB unavailable', error);
|
53 |
-
toast.error('Database is not available');
|
54 |
-
|
55 |
-
return;
|
56 |
-
}
|
57 |
-
|
58 |
-
try {
|
59 |
-
const allChats = await getAll(db);
|
60 |
-
const exportData = {
|
61 |
-
chats: allChats,
|
62 |
-
exportDate: new Date().toISOString(),
|
63 |
-
};
|
64 |
-
|
65 |
-
downloadAsJson(exportData, `all-chats-${new Date().toISOString()}.json`);
|
66 |
-
logStore.logSystem('Chats exported successfully', { count: allChats.length });
|
67 |
-
toast.success('Chats exported successfully');
|
68 |
-
} catch (error) {
|
69 |
-
logStore.logError('Failed to export chats', error);
|
70 |
-
toast.error('Failed to export chats');
|
71 |
-
console.error(error);
|
72 |
-
}
|
73 |
-
};
|
74 |
-
|
75 |
-
const handleDeleteAllChats = async () => {
|
76 |
-
const confirmDelete = window.confirm('Are you sure you want to delete all chats? This action cannot be undone.');
|
77 |
-
|
78 |
-
if (!confirmDelete) {
|
79 |
-
return;
|
80 |
-
}
|
81 |
-
|
82 |
-
if (!db) {
|
83 |
-
const error = new Error('Database is not available');
|
84 |
-
logStore.logError('Failed to delete chats - DB unavailable', error);
|
85 |
-
toast.error('Database is not available');
|
86 |
-
|
87 |
-
return;
|
88 |
-
}
|
89 |
-
|
90 |
-
try {
|
91 |
-
setIsDeleting(true);
|
92 |
-
|
93 |
-
const allChats = await getAll(db);
|
94 |
-
await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
|
95 |
-
logStore.logSystem('All chats deleted successfully', { count: allChats.length });
|
96 |
-
toast.success('All chats deleted successfully');
|
97 |
-
navigate('/', { replace: true });
|
98 |
-
} catch (error) {
|
99 |
-
logStore.logError('Failed to delete chats', error);
|
100 |
-
toast.error('Failed to delete chats');
|
101 |
-
console.error(error);
|
102 |
-
} finally {
|
103 |
-
setIsDeleting(false);
|
104 |
-
}
|
105 |
-
};
|
106 |
-
|
107 |
-
const handleExportSettings = () => {
|
108 |
-
const settings = {
|
109 |
-
providers: Cookies.get('providers'),
|
110 |
-
isDebugEnabled: Cookies.get('isDebugEnabled'),
|
111 |
-
isEventLogsEnabled: Cookies.get('isEventLogsEnabled'),
|
112 |
-
isLocalModelsEnabled: Cookies.get('isLocalModelsEnabled'),
|
113 |
-
promptId: Cookies.get('promptId'),
|
114 |
-
isLatestBranch: Cookies.get('isLatestBranch'),
|
115 |
-
commitHash: Cookies.get('commitHash'),
|
116 |
-
eventLogs: Cookies.get('eventLogs'),
|
117 |
-
selectedModel: Cookies.get('selectedModel'),
|
118 |
-
selectedProvider: Cookies.get('selectedProvider'),
|
119 |
-
githubUsername: Cookies.get('githubUsername'),
|
120 |
-
githubToken: Cookies.get('githubToken'),
|
121 |
-
bolt_theme: localStorage.getItem('bolt_theme'),
|
122 |
-
};
|
123 |
-
|
124 |
-
downloadAsJson(settings, 'bolt-settings.json');
|
125 |
-
toast.success('Settings exported successfully');
|
126 |
-
};
|
127 |
-
|
128 |
-
const handleImportSettings = (event: React.ChangeEvent<HTMLInputElement>) => {
|
129 |
-
const file = event.target.files?.[0];
|
130 |
-
|
131 |
-
if (!file) {
|
132 |
-
return;
|
133 |
-
}
|
134 |
-
|
135 |
-
const reader = new FileReader();
|
136 |
-
|
137 |
-
reader.onload = (e) => {
|
138 |
-
try {
|
139 |
-
const settings = JSON.parse(e.target?.result as string);
|
140 |
-
|
141 |
-
Object.entries(settings).forEach(([key, value]) => {
|
142 |
-
if (key === 'bolt_theme') {
|
143 |
-
if (value) {
|
144 |
-
localStorage.setItem(key, value as string);
|
145 |
-
}
|
146 |
-
} else if (value) {
|
147 |
-
Cookies.set(key, value as string);
|
148 |
-
}
|
149 |
-
});
|
150 |
-
|
151 |
-
toast.success('Settings imported successfully. Please refresh the page for changes to take effect.');
|
152 |
-
} catch (error) {
|
153 |
-
toast.error('Failed to import settings. Make sure the file is a valid JSON file.');
|
154 |
-
console.error('Failed to import settings:', error);
|
155 |
-
}
|
156 |
-
};
|
157 |
-
reader.readAsText(file);
|
158 |
-
event.target.value = '';
|
159 |
-
};
|
160 |
-
|
161 |
-
const handleExportApiKeyTemplate = () => {
|
162 |
-
const template: ApiKeys = {};
|
163 |
-
API_KEY_PROVIDERS.forEach((provider) => {
|
164 |
-
template[`${provider}_API_KEY`] = '';
|
165 |
-
});
|
166 |
-
|
167 |
-
template.OPENAI_LIKE_API_BASE_URL = '';
|
168 |
-
template.LMSTUDIO_API_BASE_URL = '';
|
169 |
-
template.OLLAMA_API_BASE_URL = '';
|
170 |
-
template.TOGETHER_API_BASE_URL = '';
|
171 |
-
|
172 |
-
downloadAsJson(template, 'api-keys-template.json');
|
173 |
-
toast.success('API keys template exported successfully');
|
174 |
-
};
|
175 |
-
|
176 |
-
const handleImportApiKeys = (event: React.ChangeEvent<HTMLInputElement>) => {
|
177 |
-
const file = event.target.files?.[0];
|
178 |
-
|
179 |
-
if (!file) {
|
180 |
-
return;
|
181 |
-
}
|
182 |
-
|
183 |
-
const reader = new FileReader();
|
184 |
-
|
185 |
-
reader.onload = (e) => {
|
186 |
-
try {
|
187 |
-
const apiKeys = JSON.parse(e.target?.result as string);
|
188 |
-
let importedCount = 0;
|
189 |
-
const consolidatedKeys: Record<string, string> = {};
|
190 |
-
|
191 |
-
API_KEY_PROVIDERS.forEach((provider) => {
|
192 |
-
const keyName = `${provider}_API_KEY`;
|
193 |
-
|
194 |
-
if (apiKeys[keyName]) {
|
195 |
-
consolidatedKeys[provider] = apiKeys[keyName];
|
196 |
-
importedCount++;
|
197 |
-
}
|
198 |
-
});
|
199 |
-
|
200 |
-
if (importedCount > 0) {
|
201 |
-
// Store all API keys in a single cookie as JSON
|
202 |
-
Cookies.set('apiKeys', JSON.stringify(consolidatedKeys));
|
203 |
-
|
204 |
-
// Also set individual cookies for backward compatibility
|
205 |
-
Object.entries(consolidatedKeys).forEach(([provider, key]) => {
|
206 |
-
Cookies.set(`${provider}_API_KEY`, key);
|
207 |
-
});
|
208 |
-
|
209 |
-
toast.success(`Successfully imported ${importedCount} API keys/URLs. Refreshing page to apply changes...`);
|
210 |
-
|
211 |
-
// Reload the page after a short delay to allow the toast to be seen
|
212 |
-
setTimeout(() => {
|
213 |
-
window.location.reload();
|
214 |
-
}, 1500);
|
215 |
-
} else {
|
216 |
-
toast.warn('No valid API keys found in the file');
|
217 |
-
}
|
218 |
-
|
219 |
-
// Set base URLs if they exist
|
220 |
-
['OPENAI_LIKE_API_BASE_URL', 'LMSTUDIO_API_BASE_URL', 'OLLAMA_API_BASE_URL', 'TOGETHER_API_BASE_URL'].forEach(
|
221 |
-
(baseUrl) => {
|
222 |
-
if (apiKeys[baseUrl]) {
|
223 |
-
Cookies.set(baseUrl, apiKeys[baseUrl]);
|
224 |
-
}
|
225 |
-
},
|
226 |
-
);
|
227 |
-
} catch (error) {
|
228 |
-
toast.error('Failed to import API keys. Make sure the file is a valid JSON file.');
|
229 |
-
console.error('Failed to import API keys:', error);
|
230 |
-
}
|
231 |
-
};
|
232 |
-
reader.readAsText(file);
|
233 |
-
event.target.value = '';
|
234 |
-
};
|
235 |
-
|
236 |
-
const processChatData = (
|
237 |
-
data: any,
|
238 |
-
): Array<{
|
239 |
-
id: string;
|
240 |
-
messages: Message[];
|
241 |
-
description: string;
|
242 |
-
urlId?: string;
|
243 |
-
}> => {
|
244 |
-
// Handle Bolt standard format (single chat)
|
245 |
-
if (data.messages && Array.isArray(data.messages)) {
|
246 |
-
const chatId = crypto.randomUUID();
|
247 |
-
return [
|
248 |
-
{
|
249 |
-
id: chatId,
|
250 |
-
messages: data.messages,
|
251 |
-
description: data.description || 'Imported Chat',
|
252 |
-
urlId: chatId,
|
253 |
-
},
|
254 |
-
];
|
255 |
-
}
|
256 |
-
|
257 |
-
// Handle Bolt export format (multiple chats)
|
258 |
-
if (data.chats && Array.isArray(data.chats)) {
|
259 |
-
return data.chats.map((chat: { id?: string; messages: Message[]; description?: string; urlId?: string }) => ({
|
260 |
-
id: chat.id || crypto.randomUUID(),
|
261 |
-
messages: chat.messages,
|
262 |
-
description: chat.description || 'Imported Chat',
|
263 |
-
urlId: chat.urlId,
|
264 |
-
}));
|
265 |
-
}
|
266 |
-
|
267 |
-
console.error('No matching format found for:', data);
|
268 |
-
throw new Error('Unsupported chat format');
|
269 |
-
};
|
270 |
-
|
271 |
-
const handleImportChats = () => {
|
272 |
-
const input = document.createElement('input');
|
273 |
-
input.type = 'file';
|
274 |
-
input.accept = '.json';
|
275 |
-
|
276 |
-
input.onchange = async (e) => {
|
277 |
-
const file = (e.target as HTMLInputElement).files?.[0];
|
278 |
-
|
279 |
-
if (!file || !db) {
|
280 |
-
toast.error('Something went wrong');
|
281 |
-
return;
|
282 |
-
}
|
283 |
-
|
284 |
-
try {
|
285 |
-
const content = await file.text();
|
286 |
-
const data = JSON.parse(content);
|
287 |
-
const chatsToImport = processChatData(data);
|
288 |
-
|
289 |
-
for (const chat of chatsToImport) {
|
290 |
-
await setMessages(db, chat.id, chat.messages, chat.urlId, chat.description);
|
291 |
-
}
|
292 |
-
|
293 |
-
logStore.logSystem('Chats imported successfully', { count: chatsToImport.length });
|
294 |
-
toast.success(`Successfully imported ${chatsToImport.length} chat${chatsToImport.length > 1 ? 's' : ''}`);
|
295 |
-
window.location.reload();
|
296 |
-
} catch (error) {
|
297 |
-
if (error instanceof Error) {
|
298 |
-
logStore.logError('Failed to import chats:', error);
|
299 |
-
toast.error('Failed to import chats: ' + error.message);
|
300 |
-
} else {
|
301 |
-
toast.error('Failed to import chats');
|
302 |
-
}
|
303 |
-
|
304 |
-
console.error(error);
|
305 |
-
}
|
306 |
-
};
|
307 |
-
|
308 |
-
input.click();
|
309 |
-
};
|
310 |
-
|
311 |
-
return (
|
312 |
-
<div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
|
313 |
-
<div className="mb-6">
|
314 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Data Management</h3>
|
315 |
-
<div className="space-y-8">
|
316 |
-
<div className="flex flex-col gap-4">
|
317 |
-
<div>
|
318 |
-
<h4 className="text-bolt-elements-textPrimary mb-2">Chat History</h4>
|
319 |
-
<p className="text-sm text-bolt-elements-textSecondary mb-4">Export or delete all your chat history.</p>
|
320 |
-
<div className="flex gap-4">
|
321 |
-
<button
|
322 |
-
onClick={handleExportAllChats}
|
323 |
-
className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors"
|
324 |
-
>
|
325 |
-
Export All Chats
|
326 |
-
</button>
|
327 |
-
<button
|
328 |
-
onClick={handleImportChats}
|
329 |
-
className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors"
|
330 |
-
>
|
331 |
-
Import Chats
|
332 |
-
</button>
|
333 |
-
<button
|
334 |
-
onClick={handleDeleteAllChats}
|
335 |
-
disabled={isDeleting}
|
336 |
-
className={classNames(
|
337 |
-
'px-4 py-2 bg-bolt-elements-button-danger-background hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text rounded-lg transition-colors',
|
338 |
-
isDeleting ? 'opacity-50 cursor-not-allowed' : '',
|
339 |
-
)}
|
340 |
-
>
|
341 |
-
{isDeleting ? 'Deleting...' : 'Delete All Chats'}
|
342 |
-
</button>
|
343 |
-
</div>
|
344 |
-
</div>
|
345 |
-
|
346 |
-
<div>
|
347 |
-
<h4 className="text-bolt-elements-textPrimary mb-2">Settings Backup</h4>
|
348 |
-
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
349 |
-
Export your settings to a JSON file or import settings from a previously exported file.
|
350 |
-
</p>
|
351 |
-
<div className="flex gap-4">
|
352 |
-
<button
|
353 |
-
onClick={handleExportSettings}
|
354 |
-
className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors"
|
355 |
-
>
|
356 |
-
Export Settings
|
357 |
-
</button>
|
358 |
-
<label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
|
359 |
-
Import Settings
|
360 |
-
<input type="file" accept=".json" onChange={handleImportSettings} className="hidden" />
|
361 |
-
</label>
|
362 |
-
</div>
|
363 |
-
</div>
|
364 |
-
|
365 |
-
<div>
|
366 |
-
<h4 className="text-bolt-elements-textPrimary mb-2">API Keys Management</h4>
|
367 |
-
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
368 |
-
Import API keys from a JSON file or download a template to fill in your keys.
|
369 |
-
</p>
|
370 |
-
<div className="flex gap-4">
|
371 |
-
<button
|
372 |
-
onClick={handleExportApiKeyTemplate}
|
373 |
-
className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors"
|
374 |
-
>
|
375 |
-
Download Template
|
376 |
-
</button>
|
377 |
-
<label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
|
378 |
-
Import API Keys
|
379 |
-
<input type="file" accept=".json" onChange={handleImportApiKeys} className="hidden" />
|
380 |
-
</label>
|
381 |
-
</div>
|
382 |
-
</div>
|
383 |
-
</div>
|
384 |
-
</div>
|
385 |
-
</div>
|
386 |
-
</div>
|
387 |
-
);
|
388 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/debug/DebugTab.tsx
DELETED
@@ -1,639 +0,0 @@
|
|
1 |
-
import React, { useCallback, useEffect, useState } from 'react';
|
2 |
-
import { useSettings } from '~/lib/hooks/useSettings';
|
3 |
-
import { toast } from 'react-toastify';
|
4 |
-
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
5 |
-
|
6 |
-
interface ProviderStatus {
|
7 |
-
name: string;
|
8 |
-
enabled: boolean;
|
9 |
-
isLocal: boolean;
|
10 |
-
isRunning: boolean | null;
|
11 |
-
error?: string;
|
12 |
-
lastChecked: Date;
|
13 |
-
responseTime?: number;
|
14 |
-
url: string | null;
|
15 |
-
}
|
16 |
-
|
17 |
-
interface SystemInfo {
|
18 |
-
os: string;
|
19 |
-
browser: string;
|
20 |
-
screen: string;
|
21 |
-
language: string;
|
22 |
-
timezone: string;
|
23 |
-
memory: string;
|
24 |
-
cores: number;
|
25 |
-
deviceType: string;
|
26 |
-
colorDepth: string;
|
27 |
-
pixelRatio: number;
|
28 |
-
online: boolean;
|
29 |
-
cookiesEnabled: boolean;
|
30 |
-
doNotTrack: boolean;
|
31 |
-
}
|
32 |
-
|
33 |
-
interface IProviderConfig {
|
34 |
-
name: string;
|
35 |
-
settings: {
|
36 |
-
enabled: boolean;
|
37 |
-
baseUrl?: string;
|
38 |
-
};
|
39 |
-
}
|
40 |
-
|
41 |
-
interface CommitData {
|
42 |
-
commit: string;
|
43 |
-
version?: string;
|
44 |
-
}
|
45 |
-
|
46 |
-
const connitJson: CommitData = {
|
47 |
-
commit: __COMMIT_HASH,
|
48 |
-
version: __APP_VERSION,
|
49 |
-
};
|
50 |
-
|
51 |
-
const LOCAL_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
|
52 |
-
|
53 |
-
const versionHash = connitJson.commit;
|
54 |
-
const versionTag = connitJson.version;
|
55 |
-
|
56 |
-
const GITHUB_URLS = {
|
57 |
-
original: 'https://api.github.com/repos/stackblitz-labs/bolt.diy/commits/main',
|
58 |
-
fork: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main',
|
59 |
-
commitJson: async (branch: string) => {
|
60 |
-
try {
|
61 |
-
const response = await fetch(`https://api.github.com/repos/stackblitz-labs/bolt.diy/commits/${branch}`);
|
62 |
-
const data: { sha: string } = await response.json();
|
63 |
-
|
64 |
-
const packageJsonResp = await fetch(
|
65 |
-
`https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/${branch}/package.json`,
|
66 |
-
);
|
67 |
-
const packageJson: { version: string } = await packageJsonResp.json();
|
68 |
-
|
69 |
-
return {
|
70 |
-
commit: data.sha.slice(0, 7),
|
71 |
-
version: packageJson.version,
|
72 |
-
};
|
73 |
-
} catch (error) {
|
74 |
-
console.log('Failed to fetch local commit info:', error);
|
75 |
-
throw new Error('Failed to fetch local commit info');
|
76 |
-
}
|
77 |
-
},
|
78 |
-
};
|
79 |
-
|
80 |
-
function getSystemInfo(): SystemInfo {
|
81 |
-
const formatBytes = (bytes: number): string => {
|
82 |
-
if (bytes === 0) {
|
83 |
-
return '0 Bytes';
|
84 |
-
}
|
85 |
-
|
86 |
-
const k = 1024;
|
87 |
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
88 |
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
89 |
-
|
90 |
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
91 |
-
};
|
92 |
-
|
93 |
-
const getBrowserInfo = (): string => {
|
94 |
-
const ua = navigator.userAgent;
|
95 |
-
let browser = 'Unknown';
|
96 |
-
|
97 |
-
if (ua.includes('Firefox/')) {
|
98 |
-
browser = 'Firefox';
|
99 |
-
} else if (ua.includes('Chrome/')) {
|
100 |
-
if (ua.includes('Edg/')) {
|
101 |
-
browser = 'Edge';
|
102 |
-
} else if (ua.includes('OPR/')) {
|
103 |
-
browser = 'Opera';
|
104 |
-
} else {
|
105 |
-
browser = 'Chrome';
|
106 |
-
}
|
107 |
-
} else if (ua.includes('Safari/')) {
|
108 |
-
if (!ua.includes('Chrome')) {
|
109 |
-
browser = 'Safari';
|
110 |
-
}
|
111 |
-
}
|
112 |
-
|
113 |
-
// Extract version number
|
114 |
-
const match = ua.match(new RegExp(`${browser}\\/([\\d.]+)`));
|
115 |
-
const version = match ? ` ${match[1]}` : '';
|
116 |
-
|
117 |
-
return `${browser}${version}`;
|
118 |
-
};
|
119 |
-
|
120 |
-
const getOperatingSystem = (): string => {
|
121 |
-
const ua = navigator.userAgent;
|
122 |
-
const platform = navigator.platform;
|
123 |
-
|
124 |
-
if (ua.includes('Win')) {
|
125 |
-
return 'Windows';
|
126 |
-
}
|
127 |
-
|
128 |
-
if (ua.includes('Mac')) {
|
129 |
-
if (ua.includes('iPhone') || ua.includes('iPad')) {
|
130 |
-
return 'iOS';
|
131 |
-
}
|
132 |
-
|
133 |
-
return 'macOS';
|
134 |
-
}
|
135 |
-
|
136 |
-
if (ua.includes('Linux')) {
|
137 |
-
return 'Linux';
|
138 |
-
}
|
139 |
-
|
140 |
-
if (ua.includes('Android')) {
|
141 |
-
return 'Android';
|
142 |
-
}
|
143 |
-
|
144 |
-
return platform || 'Unknown';
|
145 |
-
};
|
146 |
-
|
147 |
-
const getDeviceType = (): string => {
|
148 |
-
const ua = navigator.userAgent;
|
149 |
-
|
150 |
-
if (ua.includes('Mobile')) {
|
151 |
-
return 'Mobile';
|
152 |
-
}
|
153 |
-
|
154 |
-
if (ua.includes('Tablet')) {
|
155 |
-
return 'Tablet';
|
156 |
-
}
|
157 |
-
|
158 |
-
return 'Desktop';
|
159 |
-
};
|
160 |
-
|
161 |
-
// Get more detailed memory info if available
|
162 |
-
const getMemoryInfo = (): string => {
|
163 |
-
if ('memory' in performance) {
|
164 |
-
const memory = (performance as any).memory;
|
165 |
-
return `${formatBytes(memory.jsHeapSizeLimit)} (Used: ${formatBytes(memory.usedJSHeapSize)})`;
|
166 |
-
}
|
167 |
-
|
168 |
-
return 'Not available';
|
169 |
-
};
|
170 |
-
|
171 |
-
return {
|
172 |
-
os: getOperatingSystem(),
|
173 |
-
browser: getBrowserInfo(),
|
174 |
-
screen: `${window.screen.width}x${window.screen.height}`,
|
175 |
-
language: navigator.language,
|
176 |
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
177 |
-
memory: getMemoryInfo(),
|
178 |
-
cores: navigator.hardwareConcurrency || 0,
|
179 |
-
deviceType: getDeviceType(),
|
180 |
-
|
181 |
-
// Add new fields
|
182 |
-
colorDepth: `${window.screen.colorDepth}-bit`,
|
183 |
-
pixelRatio: window.devicePixelRatio,
|
184 |
-
online: navigator.onLine,
|
185 |
-
cookiesEnabled: navigator.cookieEnabled,
|
186 |
-
doNotTrack: navigator.doNotTrack === '1',
|
187 |
-
};
|
188 |
-
}
|
189 |
-
|
190 |
-
const checkProviderStatus = async (url: string | null, providerName: string): Promise<ProviderStatus> => {
|
191 |
-
if (!url) {
|
192 |
-
console.log(`[Debug] No URL provided for ${providerName}`);
|
193 |
-
return {
|
194 |
-
name: providerName,
|
195 |
-
enabled: false,
|
196 |
-
isLocal: true,
|
197 |
-
isRunning: false,
|
198 |
-
error: 'No URL configured',
|
199 |
-
lastChecked: new Date(),
|
200 |
-
url: null,
|
201 |
-
};
|
202 |
-
}
|
203 |
-
|
204 |
-
console.log(`[Debug] Checking status for ${providerName} at ${url}`);
|
205 |
-
|
206 |
-
const startTime = performance.now();
|
207 |
-
|
208 |
-
try {
|
209 |
-
if (providerName.toLowerCase() === 'ollama') {
|
210 |
-
// Special check for Ollama root endpoint
|
211 |
-
try {
|
212 |
-
console.log(`[Debug] Checking Ollama root endpoint: ${url}`);
|
213 |
-
|
214 |
-
const controller = new AbortController();
|
215 |
-
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
216 |
-
|
217 |
-
const response = await fetch(url, {
|
218 |
-
signal: controller.signal,
|
219 |
-
headers: {
|
220 |
-
Accept: 'text/plain,application/json',
|
221 |
-
},
|
222 |
-
});
|
223 |
-
clearTimeout(timeoutId);
|
224 |
-
|
225 |
-
const text = await response.text();
|
226 |
-
console.log(`[Debug] Ollama root response:`, text);
|
227 |
-
|
228 |
-
if (text.includes('Ollama is running')) {
|
229 |
-
console.log(`[Debug] Ollama running confirmed via root endpoint`);
|
230 |
-
return {
|
231 |
-
name: providerName,
|
232 |
-
enabled: false,
|
233 |
-
isLocal: true,
|
234 |
-
isRunning: true,
|
235 |
-
lastChecked: new Date(),
|
236 |
-
responseTime: performance.now() - startTime,
|
237 |
-
url,
|
238 |
-
};
|
239 |
-
}
|
240 |
-
} catch (error) {
|
241 |
-
console.log(`[Debug] Ollama root check failed:`, error);
|
242 |
-
|
243 |
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
244 |
-
|
245 |
-
if (errorMessage.includes('aborted')) {
|
246 |
-
return {
|
247 |
-
name: providerName,
|
248 |
-
enabled: false,
|
249 |
-
isLocal: true,
|
250 |
-
isRunning: false,
|
251 |
-
error: 'Connection timeout',
|
252 |
-
lastChecked: new Date(),
|
253 |
-
responseTime: performance.now() - startTime,
|
254 |
-
url,
|
255 |
-
};
|
256 |
-
}
|
257 |
-
}
|
258 |
-
}
|
259 |
-
|
260 |
-
// Try different endpoints based on provider
|
261 |
-
const checkUrls = [`${url}/api/health`, url.endsWith('v1') ? `${url}/models` : `${url}/v1/models`];
|
262 |
-
console.log(`[Debug] Checking additional endpoints:`, checkUrls);
|
263 |
-
|
264 |
-
const results = await Promise.all(
|
265 |
-
checkUrls.map(async (checkUrl) => {
|
266 |
-
try {
|
267 |
-
console.log(`[Debug] Trying endpoint: ${checkUrl}`);
|
268 |
-
|
269 |
-
const controller = new AbortController();
|
270 |
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
271 |
-
|
272 |
-
const response = await fetch(checkUrl, {
|
273 |
-
signal: controller.signal,
|
274 |
-
headers: {
|
275 |
-
Accept: 'application/json',
|
276 |
-
},
|
277 |
-
});
|
278 |
-
clearTimeout(timeoutId);
|
279 |
-
|
280 |
-
const ok = response.ok;
|
281 |
-
console.log(`[Debug] Endpoint ${checkUrl} response:`, ok);
|
282 |
-
|
283 |
-
if (ok) {
|
284 |
-
try {
|
285 |
-
const data = await response.json();
|
286 |
-
console.log(`[Debug] Endpoint ${checkUrl} data:`, data);
|
287 |
-
} catch {
|
288 |
-
console.log(`[Debug] Could not parse JSON from ${checkUrl}`);
|
289 |
-
}
|
290 |
-
}
|
291 |
-
|
292 |
-
return ok;
|
293 |
-
} catch (error) {
|
294 |
-
console.log(`[Debug] Endpoint ${checkUrl} failed:`, error);
|
295 |
-
return false;
|
296 |
-
}
|
297 |
-
}),
|
298 |
-
);
|
299 |
-
|
300 |
-
const isRunning = results.some((result) => result);
|
301 |
-
console.log(`[Debug] Final status for ${providerName}:`, isRunning);
|
302 |
-
|
303 |
-
return {
|
304 |
-
name: providerName,
|
305 |
-
enabled: false,
|
306 |
-
isLocal: true,
|
307 |
-
isRunning,
|
308 |
-
lastChecked: new Date(),
|
309 |
-
responseTime: performance.now() - startTime,
|
310 |
-
url,
|
311 |
-
};
|
312 |
-
} catch (error) {
|
313 |
-
console.log(`[Debug] Provider check failed for ${providerName}:`, error);
|
314 |
-
return {
|
315 |
-
name: providerName,
|
316 |
-
enabled: false,
|
317 |
-
isLocal: true,
|
318 |
-
isRunning: false,
|
319 |
-
error: error instanceof Error ? error.message : 'Unknown error',
|
320 |
-
lastChecked: new Date(),
|
321 |
-
responseTime: performance.now() - startTime,
|
322 |
-
url,
|
323 |
-
};
|
324 |
-
}
|
325 |
-
};
|
326 |
-
|
327 |
-
export default function DebugTab() {
|
328 |
-
const { providers, isLatestBranch } = useSettings();
|
329 |
-
const [activeProviders, setActiveProviders] = useState<ProviderStatus[]>([]);
|
330 |
-
const [updateMessage, setUpdateMessage] = useState<string>('');
|
331 |
-
const [systemInfo] = useState<SystemInfo>(getSystemInfo());
|
332 |
-
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
333 |
-
|
334 |
-
const updateProviderStatuses = async () => {
|
335 |
-
if (!providers) {
|
336 |
-
return;
|
337 |
-
}
|
338 |
-
|
339 |
-
try {
|
340 |
-
const entries = Object.entries(providers) as [string, IProviderConfig][];
|
341 |
-
const statuses = await Promise.all(
|
342 |
-
entries
|
343 |
-
.filter(([, provider]) => LOCAL_PROVIDERS.includes(provider.name))
|
344 |
-
.map(async ([, provider]) => {
|
345 |
-
const envVarName =
|
346 |
-
providerBaseUrlEnvKeys[provider.name].baseUrlKey || `REACT_APP_${provider.name.toUpperCase()}_URL`;
|
347 |
-
|
348 |
-
// Access environment variables through import.meta.env
|
349 |
-
let settingsUrl = provider.settings.baseUrl;
|
350 |
-
|
351 |
-
if (settingsUrl && settingsUrl.trim().length === 0) {
|
352 |
-
settingsUrl = undefined;
|
353 |
-
}
|
354 |
-
|
355 |
-
const url = settingsUrl || import.meta.env[envVarName] || null; // Ensure baseUrl is used
|
356 |
-
console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`);
|
357 |
-
|
358 |
-
const status = await checkProviderStatus(url, provider.name);
|
359 |
-
|
360 |
-
return {
|
361 |
-
...status,
|
362 |
-
enabled: provider.settings.enabled ?? false,
|
363 |
-
};
|
364 |
-
}),
|
365 |
-
);
|
366 |
-
|
367 |
-
setActiveProviders(statuses);
|
368 |
-
} catch (error) {
|
369 |
-
console.error('[Debug] Failed to update provider statuses:', error);
|
370 |
-
}
|
371 |
-
};
|
372 |
-
|
373 |
-
useEffect(() => {
|
374 |
-
updateProviderStatuses();
|
375 |
-
|
376 |
-
const interval = setInterval(updateProviderStatuses, 30000);
|
377 |
-
|
378 |
-
return () => clearInterval(interval);
|
379 |
-
}, [providers]);
|
380 |
-
|
381 |
-
const handleCheckForUpdate = useCallback(async () => {
|
382 |
-
if (isCheckingUpdate) {
|
383 |
-
return;
|
384 |
-
}
|
385 |
-
|
386 |
-
try {
|
387 |
-
setIsCheckingUpdate(true);
|
388 |
-
setUpdateMessage('Checking for updates...');
|
389 |
-
|
390 |
-
const branchToCheck = isLatestBranch ? 'main' : 'stable';
|
391 |
-
console.log(`[Debug] Checking for updates against ${branchToCheck} branch`);
|
392 |
-
|
393 |
-
const latestCommitResp = await GITHUB_URLS.commitJson(branchToCheck);
|
394 |
-
|
395 |
-
const remoteCommitHash = latestCommitResp.commit;
|
396 |
-
const currentCommitHash = versionHash;
|
397 |
-
|
398 |
-
if (remoteCommitHash !== currentCommitHash) {
|
399 |
-
setUpdateMessage(
|
400 |
-
`Update available from ${branchToCheck} branch!\n` +
|
401 |
-
`Current: ${currentCommitHash.slice(0, 7)}\n` +
|
402 |
-
`Latest: ${remoteCommitHash.slice(0, 7)}`,
|
403 |
-
);
|
404 |
-
} else {
|
405 |
-
setUpdateMessage(`You are on the latest version from the ${branchToCheck} branch`);
|
406 |
-
}
|
407 |
-
} catch (error) {
|
408 |
-
setUpdateMessage('Failed to check for updates');
|
409 |
-
console.error('[Debug] Failed to check for updates:', error);
|
410 |
-
} finally {
|
411 |
-
setIsCheckingUpdate(false);
|
412 |
-
}
|
413 |
-
}, [isCheckingUpdate, isLatestBranch]);
|
414 |
-
|
415 |
-
const handleCopyToClipboard = useCallback(() => {
|
416 |
-
const debugInfo = {
|
417 |
-
System: systemInfo,
|
418 |
-
Providers: activeProviders.map((provider) => ({
|
419 |
-
name: provider.name,
|
420 |
-
enabled: provider.enabled,
|
421 |
-
isLocal: provider.isLocal,
|
422 |
-
running: provider.isRunning,
|
423 |
-
error: provider.error,
|
424 |
-
lastChecked: provider.lastChecked,
|
425 |
-
responseTime: provider.responseTime,
|
426 |
-
url: provider.url,
|
427 |
-
})),
|
428 |
-
Version: {
|
429 |
-
hash: versionHash.slice(0, 7),
|
430 |
-
branch: isLatestBranch ? 'main' : 'stable',
|
431 |
-
},
|
432 |
-
Timestamp: new Date().toISOString(),
|
433 |
-
};
|
434 |
-
|
435 |
-
navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => {
|
436 |
-
toast.success('Debug information copied to clipboard!');
|
437 |
-
});
|
438 |
-
}, [activeProviders, systemInfo, isLatestBranch]);
|
439 |
-
|
440 |
-
return (
|
441 |
-
<div className="p-4 space-y-6">
|
442 |
-
<div className="flex items-center justify-between">
|
443 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Debug Information</h3>
|
444 |
-
<div className="flex gap-2">
|
445 |
-
<button
|
446 |
-
onClick={handleCopyToClipboard}
|
447 |
-
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text"
|
448 |
-
>
|
449 |
-
Copy Debug Info
|
450 |
-
</button>
|
451 |
-
<button
|
452 |
-
onClick={handleCheckForUpdate}
|
453 |
-
disabled={isCheckingUpdate}
|
454 |
-
className={`bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 transition-colors duration-200
|
455 |
-
${!isCheckingUpdate ? 'hover:bg-bolt-elements-button-primary-backgroundHover' : 'opacity-75 cursor-not-allowed'}
|
456 |
-
text-bolt-elements-button-primary-text`}
|
457 |
-
>
|
458 |
-
{isCheckingUpdate ? 'Checking...' : 'Check for Updates'}
|
459 |
-
</button>
|
460 |
-
</div>
|
461 |
-
</div>
|
462 |
-
|
463 |
-
{updateMessage && (
|
464 |
-
<div
|
465 |
-
className={`bg-bolt-elements-surface rounded-lg p-3 ${
|
466 |
-
updateMessage.includes('Update available') ? 'border-l-4 border-yellow-400' : ''
|
467 |
-
}`}
|
468 |
-
>
|
469 |
-
<p className="text-bolt-elements-textSecondary whitespace-pre-line">{updateMessage}</p>
|
470 |
-
{updateMessage.includes('Update available') && (
|
471 |
-
<div className="mt-3 text-sm">
|
472 |
-
<p className="font-medium text-bolt-elements-textPrimary">To update:</p>
|
473 |
-
<ol className="list-decimal ml-4 mt-1 text-bolt-elements-textSecondary">
|
474 |
-
<li>
|
475 |
-
Pull the latest changes:{' '}
|
476 |
-
<code className="bg-bolt-elements-surface-hover px-1 rounded">git pull upstream main</code>
|
477 |
-
</li>
|
478 |
-
<li>
|
479 |
-
Install any new dependencies:{' '}
|
480 |
-
<code className="bg-bolt-elements-surface-hover px-1 rounded">pnpm install</code>
|
481 |
-
</li>
|
482 |
-
<li>Restart the application</li>
|
483 |
-
</ol>
|
484 |
-
</div>
|
485 |
-
)}
|
486 |
-
</div>
|
487 |
-
)}
|
488 |
-
|
489 |
-
<section className="space-y-4">
|
490 |
-
<div>
|
491 |
-
<h4 className="text-md font-medium text-bolt-elements-textPrimary mb-2">System Information</h4>
|
492 |
-
<div className="bg-bolt-elements-surface rounded-lg p-4">
|
493 |
-
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
494 |
-
<div>
|
495 |
-
<p className="text-xs text-bolt-elements-textSecondary">Operating System</p>
|
496 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.os}</p>
|
497 |
-
</div>
|
498 |
-
<div>
|
499 |
-
<p className="text-xs text-bolt-elements-textSecondary">Device Type</p>
|
500 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.deviceType}</p>
|
501 |
-
</div>
|
502 |
-
<div>
|
503 |
-
<p className="text-xs text-bolt-elements-textSecondary">Browser</p>
|
504 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.browser}</p>
|
505 |
-
</div>
|
506 |
-
<div>
|
507 |
-
<p className="text-xs text-bolt-elements-textSecondary">Display</p>
|
508 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">
|
509 |
-
{systemInfo.screen} ({systemInfo.colorDepth}) @{systemInfo.pixelRatio}x
|
510 |
-
</p>
|
511 |
-
</div>
|
512 |
-
<div>
|
513 |
-
<p className="text-xs text-bolt-elements-textSecondary">Connection</p>
|
514 |
-
<p className="text-sm font-medium flex items-center gap-2">
|
515 |
-
<span
|
516 |
-
className={`inline-block w-2 h-2 rounded-full ${systemInfo.online ? 'bg-green-500' : 'bg-red-500'}`}
|
517 |
-
/>
|
518 |
-
<span className={`${systemInfo.online ? 'text-green-600' : 'text-red-600'}`}>
|
519 |
-
{systemInfo.online ? 'Online' : 'Offline'}
|
520 |
-
</span>
|
521 |
-
</p>
|
522 |
-
</div>
|
523 |
-
<div>
|
524 |
-
<p className="text-xs text-bolt-elements-textSecondary">Screen Resolution</p>
|
525 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.screen}</p>
|
526 |
-
</div>
|
527 |
-
<div>
|
528 |
-
<p className="text-xs text-bolt-elements-textSecondary">Language</p>
|
529 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.language}</p>
|
530 |
-
</div>
|
531 |
-
<div>
|
532 |
-
<p className="text-xs text-bolt-elements-textSecondary">Timezone</p>
|
533 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.timezone}</p>
|
534 |
-
</div>
|
535 |
-
<div>
|
536 |
-
<p className="text-xs text-bolt-elements-textSecondary">CPU Cores</p>
|
537 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.cores}</p>
|
538 |
-
</div>
|
539 |
-
</div>
|
540 |
-
<div className="mt-3 pt-3 border-t border-bolt-elements-surface-hover">
|
541 |
-
<p className="text-xs text-bolt-elements-textSecondary">Version</p>
|
542 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary font-mono">
|
543 |
-
{connitJson.commit.slice(0, 7)}
|
544 |
-
<span className="ml-2 text-xs text-bolt-elements-textSecondary">
|
545 |
-
(v{versionTag || '0.0.1'}) - {isLatestBranch ? 'nightly' : 'stable'}
|
546 |
-
</span>
|
547 |
-
</p>
|
548 |
-
</div>
|
549 |
-
</div>
|
550 |
-
</div>
|
551 |
-
|
552 |
-
<div>
|
553 |
-
<h4 className="text-md font-medium text-bolt-elements-textPrimary mb-2">Local LLM Status</h4>
|
554 |
-
<div className="bg-bolt-elements-surface rounded-lg">
|
555 |
-
<div className="grid grid-cols-1 divide-y">
|
556 |
-
{activeProviders.map((provider) => (
|
557 |
-
<div key={provider.name} className="p-3 flex flex-col space-y-2">
|
558 |
-
<div className="flex items-center justify-between">
|
559 |
-
<div className="flex items-center gap-3">
|
560 |
-
<div className="flex-shrink-0">
|
561 |
-
<div
|
562 |
-
className={`w-2 h-2 rounded-full ${
|
563 |
-
!provider.enabled ? 'bg-gray-300' : provider.isRunning ? 'bg-green-400' : 'bg-red-400'
|
564 |
-
}`}
|
565 |
-
/>
|
566 |
-
</div>
|
567 |
-
<div>
|
568 |
-
<p className="text-sm font-medium text-bolt-elements-textPrimary">{provider.name}</p>
|
569 |
-
{provider.url && (
|
570 |
-
<p className="text-xs text-bolt-elements-textSecondary truncate max-w-[300px]">
|
571 |
-
{provider.url}
|
572 |
-
</p>
|
573 |
-
)}
|
574 |
-
</div>
|
575 |
-
</div>
|
576 |
-
<div className="flex items-center gap-2">
|
577 |
-
<span
|
578 |
-
className={`px-2 py-0.5 text-xs rounded-full ${
|
579 |
-
provider.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
580 |
-
}`}
|
581 |
-
>
|
582 |
-
{provider.enabled ? 'Enabled' : 'Disabled'}
|
583 |
-
</span>
|
584 |
-
{provider.enabled && (
|
585 |
-
<span
|
586 |
-
className={`px-2 py-0.5 text-xs rounded-full ${
|
587 |
-
provider.isRunning ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
588 |
-
}`}
|
589 |
-
>
|
590 |
-
{provider.isRunning ? 'Running' : 'Not Running'}
|
591 |
-
</span>
|
592 |
-
)}
|
593 |
-
</div>
|
594 |
-
</div>
|
595 |
-
|
596 |
-
<div className="pl-5 flex flex-col space-y-1 text-xs">
|
597 |
-
{/* Status Details */}
|
598 |
-
<div className="flex flex-wrap gap-2">
|
599 |
-
<span className="text-bolt-elements-textSecondary">
|
600 |
-
Last checked: {new Date(provider.lastChecked).toLocaleTimeString()}
|
601 |
-
</span>
|
602 |
-
{provider.responseTime && (
|
603 |
-
<span className="text-bolt-elements-textSecondary">
|
604 |
-
Response time: {Math.round(provider.responseTime)}ms
|
605 |
-
</span>
|
606 |
-
)}
|
607 |
-
</div>
|
608 |
-
|
609 |
-
{/* Error Message */}
|
610 |
-
{provider.error && (
|
611 |
-
<div className="mt-1 text-red-600 bg-red-50 rounded-md p-2">
|
612 |
-
<span className="font-medium">Error:</span> {provider.error}
|
613 |
-
</div>
|
614 |
-
)}
|
615 |
-
|
616 |
-
{/* Connection Info */}
|
617 |
-
{provider.url && (
|
618 |
-
<div className="text-bolt-elements-textSecondary">
|
619 |
-
<span className="font-medium">Endpoints checked:</span>
|
620 |
-
<ul className="list-disc list-inside pl-2 mt-1">
|
621 |
-
<li>{provider.url} (root)</li>
|
622 |
-
<li>{provider.url}/api/health</li>
|
623 |
-
<li>{provider.url}/v1/models</li>
|
624 |
-
</ul>
|
625 |
-
</div>
|
626 |
-
)}
|
627 |
-
</div>
|
628 |
-
</div>
|
629 |
-
))}
|
630 |
-
{activeProviders.length === 0 && (
|
631 |
-
<div className="p-4 text-center text-bolt-elements-textSecondary">No local LLMs configured</div>
|
632 |
-
)}
|
633 |
-
</div>
|
634 |
-
</div>
|
635 |
-
</div>
|
636 |
-
</section>
|
637 |
-
</div>
|
638 |
-
);
|
639 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/event-logs/EventLogsTab.tsx
DELETED
@@ -1,219 +0,0 @@
|
|
1 |
-
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
2 |
-
import { useSettings } from '~/lib/hooks/useSettings';
|
3 |
-
import { toast } from 'react-toastify';
|
4 |
-
import { Switch } from '~/components/ui/Switch';
|
5 |
-
import { logStore, type LogEntry } from '~/lib/stores/logs';
|
6 |
-
import { useStore } from '@nanostores/react';
|
7 |
-
import { classNames } from '~/utils/classNames';
|
8 |
-
|
9 |
-
export default function EventLogsTab() {
|
10 |
-
const {} = useSettings();
|
11 |
-
const showLogs = useStore(logStore.showLogs);
|
12 |
-
const [logLevel, setLogLevel] = useState<LogEntry['level'] | 'all'>('info');
|
13 |
-
const [autoScroll, setAutoScroll] = useState(true);
|
14 |
-
const [searchQuery, setSearchQuery] = useState('');
|
15 |
-
const [, forceUpdate] = useState({});
|
16 |
-
|
17 |
-
const filteredLogs = useMemo(() => {
|
18 |
-
const logs = logStore.getLogs();
|
19 |
-
return logs.filter((log) => {
|
20 |
-
const matchesLevel = !logLevel || log.level === logLevel || logLevel === 'all';
|
21 |
-
const matchesSearch =
|
22 |
-
!searchQuery ||
|
23 |
-
log.message?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
24 |
-
JSON.stringify(log.details)?.toLowerCase()?.includes(searchQuery?.toLowerCase());
|
25 |
-
|
26 |
-
return matchesLevel && matchesSearch;
|
27 |
-
});
|
28 |
-
}, [logLevel, searchQuery]);
|
29 |
-
|
30 |
-
// Effect to initialize showLogs
|
31 |
-
useEffect(() => {
|
32 |
-
logStore.showLogs.set(true);
|
33 |
-
}, []);
|
34 |
-
|
35 |
-
useEffect(() => {
|
36 |
-
// System info logs
|
37 |
-
logStore.logSystem('Application initialized', {
|
38 |
-
version: process.env.NEXT_PUBLIC_APP_VERSION,
|
39 |
-
environment: process.env.NODE_ENV,
|
40 |
-
});
|
41 |
-
|
42 |
-
// Debug logs for system state
|
43 |
-
logStore.logDebug('System configuration loaded', {
|
44 |
-
runtime: 'Next.js',
|
45 |
-
features: ['AI Chat', 'Event Logging'],
|
46 |
-
});
|
47 |
-
|
48 |
-
// Warning logs for potential issues
|
49 |
-
logStore.logWarning('Resource usage threshold approaching', {
|
50 |
-
memoryUsage: '75%',
|
51 |
-
cpuLoad: '60%',
|
52 |
-
});
|
53 |
-
|
54 |
-
// Error logs with detailed context
|
55 |
-
logStore.logError('API connection failed', new Error('Connection timeout'), {
|
56 |
-
endpoint: '/api/chat',
|
57 |
-
retryCount: 3,
|
58 |
-
lastAttempt: new Date().toISOString(),
|
59 |
-
});
|
60 |
-
}, []);
|
61 |
-
|
62 |
-
useEffect(() => {
|
63 |
-
const container = document.querySelector('.logs-container');
|
64 |
-
|
65 |
-
if (container && autoScroll) {
|
66 |
-
container.scrollTop = container.scrollHeight;
|
67 |
-
}
|
68 |
-
}, [filteredLogs, autoScroll]);
|
69 |
-
|
70 |
-
const handleClearLogs = useCallback(() => {
|
71 |
-
if (confirm('Are you sure you want to clear all logs?')) {
|
72 |
-
logStore.clearLogs();
|
73 |
-
toast.success('Logs cleared successfully');
|
74 |
-
forceUpdate({}); // Force a re-render after clearing logs
|
75 |
-
}
|
76 |
-
}, []);
|
77 |
-
|
78 |
-
const handleExportLogs = useCallback(() => {
|
79 |
-
try {
|
80 |
-
const logText = logStore
|
81 |
-
.getLogs()
|
82 |
-
.map(
|
83 |
-
(log) =>
|
84 |
-
`[${log.level.toUpperCase()}] ${log.timestamp} - ${log.message}${
|
85 |
-
log.details ? '\nDetails: ' + JSON.stringify(log.details, null, 2) : ''
|
86 |
-
}`,
|
87 |
-
)
|
88 |
-
.join('\n\n');
|
89 |
-
|
90 |
-
const blob = new Blob([logText], { type: 'text/plain' });
|
91 |
-
const url = URL.createObjectURL(blob);
|
92 |
-
const a = document.createElement('a');
|
93 |
-
a.href = url;
|
94 |
-
a.download = `event-logs-${new Date().toISOString()}.txt`;
|
95 |
-
document.body.appendChild(a);
|
96 |
-
a.click();
|
97 |
-
document.body.removeChild(a);
|
98 |
-
URL.revokeObjectURL(url);
|
99 |
-
toast.success('Logs exported successfully');
|
100 |
-
} catch (error) {
|
101 |
-
toast.error('Failed to export logs');
|
102 |
-
console.error('Export error:', error);
|
103 |
-
}
|
104 |
-
}, []);
|
105 |
-
|
106 |
-
const getLevelColor = (level: LogEntry['level']) => {
|
107 |
-
switch (level) {
|
108 |
-
case 'info':
|
109 |
-
return 'text-blue-500';
|
110 |
-
case 'warning':
|
111 |
-
return 'text-yellow-500';
|
112 |
-
case 'error':
|
113 |
-
return 'text-red-500';
|
114 |
-
case 'debug':
|
115 |
-
return 'text-gray-500';
|
116 |
-
default:
|
117 |
-
return 'text-bolt-elements-textPrimary';
|
118 |
-
}
|
119 |
-
};
|
120 |
-
|
121 |
-
return (
|
122 |
-
<div className="p-4 h-full flex flex-col">
|
123 |
-
<div className="flex flex-col space-y-4 mb-4">
|
124 |
-
{/* Title and Toggles Row */}
|
125 |
-
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
126 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Event Logs</h3>
|
127 |
-
<div className="flex flex-wrap items-center gap-4">
|
128 |
-
<div className="flex items-center space-x-2">
|
129 |
-
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Show Actions</span>
|
130 |
-
<Switch checked={showLogs} onCheckedChange={(checked) => logStore.showLogs.set(checked)} />
|
131 |
-
</div>
|
132 |
-
<div className="flex items-center space-x-2">
|
133 |
-
<span className="text-sm text-bolt-elements-textSecondary whitespace-nowrap">Auto-scroll</span>
|
134 |
-
<Switch checked={autoScroll} onCheckedChange={setAutoScroll} />
|
135 |
-
</div>
|
136 |
-
</div>
|
137 |
-
</div>
|
138 |
-
|
139 |
-
{/* Controls Row */}
|
140 |
-
<div className="flex flex-wrap items-center gap-2">
|
141 |
-
<select
|
142 |
-
value={logLevel}
|
143 |
-
onChange={(e) => setLogLevel(e.target.value as LogEntry['level'])}
|
144 |
-
className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[20%] text-sm min-w-[100px]"
|
145 |
-
>
|
146 |
-
<option value="all">All</option>
|
147 |
-
<option value="info">Info</option>
|
148 |
-
<option value="warning">Warning</option>
|
149 |
-
<option value="error">Error</option>
|
150 |
-
<option value="debug">Debug</option>
|
151 |
-
</select>
|
152 |
-
<div className="flex-1 min-w-[200px]">
|
153 |
-
<input
|
154 |
-
type="text"
|
155 |
-
placeholder="Search logs..."
|
156 |
-
value={searchQuery}
|
157 |
-
onChange={(e) => setSearchQuery(e.target.value)}
|
158 |
-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
159 |
-
/>
|
160 |
-
</div>
|
161 |
-
{showLogs && (
|
162 |
-
<div className="flex items-center gap-2 flex-nowrap">
|
163 |
-
<button
|
164 |
-
onClick={handleExportLogs}
|
165 |
-
className={classNames(
|
166 |
-
'bg-bolt-elements-button-primary-background',
|
167 |
-
'rounded-lg px-4 py-2 transition-colors duration-200',
|
168 |
-
'hover:bg-bolt-elements-button-primary-backgroundHover',
|
169 |
-
'text-bolt-elements-button-primary-text',
|
170 |
-
)}
|
171 |
-
>
|
172 |
-
Export Logs
|
173 |
-
</button>
|
174 |
-
<button
|
175 |
-
onClick={handleClearLogs}
|
176 |
-
className={classNames(
|
177 |
-
'bg-bolt-elements-button-danger-background',
|
178 |
-
'rounded-lg px-4 py-2 transition-colors duration-200',
|
179 |
-
'hover:bg-bolt-elements-button-danger-backgroundHover',
|
180 |
-
'text-bolt-elements-button-danger-text',
|
181 |
-
)}
|
182 |
-
>
|
183 |
-
Clear Logs
|
184 |
-
</button>
|
185 |
-
</div>
|
186 |
-
)}
|
187 |
-
</div>
|
188 |
-
</div>
|
189 |
-
|
190 |
-
<div className="bg-bolt-elements-bg-depth-1 rounded-lg p-4 h-[calc(100vh - 250px)] min-h-[400px] overflow-y-auto logs-container overflow-y-auto">
|
191 |
-
{filteredLogs.length === 0 ? (
|
192 |
-
<div className="text-center text-bolt-elements-textSecondary py-8">No logs found</div>
|
193 |
-
) : (
|
194 |
-
filteredLogs.map((log, index) => (
|
195 |
-
<div
|
196 |
-
key={index}
|
197 |
-
className="text-sm mb-3 font-mono border-b border-bolt-elements-borderColor pb-2 last:border-0"
|
198 |
-
>
|
199 |
-
<div className="flex items-start space-x-2 flex-wrap">
|
200 |
-
<span className={`font-bold ${getLevelColor(log.level)} whitespace-nowrap`}>
|
201 |
-
[{log.level.toUpperCase()}]
|
202 |
-
</span>
|
203 |
-
<span className="text-bolt-elements-textSecondary whitespace-nowrap">
|
204 |
-
{new Date(log.timestamp).toLocaleString()}
|
205 |
-
</span>
|
206 |
-
<span className="text-bolt-elements-textPrimary break-all">{log.message}</span>
|
207 |
-
</div>
|
208 |
-
{log.details && (
|
209 |
-
<pre className="mt-2 text-xs text-bolt-elements-textSecondary overflow-x-auto whitespace-pre-wrap break-all">
|
210 |
-
{JSON.stringify(log.details, null, 2)}
|
211 |
-
</pre>
|
212 |
-
)}
|
213 |
-
</div>
|
214 |
-
))
|
215 |
-
)}
|
216 |
-
</div>
|
217 |
-
</div>
|
218 |
-
);
|
219 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/features/FeaturesTab.tsx
DELETED
@@ -1,107 +0,0 @@
|
|
1 |
-
import React from 'react';
|
2 |
-
import { Switch } from '~/components/ui/Switch';
|
3 |
-
import { PromptLibrary } from '~/lib/common/prompt-library';
|
4 |
-
import { useSettings } from '~/lib/hooks/useSettings';
|
5 |
-
|
6 |
-
export default function FeaturesTab() {
|
7 |
-
const {
|
8 |
-
debug,
|
9 |
-
enableDebugMode,
|
10 |
-
isLocalModel,
|
11 |
-
enableLocalModels,
|
12 |
-
enableEventLogs,
|
13 |
-
isLatestBranch,
|
14 |
-
enableLatestBranch,
|
15 |
-
promptId,
|
16 |
-
setPromptId,
|
17 |
-
autoSelectTemplate,
|
18 |
-
setAutoSelectTemplate,
|
19 |
-
enableContextOptimization,
|
20 |
-
contextOptimizationEnabled,
|
21 |
-
} = useSettings();
|
22 |
-
|
23 |
-
const handleToggle = (enabled: boolean) => {
|
24 |
-
enableDebugMode(enabled);
|
25 |
-
enableEventLogs(enabled);
|
26 |
-
};
|
27 |
-
|
28 |
-
return (
|
29 |
-
<div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
|
30 |
-
<div className="mb-6">
|
31 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Optional Features</h3>
|
32 |
-
<div className="space-y-4">
|
33 |
-
<div className="flex items-center justify-between">
|
34 |
-
<span className="text-bolt-elements-textPrimary">Debug Features</span>
|
35 |
-
<Switch className="ml-auto" checked={debug} onCheckedChange={handleToggle} />
|
36 |
-
</div>
|
37 |
-
<div className="flex items-center justify-between">
|
38 |
-
<div>
|
39 |
-
<span className="text-bolt-elements-textPrimary">Use Main Branch</span>
|
40 |
-
<p className="text-xs text-bolt-elements-textTertiary">
|
41 |
-
Check for updates against the main branch instead of stable
|
42 |
-
</p>
|
43 |
-
</div>
|
44 |
-
<Switch className="ml-auto" checked={isLatestBranch} onCheckedChange={enableLatestBranch} />
|
45 |
-
</div>
|
46 |
-
<div className="flex items-center justify-between">
|
47 |
-
<div>
|
48 |
-
<span className="text-bolt-elements-textPrimary">Auto Select Code Template</span>
|
49 |
-
<p className="text-xs text-bolt-elements-textTertiary">
|
50 |
-
Let Bolt select the best starter template for your project.
|
51 |
-
</p>
|
52 |
-
</div>
|
53 |
-
<Switch className="ml-auto" checked={autoSelectTemplate} onCheckedChange={setAutoSelectTemplate} />
|
54 |
-
</div>
|
55 |
-
<div className="flex items-center justify-between">
|
56 |
-
<div>
|
57 |
-
<span className="text-bolt-elements-textPrimary">Use Context Optimization</span>
|
58 |
-
<p className="text-sm text-bolt-elements-textSecondary">
|
59 |
-
redact file contents form chat and puts the latest file contents on the system prompt
|
60 |
-
</p>
|
61 |
-
</div>
|
62 |
-
<Switch
|
63 |
-
className="ml-auto"
|
64 |
-
checked={contextOptimizationEnabled}
|
65 |
-
onCheckedChange={enableContextOptimization}
|
66 |
-
/>
|
67 |
-
</div>
|
68 |
-
</div>
|
69 |
-
</div>
|
70 |
-
|
71 |
-
<div className="mb-6 border-t border-bolt-elements-borderColor pt-4">
|
72 |
-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">Experimental Features</h3>
|
73 |
-
<p className="text-sm text-bolt-elements-textSecondary mb-10">
|
74 |
-
Disclaimer: Experimental features may be unstable and are subject to change.
|
75 |
-
</p>
|
76 |
-
<div className="flex flex-col">
|
77 |
-
<div className="flex items-center justify-between mb-2">
|
78 |
-
<span className="text-bolt-elements-textPrimary">Experimental Providers</span>
|
79 |
-
<Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
|
80 |
-
</div>
|
81 |
-
<p className="text-xs text-bolt-elements-textTertiary mb-4">
|
82 |
-
Enable experimental providers such as Ollama, LMStudio, and OpenAILike.
|
83 |
-
</p>
|
84 |
-
</div>
|
85 |
-
<div className="flex items-start justify-between pt-4 mb-2 gap-2">
|
86 |
-
<div className="flex-1 max-w-[200px]">
|
87 |
-
<span className="text-bolt-elements-textPrimary">Prompt Library</span>
|
88 |
-
<p className="text-xs text-bolt-elements-textTertiary mb-4">
|
89 |
-
Choose a prompt from the library to use as the system prompt.
|
90 |
-
</p>
|
91 |
-
</div>
|
92 |
-
<select
|
93 |
-
value={promptId}
|
94 |
-
onChange={(e) => setPromptId(e.target.value)}
|
95 |
-
className="flex-1 p-2 ml-auto rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all text-sm min-w-[100px]"
|
96 |
-
>
|
97 |
-
{PromptLibrary.getList().map((x) => (
|
98 |
-
<option key={x.id} value={x.id}>
|
99 |
-
{x.label}
|
100 |
-
</option>
|
101 |
-
))}
|
102 |
-
</select>
|
103 |
-
</div>
|
104 |
-
</div>
|
105 |
-
</div>
|
106 |
-
);
|
107 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/settings/providers/ProvidersTab.tsx
DELETED
@@ -1,147 +0,0 @@
|
|
1 |
-
import React, { useEffect, useState } from 'react';
|
2 |
-
import { Switch } from '~/components/ui/Switch';
|
3 |
-
import { useSettings } from '~/lib/hooks/useSettings';
|
4 |
-
import { LOCAL_PROVIDERS, URL_CONFIGURABLE_PROVIDERS } from '~/lib/stores/settings';
|
5 |
-
import type { IProviderConfig } from '~/types/model';
|
6 |
-
import { logStore } from '~/lib/stores/logs';
|
7 |
-
|
8 |
-
// Import a default fallback icon
|
9 |
-
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
10 |
-
|
11 |
-
const DefaultIcon = '/icons/Default.svg'; // Adjust the path as necessary
|
12 |
-
|
13 |
-
export default function ProvidersTab() {
|
14 |
-
const { providers, updateProviderSettings, isLocalModel } = useSettings();
|
15 |
-
const [filteredProviders, setFilteredProviders] = useState<IProviderConfig[]>([]);
|
16 |
-
|
17 |
-
// Load base URLs from cookies
|
18 |
-
const [searchTerm, setSearchTerm] = useState('');
|
19 |
-
|
20 |
-
useEffect(() => {
|
21 |
-
let newFilteredProviders: IProviderConfig[] = Object.entries(providers).map(([key, value]) => ({
|
22 |
-
...value,
|
23 |
-
name: key,
|
24 |
-
}));
|
25 |
-
|
26 |
-
if (searchTerm && searchTerm.length > 0) {
|
27 |
-
newFilteredProviders = newFilteredProviders.filter((provider) =>
|
28 |
-
provider.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
29 |
-
);
|
30 |
-
}
|
31 |
-
|
32 |
-
if (!isLocalModel) {
|
33 |
-
newFilteredProviders = newFilteredProviders.filter((provider) => !LOCAL_PROVIDERS.includes(provider.name));
|
34 |
-
}
|
35 |
-
|
36 |
-
newFilteredProviders.sort((a, b) => a.name.localeCompare(b.name));
|
37 |
-
|
38 |
-
// Split providers into regular and URL-configurable
|
39 |
-
const regular = newFilteredProviders.filter((p) => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
|
40 |
-
const urlConfigurable = newFilteredProviders.filter((p) => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
|
41 |
-
|
42 |
-
setFilteredProviders([...regular, ...urlConfigurable]);
|
43 |
-
}, [providers, searchTerm, isLocalModel]);
|
44 |
-
|
45 |
-
const renderProviderCard = (provider: IProviderConfig) => {
|
46 |
-
const envBaseUrlKey = providerBaseUrlEnvKeys[provider.name].baseUrlKey;
|
47 |
-
const envBaseUrl = envBaseUrlKey ? import.meta.env[envBaseUrlKey] : undefined;
|
48 |
-
const isUrlConfigurable = URL_CONFIGURABLE_PROVIDERS.includes(provider.name);
|
49 |
-
|
50 |
-
return (
|
51 |
-
<div
|
52 |
-
key={provider.name}
|
53 |
-
className="flex flex-col provider-item hover:bg-bolt-elements-bg-depth-3 p-4 rounded-lg border border-bolt-elements-borderColor"
|
54 |
-
>
|
55 |
-
<div className="flex items-center justify-between mb-2">
|
56 |
-
<div className="flex items-center gap-2">
|
57 |
-
<img
|
58 |
-
src={`/icons/${provider.name}.svg`}
|
59 |
-
onError={(e) => {
|
60 |
-
e.currentTarget.src = DefaultIcon;
|
61 |
-
}}
|
62 |
-
alt={`${provider.name} icon`}
|
63 |
-
className="w-6 h-6 dark:invert"
|
64 |
-
/>
|
65 |
-
<span className="text-bolt-elements-textPrimary">{provider.name}</span>
|
66 |
-
</div>
|
67 |
-
<Switch
|
68 |
-
className="ml-auto"
|
69 |
-
checked={provider.settings.enabled}
|
70 |
-
onCheckedChange={(enabled) => {
|
71 |
-
updateProviderSettings(provider.name, { ...provider.settings, enabled });
|
72 |
-
|
73 |
-
if (enabled) {
|
74 |
-
logStore.logProvider(`Provider ${provider.name} enabled`, { provider: provider.name });
|
75 |
-
} else {
|
76 |
-
logStore.logProvider(`Provider ${provider.name} disabled`, { provider: provider.name });
|
77 |
-
}
|
78 |
-
}}
|
79 |
-
/>
|
80 |
-
</div>
|
81 |
-
{isUrlConfigurable && provider.settings.enabled && (
|
82 |
-
<div className="mt-2">
|
83 |
-
{envBaseUrl && (
|
84 |
-
<label className="block text-xs text-bolt-elements-textSecondary text-green-300 mb-2">
|
85 |
-
Set On (.env) : {envBaseUrl}
|
86 |
-
</label>
|
87 |
-
)}
|
88 |
-
<label className="block text-sm text-bolt-elements-textSecondary mb-2">
|
89 |
-
{envBaseUrl ? 'Override Base Url' : 'Base URL '}:{' '}
|
90 |
-
</label>
|
91 |
-
<input
|
92 |
-
type="text"
|
93 |
-
value={provider.settings.baseUrl || ''}
|
94 |
-
onChange={(e) => {
|
95 |
-
let newBaseUrl: string | undefined = e.target.value;
|
96 |
-
|
97 |
-
if (newBaseUrl && newBaseUrl.trim().length === 0) {
|
98 |
-
newBaseUrl = undefined;
|
99 |
-
}
|
100 |
-
|
101 |
-
updateProviderSettings(provider.name, { ...provider.settings, baseUrl: newBaseUrl });
|
102 |
-
logStore.logProvider(`Base URL updated for ${provider.name}`, {
|
103 |
-
provider: provider.name,
|
104 |
-
baseUrl: newBaseUrl,
|
105 |
-
});
|
106 |
-
}}
|
107 |
-
placeholder={`Enter ${provider.name} base URL`}
|
108 |
-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
109 |
-
/>
|
110 |
-
</div>
|
111 |
-
)}
|
112 |
-
</div>
|
113 |
-
);
|
114 |
-
};
|
115 |
-
|
116 |
-
const regularProviders = filteredProviders.filter((p) => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
|
117 |
-
const urlConfigurableProviders = filteredProviders.filter((p) => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
|
118 |
-
|
119 |
-
return (
|
120 |
-
<div className="p-4">
|
121 |
-
<div className="flex mb-4">
|
122 |
-
<input
|
123 |
-
type="text"
|
124 |
-
placeholder="Search providers..."
|
125 |
-
value={searchTerm}
|
126 |
-
onChange={(e) => setSearchTerm(e.target.value)}
|
127 |
-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
128 |
-
/>
|
129 |
-
</div>
|
130 |
-
|
131 |
-
{/* Regular Providers Grid */}
|
132 |
-
<div className="grid grid-cols-2 gap-4 mb-8">{regularProviders.map(renderProviderCard)}</div>
|
133 |
-
|
134 |
-
{/* URL Configurable Providers Section */}
|
135 |
-
{urlConfigurableProviders.length > 0 && (
|
136 |
-
<div className="mt-8">
|
137 |
-
<h3 className="text-lg font-semibold mb-2 text-bolt-elements-textPrimary">Experimental Providers</h3>
|
138 |
-
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
139 |
-
These providers are experimental and allow you to run AI models locally or connect to your own
|
140 |
-
infrastructure. They require additional setup but offer more flexibility.
|
141 |
-
</p>
|
142 |
-
<div className="space-y-4">{urlConfigurableProviders.map(renderProviderCard)}</div>
|
143 |
-
</div>
|
144 |
-
)}
|
145 |
-
</div>
|
146 |
-
);
|
147 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/lib/hooks/useSettings.tsx
DELETED
@@ -1,229 +0,0 @@
|
|
1 |
-
import { useStore } from '@nanostores/react';
|
2 |
-
import {
|
3 |
-
isDebugMode,
|
4 |
-
isEventLogsEnabled,
|
5 |
-
isLocalModelsEnabled,
|
6 |
-
LOCAL_PROVIDERS,
|
7 |
-
promptStore,
|
8 |
-
providersStore,
|
9 |
-
latestBranchStore,
|
10 |
-
autoSelectStarterTemplate,
|
11 |
-
enableContextOptimizationStore,
|
12 |
-
} from '~/lib/stores/settings';
|
13 |
-
import { useCallback, useEffect, useState } from 'react';
|
14 |
-
import Cookies from 'js-cookie';
|
15 |
-
import type { IProviderSetting, ProviderInfo } from '~/types/model';
|
16 |
-
import { logStore } from '~/lib/stores/logs'; // assuming logStore is imported from this location
|
17 |
-
|
18 |
-
interface CommitData {
|
19 |
-
commit: string;
|
20 |
-
version?: string;
|
21 |
-
}
|
22 |
-
|
23 |
-
const versionData: CommitData = {
|
24 |
-
commit: __COMMIT_HASH,
|
25 |
-
version: __APP_VERSION,
|
26 |
-
};
|
27 |
-
|
28 |
-
export function useSettings() {
|
29 |
-
const providers = useStore(providersStore);
|
30 |
-
const debug = useStore(isDebugMode);
|
31 |
-
const eventLogs = useStore(isEventLogsEnabled);
|
32 |
-
const promptId = useStore(promptStore);
|
33 |
-
const isLocalModel = useStore(isLocalModelsEnabled);
|
34 |
-
const isLatestBranch = useStore(latestBranchStore);
|
35 |
-
const autoSelectTemplate = useStore(autoSelectStarterTemplate);
|
36 |
-
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
|
37 |
-
const contextOptimizationEnabled = useStore(enableContextOptimizationStore);
|
38 |
-
|
39 |
-
// Function to check if we're on stable version
|
40 |
-
const checkIsStableVersion = async () => {
|
41 |
-
try {
|
42 |
-
const response = await fetch(
|
43 |
-
`https://api.github.com/repos/stackblitz-labs/bolt.diy/git/refs/tags/v${versionData.version}`,
|
44 |
-
);
|
45 |
-
const data: { object: { sha: string } } = await response.json();
|
46 |
-
|
47 |
-
return versionData.commit.slice(0, 7) === data.object.sha.slice(0, 7);
|
48 |
-
} catch (error) {
|
49 |
-
console.warn('Error checking stable version:', error);
|
50 |
-
return false;
|
51 |
-
}
|
52 |
-
};
|
53 |
-
|
54 |
-
// reading values from cookies on mount
|
55 |
-
useEffect(() => {
|
56 |
-
const savedProviders = Cookies.get('providers');
|
57 |
-
|
58 |
-
if (savedProviders) {
|
59 |
-
try {
|
60 |
-
const parsedProviders: Record<string, IProviderSetting> = JSON.parse(savedProviders);
|
61 |
-
Object.keys(providers).forEach((provider) => {
|
62 |
-
const currentProviderSettings = parsedProviders[provider];
|
63 |
-
|
64 |
-
if (currentProviderSettings) {
|
65 |
-
providersStore.setKey(provider, {
|
66 |
-
...providers[provider],
|
67 |
-
settings: {
|
68 |
-
...currentProviderSettings,
|
69 |
-
enabled: currentProviderSettings.enabled ?? true,
|
70 |
-
},
|
71 |
-
});
|
72 |
-
}
|
73 |
-
});
|
74 |
-
} catch (error) {
|
75 |
-
console.error('Failed to parse providers from cookies:', error);
|
76 |
-
}
|
77 |
-
}
|
78 |
-
|
79 |
-
// load debug mode from cookies
|
80 |
-
const savedDebugMode = Cookies.get('isDebugEnabled');
|
81 |
-
|
82 |
-
if (savedDebugMode) {
|
83 |
-
isDebugMode.set(savedDebugMode === 'true');
|
84 |
-
}
|
85 |
-
|
86 |
-
// load event logs from cookies
|
87 |
-
const savedEventLogs = Cookies.get('isEventLogsEnabled');
|
88 |
-
|
89 |
-
if (savedEventLogs) {
|
90 |
-
isEventLogsEnabled.set(savedEventLogs === 'true');
|
91 |
-
}
|
92 |
-
|
93 |
-
// load local models from cookies
|
94 |
-
const savedLocalModels = Cookies.get('isLocalModelsEnabled');
|
95 |
-
|
96 |
-
if (savedLocalModels) {
|
97 |
-
isLocalModelsEnabled.set(savedLocalModels === 'true');
|
98 |
-
}
|
99 |
-
|
100 |
-
const promptId = Cookies.get('promptId');
|
101 |
-
|
102 |
-
if (promptId) {
|
103 |
-
promptStore.set(promptId);
|
104 |
-
}
|
105 |
-
|
106 |
-
// load latest branch setting from cookies or determine based on version
|
107 |
-
const savedLatestBranch = Cookies.get('isLatestBranch');
|
108 |
-
let checkCommit = Cookies.get('commitHash');
|
109 |
-
|
110 |
-
if (checkCommit === undefined) {
|
111 |
-
checkCommit = versionData.commit;
|
112 |
-
}
|
113 |
-
|
114 |
-
if (savedLatestBranch === undefined || checkCommit !== versionData.commit) {
|
115 |
-
// If setting hasn't been set by user, check version
|
116 |
-
checkIsStableVersion().then((isStable) => {
|
117 |
-
const shouldUseLatest = !isStable;
|
118 |
-
latestBranchStore.set(shouldUseLatest);
|
119 |
-
Cookies.set('isLatestBranch', String(shouldUseLatest));
|
120 |
-
Cookies.set('commitHash', String(versionData.commit));
|
121 |
-
});
|
122 |
-
} else {
|
123 |
-
latestBranchStore.set(savedLatestBranch === 'true');
|
124 |
-
}
|
125 |
-
|
126 |
-
const autoSelectTemplate = Cookies.get('autoSelectTemplate');
|
127 |
-
|
128 |
-
if (autoSelectTemplate) {
|
129 |
-
autoSelectStarterTemplate.set(autoSelectTemplate === 'true');
|
130 |
-
}
|
131 |
-
|
132 |
-
const savedContextOptimizationEnabled = Cookies.get('contextOptimizationEnabled');
|
133 |
-
|
134 |
-
if (savedContextOptimizationEnabled) {
|
135 |
-
enableContextOptimizationStore.set(savedContextOptimizationEnabled === 'true');
|
136 |
-
}
|
137 |
-
}, []);
|
138 |
-
|
139 |
-
// writing values to cookies on change
|
140 |
-
useEffect(() => {
|
141 |
-
const providers = providersStore.get();
|
142 |
-
const providerSetting: Record<string, IProviderSetting> = {};
|
143 |
-
Object.keys(providers).forEach((provider) => {
|
144 |
-
providerSetting[provider] = providers[provider].settings;
|
145 |
-
});
|
146 |
-
Cookies.set('providers', JSON.stringify(providerSetting));
|
147 |
-
}, [providers]);
|
148 |
-
|
149 |
-
useEffect(() => {
|
150 |
-
let active = Object.entries(providers)
|
151 |
-
.filter(([_key, provider]) => provider.settings.enabled)
|
152 |
-
.map(([_k, p]) => p);
|
153 |
-
|
154 |
-
if (!isLocalModel) {
|
155 |
-
active = active.filter((p) => !LOCAL_PROVIDERS.includes(p.name));
|
156 |
-
}
|
157 |
-
|
158 |
-
setActiveProviders(active);
|
159 |
-
}, [providers, isLocalModel]);
|
160 |
-
|
161 |
-
// helper function to update settings
|
162 |
-
const updateProviderSettings = useCallback(
|
163 |
-
(provider: string, config: IProviderSetting) => {
|
164 |
-
const settings = providers[provider].settings;
|
165 |
-
providersStore.setKey(provider, { ...providers[provider], settings: { ...settings, ...config } });
|
166 |
-
},
|
167 |
-
[providers],
|
168 |
-
);
|
169 |
-
|
170 |
-
const enableDebugMode = useCallback((enabled: boolean) => {
|
171 |
-
isDebugMode.set(enabled);
|
172 |
-
logStore.logSystem(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
173 |
-
Cookies.set('isDebugEnabled', String(enabled));
|
174 |
-
}, []);
|
175 |
-
|
176 |
-
const enableEventLogs = useCallback((enabled: boolean) => {
|
177 |
-
isEventLogsEnabled.set(enabled);
|
178 |
-
logStore.logSystem(`Event logs ${enabled ? 'enabled' : 'disabled'}`);
|
179 |
-
Cookies.set('isEventLogsEnabled', String(enabled));
|
180 |
-
}, []);
|
181 |
-
|
182 |
-
const enableLocalModels = useCallback((enabled: boolean) => {
|
183 |
-
isLocalModelsEnabled.set(enabled);
|
184 |
-
logStore.logSystem(`Local models ${enabled ? 'enabled' : 'disabled'}`);
|
185 |
-
Cookies.set('isLocalModelsEnabled', String(enabled));
|
186 |
-
}, []);
|
187 |
-
|
188 |
-
const setPromptId = useCallback((promptId: string) => {
|
189 |
-
promptStore.set(promptId);
|
190 |
-
Cookies.set('promptId', promptId);
|
191 |
-
}, []);
|
192 |
-
const enableLatestBranch = useCallback((enabled: boolean) => {
|
193 |
-
latestBranchStore.set(enabled);
|
194 |
-
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
195 |
-
Cookies.set('isLatestBranch', String(enabled));
|
196 |
-
}, []);
|
197 |
-
|
198 |
-
const setAutoSelectTemplate = useCallback((enabled: boolean) => {
|
199 |
-
autoSelectStarterTemplate.set(enabled);
|
200 |
-
logStore.logSystem(`Auto select template ${enabled ? 'enabled' : 'disabled'}`);
|
201 |
-
Cookies.set('autoSelectTemplate', String(enabled));
|
202 |
-
}, []);
|
203 |
-
|
204 |
-
const enableContextOptimization = useCallback((enabled: boolean) => {
|
205 |
-
enableContextOptimizationStore.set(enabled);
|
206 |
-
logStore.logSystem(`Context optimization ${enabled ? 'enabled' : 'disabled'}`);
|
207 |
-
Cookies.set('contextOptimizationEnabled', String(enabled));
|
208 |
-
}, []);
|
209 |
-
|
210 |
-
return {
|
211 |
-
providers,
|
212 |
-
activeProviders,
|
213 |
-
updateProviderSettings,
|
214 |
-
debug,
|
215 |
-
enableDebugMode,
|
216 |
-
eventLogs,
|
217 |
-
enableEventLogs,
|
218 |
-
isLocalModel,
|
219 |
-
enableLocalModels,
|
220 |
-
promptId,
|
221 |
-
setPromptId,
|
222 |
-
isLatestBranch,
|
223 |
-
enableLatestBranch,
|
224 |
-
autoSelectTemplate,
|
225 |
-
setAutoSelectTemplate,
|
226 |
-
contextOptimizationEnabled,
|
227 |
-
enableContextOptimization,
|
228 |
-
};
|
229 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker-compose.yaml
CHANGED
@@ -1,41 +1,43 @@
|
|
1 |
services:
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
34 |
|
35 |
app-dev:
|
36 |
image: bolt-ai:development
|
37 |
build:
|
38 |
target: bolt-ai-development
|
|
|
39 |
environment:
|
40 |
- NODE_ENV=development
|
41 |
- VITE_HMR_PROTOCOL=ws
|
@@ -54,11 +56,12 @@ services:
|
|
54 |
- OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL}
|
55 |
- TOGETHER_API_KEY=${TOGETHER_API_KEY}
|
56 |
- TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL}
|
|
|
57 |
- VITE_LOG_LEVEL=${VITE_LOG_LEVEL:-debug}
|
58 |
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
59 |
- RUNNING_IN_DOCKER=true
|
60 |
extra_hosts:
|
61 |
-
-
|
62 |
volumes:
|
63 |
- type: bind
|
64 |
source: .
|
@@ -66,6 +69,24 @@ services:
|
|
66 |
consistency: cached
|
67 |
- /app/node_modules
|
68 |
ports:
|
69 |
-
-
|
70 |
command: pnpm run dev --host 0.0.0.0
|
71 |
-
profiles: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
services:
|
2 |
+
app-prod:
|
3 |
+
image: bolt-ai:production
|
4 |
+
build:
|
5 |
+
context: .
|
6 |
+
dockerfile: Dockerfile
|
7 |
+
target: bolt-ai-production
|
8 |
+
ports:
|
9 |
+
- '5173:5173'
|
10 |
+
env_file: '.env.local'
|
11 |
+
environment:
|
12 |
+
- NODE_ENV=production
|
13 |
+
- COMPOSE_PROFILES=production
|
14 |
+
# No strictly needed but serving as hints for Coolify
|
15 |
+
- PORT=5173
|
16 |
+
- GROQ_API_KEY=${GROQ_API_KEY}
|
17 |
+
- HuggingFace_API_KEY=${HuggingFace_API_KEY}
|
18 |
+
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
19 |
+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
20 |
+
- OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY}
|
21 |
+
- GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY}
|
22 |
+
- OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL}
|
23 |
+
- XAI_API_KEY=${XAI_API_KEY}
|
24 |
+
- TOGETHER_API_KEY=${TOGETHER_API_KEY}
|
25 |
+
- TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL}
|
26 |
+
- AWS_BEDROCK_CONFIG=${AWS_BEDROCK_CONFIG}
|
27 |
+
- VITE_LOG_LEVEL=${VITE_LOG_LEVEL:-debug}
|
28 |
+
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
29 |
+
- RUNNING_IN_DOCKER=true
|
30 |
+
extra_hosts:
|
31 |
+
- 'host.docker.internal:host-gateway'
|
32 |
+
command: pnpm run dockerstart
|
33 |
+
profiles:
|
34 |
+
- production
|
35 |
|
36 |
app-dev:
|
37 |
image: bolt-ai:development
|
38 |
build:
|
39 |
target: bolt-ai-development
|
40 |
+
env_file: '.env.local'
|
41 |
environment:
|
42 |
- NODE_ENV=development
|
43 |
- VITE_HMR_PROTOCOL=ws
|
|
|
56 |
- OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL}
|
57 |
- TOGETHER_API_KEY=${TOGETHER_API_KEY}
|
58 |
- TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL}
|
59 |
+
- AWS_BEDROCK_CONFIG=${AWS_BEDROCK_CONFIG}
|
60 |
- VITE_LOG_LEVEL=${VITE_LOG_LEVEL:-debug}
|
61 |
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
62 |
- RUNNING_IN_DOCKER=true
|
63 |
extra_hosts:
|
64 |
+
- 'host.docker.internal:host-gateway'
|
65 |
volumes:
|
66 |
- type: bind
|
67 |
source: .
|
|
|
69 |
consistency: cached
|
70 |
- /app/node_modules
|
71 |
ports:
|
72 |
+
- '5173:5173'
|
73 |
command: pnpm run dev --host 0.0.0.0
|
74 |
+
profiles: ['development', 'default']
|
75 |
+
|
76 |
+
app-prebuild:
|
77 |
+
image: ghcr.io/stackblitz-labs/bolt.diy:latest
|
78 |
+
ports:
|
79 |
+
- '5173:5173'
|
80 |
+
environment:
|
81 |
+
- NODE_ENV=production
|
82 |
+
- COMPOSE_PROFILES=production
|
83 |
+
# No strictly needed but serving as hints for Coolify
|
84 |
+
- PORT=5173
|
85 |
+
- OLLAMA_API_BASE_URL=http://127.0.0.1:11434
|
86 |
+
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
87 |
+
- RUNNING_IN_DOCKER=true
|
88 |
+
extra_hosts:
|
89 |
+
- 'host.docker.internal:host-gateway'
|
90 |
+
command: pnpm run dockerstart
|
91 |
+
profiles:
|
92 |
+
- prebuilt
|