Merge branch 'main' into main
Browse files- .env.example +11 -1
- .gitignore +1 -0
- .husky/commit-msg +0 -7
- CONTRIBUTING.md +1 -0
- Dockerfile +4 -0
- README.md +14 -7
- app/components/chat/APIKeyManager.tsx +26 -21
- app/components/chat/Artifact.tsx +13 -1
- app/components/chat/BaseChat.tsx +21 -17
- app/components/chat/Messages.client.tsx +111 -32
- app/components/header/HeaderActionButtons.client.tsx +4 -1
- app/components/sidebar/HistoryItem.tsx +10 -2
- app/components/sidebar/Menu.client.tsx +19 -2
- app/components/workbench/Workbench.client.tsx +19 -11
- app/lib/.server/llm/api-key.ts +4 -0
- app/lib/.server/llm/model.ts +25 -0
- app/lib/.server/llm/prompts.ts +2 -2
- app/lib/.server/llm/stream-text.ts +13 -5
- app/lib/hooks/index.ts +1 -0
- app/lib/hooks/usePromptEnhancer.ts +19 -18
- app/lib/hooks/useViewport.ts +18 -0
- app/lib/persistence/db.ts +47 -0
- app/lib/persistence/useChatHistory.ts +22 -3
- app/lib/runtime/action-runner.ts +9 -2
- app/lib/stores/workbench.ts +26 -11
- app/routes/api.enhancer.ts +19 -14
- app/utils/constants.ts +77 -42
- app/utils/diff.spec.ts +11 -0
- app/utils/diff.ts +10 -1
- app/utils/types.ts +1 -0
- docker-compose.yaml +2 -0
- eslint.config.mjs +1 -1
- package.json +2 -0
- pnpm-lock.yaml +192 -0
- worker-configuration.d.ts +1 -0
.env.example
CHANGED
@@ -5,6 +5,12 @@
|
|
5 |
# You only need this environment variable set if you want to use Groq models
|
6 |
GROQ_API_KEY=
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
# Get your Open AI API Key by following these instructions -
|
9 |
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
|
10 |
# You only need this environment variable set if you want to use GPT models
|
@@ -43,6 +49,10 @@ OPENAI_LIKE_API_KEY=
|
|
43 |
# You only need this environment variable set if you want to use Mistral models
|
44 |
MISTRAL_API_KEY=
|
45 |
|
|
|
|
|
|
|
|
|
46 |
|
47 |
# Get LMStudio Base URL from LM Studio Developer Console
|
48 |
# Make sure to enable CORS
|
@@ -63,4 +73,4 @@ VITE_LOG_LEVEL=debug
|
|
63 |
# DEFAULT_NUM_CTX=24576 # Consumes 32GB of VRAM
|
64 |
# DEFAULT_NUM_CTX=12288 # Consumes 26GB of VRAM
|
65 |
# DEFAULT_NUM_CTX=6144 # Consumes 24GB of VRAM
|
66 |
-
DEFAULT_NUM_CTX=
|
|
|
5 |
# You only need this environment variable set if you want to use Groq models
|
6 |
GROQ_API_KEY=
|
7 |
|
8 |
+
# Get your HuggingFace API Key here -
|
9 |
+
# https://huggingface.co/settings/tokens
|
10 |
+
# You only need this environment variable set if you want to use HuggingFace models
|
11 |
+
HuggingFace_API_KEY=
|
12 |
+
|
13 |
+
|
14 |
# Get your Open AI API Key by following these instructions -
|
15 |
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
|
16 |
# You only need this environment variable set if you want to use GPT models
|
|
|
49 |
# You only need this environment variable set if you want to use Mistral models
|
50 |
MISTRAL_API_KEY=
|
51 |
|
52 |
+
# Get the Cohere Api key by following these instructions -
|
53 |
+
# https://dashboard.cohere.com/api-keys
|
54 |
+
# You only need this environment variable set if you want to use Cohere models
|
55 |
+
COHERE_API_KEY=
|
56 |
|
57 |
# Get LMStudio Base URL from LM Studio Developer Console
|
58 |
# Make sure to enable CORS
|
|
|
73 |
# DEFAULT_NUM_CTX=24576 # Consumes 32GB of VRAM
|
74 |
# DEFAULT_NUM_CTX=12288 # Consumes 26GB of VRAM
|
75 |
# DEFAULT_NUM_CTX=6144 # Consumes 24GB of VRAM
|
76 |
+
DEFAULT_NUM_CTX=
|
.gitignore
CHANGED
@@ -22,6 +22,7 @@ dist-ssr
|
|
22 |
*.sln
|
23 |
*.sw?
|
24 |
|
|
|
25 |
/.cache
|
26 |
/build
|
27 |
.env.local
|
|
|
22 |
*.sln
|
23 |
*.sw?
|
24 |
|
25 |
+
/.history
|
26 |
/.cache
|
27 |
/build
|
28 |
.env.local
|
.husky/commit-msg
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
#!/usr/bin/env sh
|
2 |
-
|
3 |
-
. "$(dirname "$0")/_/husky.sh"
|
4 |
-
|
5 |
-
npx commitlint --edit $1
|
6 |
-
|
7 |
-
exit 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CONTRIBUTING.md
CHANGED
@@ -75,6 +75,7 @@ pnpm install
|
|
75 |
- Add your LLM API keys (only set the ones you plan to use):
|
76 |
```bash
|
77 |
GROQ_API_KEY=XXX
|
|
|
78 |
OPENAI_API_KEY=XXX
|
79 |
ANTHROPIC_API_KEY=XXX
|
80 |
...
|
|
|
75 |
- Add your LLM API keys (only set the ones you plan to use):
|
76 |
```bash
|
77 |
GROQ_API_KEY=XXX
|
78 |
+
HuggingFace_API_KEY=XXX
|
79 |
OPENAI_API_KEY=XXX
|
80 |
ANTHROPIC_API_KEY=XXX
|
81 |
...
|
Dockerfile
CHANGED
@@ -19,6 +19,7 @@ FROM base AS bolt-ai-production
|
|
19 |
|
20 |
# Define environment variables with default values or let them be overridden
|
21 |
ARG GROQ_API_KEY
|
|
|
22 |
ARG OPENAI_API_KEY
|
23 |
ARG ANTHROPIC_API_KEY
|
24 |
ARG OPEN_ROUTER_API_KEY
|
@@ -29,6 +30,7 @@ ARG DEFAULT_NUM_CTX
|
|
29 |
|
30 |
ENV WRANGLER_SEND_METRICS=false \
|
31 |
GROQ_API_KEY=${GROQ_API_KEY} \
|
|
|
32 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
33 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
34 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
@@ -50,6 +52,7 @@ FROM base AS bolt-ai-development
|
|
50 |
|
51 |
# Define the same environment variables for development
|
52 |
ARG GROQ_API_KEY
|
|
|
53 |
ARG OPENAI_API_KEY
|
54 |
ARG ANTHROPIC_API_KEY
|
55 |
ARG OPEN_ROUTER_API_KEY
|
@@ -59,6 +62,7 @@ ARG VITE_LOG_LEVEL=debug
|
|
59 |
ARG DEFAULT_NUM_CTX
|
60 |
|
61 |
ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
|
|
62 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
63 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
64 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
|
|
19 |
|
20 |
# Define environment variables with default values or let them be overridden
|
21 |
ARG GROQ_API_KEY
|
22 |
+
ARG HuggingFace_API_KEY
|
23 |
ARG OPENAI_API_KEY
|
24 |
ARG ANTHROPIC_API_KEY
|
25 |
ARG OPEN_ROUTER_API_KEY
|
|
|
30 |
|
31 |
ENV WRANGLER_SEND_METRICS=false \
|
32 |
GROQ_API_KEY=${GROQ_API_KEY} \
|
33 |
+
HuggingFace_KEY=${HuggingFace_API_KEY} \
|
34 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
35 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
36 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
|
|
52 |
|
53 |
# Define the same environment variables for development
|
54 |
ARG GROQ_API_KEY
|
55 |
+
ARG HuggingFace
|
56 |
ARG OPENAI_API_KEY
|
57 |
ARG ANTHROPIC_API_KEY
|
58 |
ARG OPEN_ROUTER_API_KEY
|
|
|
62 |
ARG DEFAULT_NUM_CTX
|
63 |
|
64 |
ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
65 |
+
HuggingFace_API_KEY=${HuggingFace_API_KEY} \
|
66 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
67 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
68 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
README.md
CHANGED
@@ -1,8 +1,12 @@
|
|
1 |
[](https://bolt.new)
|
2 |
|
3 |
-
# Bolt.new Fork by Cole Medin
|
4 |
|
5 |
-
This fork of Bolt.new allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
|
|
|
|
|
|
|
|
|
6 |
|
7 |
# Requested Additions to this Fork - Feel Free to Contribute!!
|
8 |
|
@@ -20,21 +24,24 @@ This fork of Bolt.new allows you to choose the LLM that you use for each prompt!
|
|
20 |
- ✅ Publish projects directly to GitHub (@goncaloalves)
|
21 |
- ✅ Ability to enter API keys in the UI (@ali00209)
|
22 |
- ✅ xAI Grok Beta Integration (@milutinke)
|
|
|
|
|
|
|
|
|
|
|
23 |
- ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
|
24 |
- ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
|
25 |
-
- ⬜ **HIGH PRIORITY** Load local projects into the app
|
26 |
- ⬜ **HIGH PRIORITY** - Attach images to prompts
|
27 |
- ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call
|
28 |
- ⬜ Mobile friendly
|
29 |
-
- ⬜ LM Studio Integration
|
30 |
- ⬜ Together Integration
|
31 |
- ⬜ Azure Open AI API Integration
|
32 |
-
- ⬜ HuggingFace Integration
|
33 |
- ⬜ Perplexity Integration
|
34 |
- ⬜ Vertex AI Integration
|
35 |
-
-
|
|
|
36 |
- ⬜ Deploy directly to Vercel/Netlify/other similar platforms
|
37 |
-
- ⬜ Ability to revert code to earlier version
|
38 |
- ⬜ Prompt caching
|
39 |
- ⬜ Better prompt enhancing
|
40 |
- ⬜ Have LLM plan the project in a MD file for better results/transparency
|
|
|
1 |
[](https://bolt.new)
|
2 |
|
3 |
+
# Bolt.new Fork by Cole Medin - oTToDev
|
4 |
|
5 |
+
This fork of Bolt.new (oTToDev) allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
|
6 |
+
|
7 |
+
Join the community for oTToDev!
|
8 |
+
|
9 |
+
https://thinktank.ottomator.ai
|
10 |
|
11 |
# Requested Additions to this Fork - Feel Free to Contribute!!
|
12 |
|
|
|
24 |
- ✅ Publish projects directly to GitHub (@goncaloalves)
|
25 |
- ✅ Ability to enter API keys in the UI (@ali00209)
|
26 |
- ✅ xAI Grok Beta Integration (@milutinke)
|
27 |
+
- ✅ LM Studio Integration (@karrot0)
|
28 |
+
- ✅ HuggingFace Integration (@ahsan3219)
|
29 |
+
- ✅ Bolt terminal to see the output of LLM run commands (@thecodacus)
|
30 |
+
- ✅ Streaming of code output (@thecodacus)
|
31 |
+
- ✅ Ability to revert code to earlier version (@wonderwhy-er)
|
32 |
- ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
|
33 |
- ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
|
34 |
+
- ⬜ **HIGH PRIORITY** - Load local projects into the app
|
35 |
- ⬜ **HIGH PRIORITY** - Attach images to prompts
|
36 |
- ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call
|
37 |
- ⬜ Mobile friendly
|
|
|
38 |
- ⬜ Together Integration
|
39 |
- ⬜ Azure Open AI API Integration
|
|
|
40 |
- ⬜ Perplexity Integration
|
41 |
- ⬜ Vertex AI Integration
|
42 |
+
- ✅ Cohere Integration (@hasanraiyan)
|
43 |
+
- ✅ Dynamic model max token length (@hasanraiyan)
|
44 |
- ⬜ Deploy directly to Vercel/Netlify/other similar platforms
|
|
|
45 |
- ⬜ Prompt caching
|
46 |
- ⬜ Better prompt enhancing
|
47 |
- ⬜ Have LLM plan the project in a MD file for better results/transparency
|
app/components/chat/APIKeyManager.tsx
CHANGED
@@ -10,11 +10,7 @@ interface APIKeyManagerProps {
|
|
10 |
labelForGetApiKey?: string;
|
11 |
}
|
12 |
|
13 |
-
export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
|
14 |
-
provider,
|
15 |
-
apiKey,
|
16 |
-
setApiKey,
|
17 |
-
}) => {
|
18 |
const [isEditing, setIsEditing] = useState(false);
|
19 |
const [tempKey, setTempKey] = useState(apiKey);
|
20 |
|
@@ -24,15 +20,29 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
|
|
24 |
};
|
25 |
|
26 |
return (
|
27 |
-
<div className="flex items-
|
28 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
{isEditing ? (
|
30 |
-
|
31 |
<input
|
32 |
type="password"
|
33 |
value={tempKey}
|
|
|
34 |
onChange={(e) => setTempKey(e.target.value)}
|
35 |
-
className="flex-1
|
36 |
/>
|
37 |
<IconButton onClick={handleSave} title="Save API Key">
|
38 |
<div className="i-ph:check" />
|
@@ -40,20 +50,15 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
|
|
40 |
<IconButton onClick={() => setIsEditing(false)} title="Cancel">
|
41 |
<div className="i-ph:x" />
|
42 |
</IconButton>
|
43 |
-
|
44 |
) : (
|
45 |
<>
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
{provider?.getApiKeyLink && <IconButton onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
|
54 |
-
<span className="mr-2">{provider?.labelForGetApiKey || 'Get API Key'}</span>
|
55 |
-
<div className={provider?.icon || "i-ph:key"} />
|
56 |
-
</IconButton>}
|
57 |
</>
|
58 |
)}
|
59 |
</div>
|
|
|
10 |
labelForGetApiKey?: string;
|
11 |
}
|
12 |
|
13 |
+
export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
|
|
|
|
|
|
|
|
|
14 |
const [isEditing, setIsEditing] = useState(false);
|
15 |
const [tempKey, setTempKey] = useState(apiKey);
|
16 |
|
|
|
20 |
};
|
21 |
|
22 |
return (
|
23 |
+
<div className="flex items-start sm:items-center mt-2 mb-2 flex-col sm:flex-row">
|
24 |
+
<div>
|
25 |
+
<span className="text-sm text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
|
26 |
+
{!isEditing && (
|
27 |
+
<div className="flex items-center mb-4">
|
28 |
+
<span className="flex-1 text-xs text-bolt-elements-textPrimary mr-2">
|
29 |
+
{apiKey ? '••••••••' : 'Not set (will still work if set in .env file)'}
|
30 |
+
</span>
|
31 |
+
<IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
|
32 |
+
<div className="i-ph:pencil-simple" />
|
33 |
+
</IconButton>
|
34 |
+
</div>
|
35 |
+
)}
|
36 |
+
</div>
|
37 |
+
|
38 |
{isEditing ? (
|
39 |
+
<div className="flex items-center gap-3 mt-2">
|
40 |
<input
|
41 |
type="password"
|
42 |
value={tempKey}
|
43 |
+
placeholder="Your API Key"
|
44 |
onChange={(e) => setTempKey(e.target.value)}
|
45 |
+
className="flex-1 px-2 py-1 text-xs lg:text-sm rounded 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"
|
46 |
/>
|
47 |
<IconButton onClick={handleSave} title="Save API Key">
|
48 |
<div className="i-ph:check" />
|
|
|
50 |
<IconButton onClick={() => setIsEditing(false)} title="Cancel">
|
51 |
<div className="i-ph:x" />
|
52 |
</IconButton>
|
53 |
+
</div>
|
54 |
) : (
|
55 |
<>
|
56 |
+
{provider?.getApiKeyLink && (
|
57 |
+
<IconButton className="ml-auto" onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
|
58 |
+
<span className="mr-2 text-xs lg:text-sm">{provider?.labelForGetApiKey || 'Get API Key'}</span>
|
59 |
+
<div className={provider?.icon || 'i-ph:key'} />
|
60 |
+
</IconButton>
|
61 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
62 |
</>
|
63 |
)}
|
64 |
</div>
|
app/components/chat/Artifact.tsx
CHANGED
@@ -7,6 +7,7 @@ import type { ActionState } from '~/lib/runtime/action-runner';
|
|
7 |
import { workbenchStore } from '~/lib/stores/workbench';
|
8 |
import { classNames } from '~/utils/classNames';
|
9 |
import { cubicEasingFn } from '~/utils/easings';
|
|
|
10 |
|
11 |
const highlighterOptions = {
|
12 |
langs: ['shell'],
|
@@ -129,6 +130,14 @@ const actionVariants = {
|
|
129 |
visible: { opacity: 1, y: 0 },
|
130 |
};
|
131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
const ActionList = memo(({ actions }: ActionListProps) => {
|
133 |
return (
|
134 |
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
|
@@ -169,7 +178,10 @@ const ActionList = memo(({ actions }: ActionListProps) => {
|
|
169 |
{type === 'file' ? (
|
170 |
<div>
|
171 |
Create{' '}
|
172 |
-
<code
|
|
|
|
|
|
|
173 |
{action.filePath}
|
174 |
</code>
|
175 |
</div>
|
|
|
7 |
import { workbenchStore } from '~/lib/stores/workbench';
|
8 |
import { classNames } from '~/utils/classNames';
|
9 |
import { cubicEasingFn } from '~/utils/easings';
|
10 |
+
import { WORK_DIR } from '~/utils/constants';
|
11 |
|
12 |
const highlighterOptions = {
|
13 |
langs: ['shell'],
|
|
|
130 |
visible: { opacity: 1, y: 0 },
|
131 |
};
|
132 |
|
133 |
+
function openArtifactInWorkbench(filePath: any) {
|
134 |
+
if (workbenchStore.currentView.get() !== 'code') {
|
135 |
+
workbenchStore.currentView.set('code');
|
136 |
+
}
|
137 |
+
|
138 |
+
workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);
|
139 |
+
}
|
140 |
+
|
141 |
const ActionList = memo(({ actions }: ActionListProps) => {
|
142 |
return (
|
143 |
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
|
|
|
178 |
{type === 'file' ? (
|
179 |
<div>
|
180 |
Create{' '}
|
181 |
+
<code
|
182 |
+
className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md text-bolt-elements-item-contentAccent hover:underline cursor-pointer"
|
183 |
+
onClick={() => openArtifactInWorkbench(action.filePath)}
|
184 |
+
>
|
185 |
{action.filePath}
|
186 |
</code>
|
187 |
</div>
|
app/components/chat/BaseChat.tsx
CHANGED
@@ -27,9 +27,9 @@ const EXAMPLE_PROMPTS = [
|
|
27 |
|
28 |
const providerList = PROVIDER_LIST;
|
29 |
|
30 |
-
const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList }) => {
|
31 |
return (
|
32 |
-
<div className="mb-2 flex gap-2">
|
33 |
<select
|
34 |
value={provider?.name}
|
35 |
onChange={(e) => {
|
@@ -49,8 +49,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
|
|
49 |
key={provider?.name}
|
50 |
value={model}
|
51 |
onChange={(e) => setModel(e.target.value)}
|
52 |
-
|
53 |
-
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"
|
54 |
>
|
55 |
{[...modelList]
|
56 |
.filter((e) => e.provider == provider?.name && e.name)
|
@@ -157,25 +156,25 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
157 |
ref={ref}
|
158 |
className={classNames(
|
159 |
styles.BaseChat,
|
160 |
-
'relative flex h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
|
161 |
)}
|
162 |
data-chat-visible={showChat}
|
163 |
>
|
164 |
<ClientOnly>{() => <Menu />}</ClientOnly>
|
165 |
-
<div ref={scrollRef} className="flex overflow-y-auto w-full h-full">
|
166 |
-
<div className={classNames(styles.Chat, 'flex flex-col flex-grow min-w-[var(--chat-min-width)] h-full')}>
|
167 |
{!chatStarted && (
|
168 |
-
<div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center">
|
169 |
-
<h1 className="text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
|
170 |
Where ideas begin
|
171 |
</h1>
|
172 |
-
<p className="text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
|
173 |
Bring ideas to life in seconds or get help on existing projects.
|
174 |
</p>
|
175 |
</div>
|
176 |
)}
|
177 |
<div
|
178 |
-
className={classNames('pt-6 px-6', {
|
179 |
'h-full flex flex-col': chatStarted,
|
180 |
})}
|
181 |
>
|
@@ -184,7 +183,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
184 |
return chatStarted ? (
|
185 |
<Messages
|
186 |
ref={messageRef}
|
187 |
-
className="flex flex-col w-full flex-1 max-w-chat
|
188 |
messages={messages}
|
189 |
isStreaming={isStreaming}
|
190 |
/>
|
@@ -192,9 +191,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
192 |
}}
|
193 |
</ClientOnly>
|
194 |
<div
|
195 |
-
className={classNames(
|
196 |
-
'
|
197 |
-
|
|
|
|
|
|
|
198 |
>
|
199 |
<ModelSelector
|
200 |
key={provider?.name + ':' + modelList.length}
|
@@ -204,7 +206,9 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
204 |
provider={provider}
|
205 |
setProvider={setProvider}
|
206 |
providerList={PROVIDER_LIST}
|
|
|
207 |
/>
|
|
|
208 |
{provider && (
|
209 |
<APIKeyManager
|
210 |
provider={provider}
|
@@ -212,6 +216,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
212 |
setApiKey={(key) => updateApiKey(provider.name, key)}
|
213 |
/>
|
214 |
)}
|
|
|
215 |
<div
|
216 |
className={classNames(
|
217 |
'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
|
@@ -219,7 +224,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
219 |
>
|
220 |
<textarea
|
221 |
ref={textareaRef}
|
222 |
-
className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-
|
223 |
onKeyDown={(event) => {
|
224 |
if (event.key === 'Enter') {
|
225 |
if (event.shiftKey) {
|
@@ -292,7 +297,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
292 |
) : null}
|
293 |
</div>
|
294 |
</div>
|
295 |
-
<div className="bg-bolt-elements-background-depth-1 pb-6">{/* Ghost Element */}</div>
|
296 |
</div>
|
297 |
</div>
|
298 |
{!chatStarted && (
|
|
|
27 |
|
28 |
const providerList = PROVIDER_LIST;
|
29 |
|
30 |
+
const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList, apiKeys }) => {
|
31 |
return (
|
32 |
+
<div className="mb-2 flex gap-2 flex-col sm:flex-row">
|
33 |
<select
|
34 |
value={provider?.name}
|
35 |
onChange={(e) => {
|
|
|
49 |
key={provider?.name}
|
50 |
value={model}
|
51 |
onChange={(e) => setModel(e.target.value)}
|
52 |
+
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-[70%] "
|
|
|
53 |
>
|
54 |
{[...modelList]
|
55 |
.filter((e) => e.provider == provider?.name && e.name)
|
|
|
156 |
ref={ref}
|
157 |
className={classNames(
|
158 |
styles.BaseChat,
|
159 |
+
'relative flex flex-col lg:flex-row h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
|
160 |
)}
|
161 |
data-chat-visible={showChat}
|
162 |
>
|
163 |
<ClientOnly>{() => <Menu />}</ClientOnly>
|
164 |
+
<div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
|
165 |
+
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
|
166 |
{!chatStarted && (
|
167 |
+
<div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center px-4 lg:px-0">
|
168 |
+
<h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
|
169 |
Where ideas begin
|
170 |
</h1>
|
171 |
+
<p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
|
172 |
Bring ideas to life in seconds or get help on existing projects.
|
173 |
</p>
|
174 |
</div>
|
175 |
)}
|
176 |
<div
|
177 |
+
className={classNames('pt-6 px-2 sm:px-6', {
|
178 |
'h-full flex flex-col': chatStarted,
|
179 |
})}
|
180 |
>
|
|
|
183 |
return chatStarted ? (
|
184 |
<Messages
|
185 |
ref={messageRef}
|
186 |
+
className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
|
187 |
messages={messages}
|
188 |
isStreaming={isStreaming}
|
189 |
/>
|
|
|
191 |
}}
|
192 |
</ClientOnly>
|
193 |
<div
|
194 |
+
className={classNames(
|
195 |
+
' bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt mb-6',
|
196 |
+
{
|
197 |
+
'sticky bottom-2': chatStarted,
|
198 |
+
},
|
199 |
+
)}
|
200 |
>
|
201 |
<ModelSelector
|
202 |
key={provider?.name + ':' + modelList.length}
|
|
|
206 |
provider={provider}
|
207 |
setProvider={setProvider}
|
208 |
providerList={PROVIDER_LIST}
|
209 |
+
apiKeys={apiKeys}
|
210 |
/>
|
211 |
+
|
212 |
{provider && (
|
213 |
<APIKeyManager
|
214 |
provider={provider}
|
|
|
216 |
setApiKey={(key) => updateApiKey(provider.name, key)}
|
217 |
/>
|
218 |
)}
|
219 |
+
|
220 |
<div
|
221 |
className={classNames(
|
222 |
'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
|
|
|
224 |
>
|
225 |
<textarea
|
226 |
ref={textareaRef}
|
227 |
+
className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-0 focus:border-none focus:shadow-none resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
|
228 |
onKeyDown={(event) => {
|
229 |
if (event.key === 'Enter') {
|
230 |
if (event.shiftKey) {
|
|
|
297 |
) : null}
|
298 |
</div>
|
299 |
</div>
|
|
|
300 |
</div>
|
301 |
</div>
|
302 |
{!chatStarted && (
|
app/components/chat/Messages.client.tsx
CHANGED
@@ -3,6 +3,11 @@ import React from 'react';
|
|
3 |
import { classNames } from '~/utils/classNames';
|
4 |
import { AssistantMessage } from './AssistantMessage';
|
5 |
import { UserMessage } from './UserMessage';
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
interface MessagesProps {
|
8 |
id?: string;
|
@@ -13,41 +18,115 @@ interface MessagesProps {
|
|
13 |
|
14 |
export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props: MessagesProps, ref) => {
|
15 |
const { id, isStreaming = false, messages = [] } = props;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
return (
|
18 |
-
<
|
19 |
-
{
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
'
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
<div className="
|
|
|
|
|
|
|
|
|
|
|
39 |
</div>
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
</div>
|
44 |
-
|
45 |
-
)
|
46 |
-
}
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
</
|
52 |
);
|
53 |
});
|
|
|
3 |
import { classNames } from '~/utils/classNames';
|
4 |
import { AssistantMessage } from './AssistantMessage';
|
5 |
import { UserMessage } from './UserMessage';
|
6 |
+
import * as Tooltip from '@radix-ui/react-tooltip';
|
7 |
+
import { useLocation } from '@remix-run/react';
|
8 |
+
import { db, chatId } from '~/lib/persistence/useChatHistory';
|
9 |
+
import { forkChat } from '~/lib/persistence/db';
|
10 |
+
import { toast } from 'react-toastify';
|
11 |
|
12 |
interface MessagesProps {
|
13 |
id?: string;
|
|
|
18 |
|
19 |
export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props: MessagesProps, ref) => {
|
20 |
const { id, isStreaming = false, messages = [] } = props;
|
21 |
+
const location = useLocation();
|
22 |
+
|
23 |
+
const handleRewind = (messageId: string) => {
|
24 |
+
const searchParams = new URLSearchParams(location.search);
|
25 |
+
searchParams.set('rewindTo', messageId);
|
26 |
+
window.location.search = searchParams.toString();
|
27 |
+
};
|
28 |
+
|
29 |
+
const handleFork = async (messageId: string) => {
|
30 |
+
try {
|
31 |
+
if (!db || !chatId.get()) {
|
32 |
+
toast.error('Chat persistence is not available');
|
33 |
+
return;
|
34 |
+
}
|
35 |
+
|
36 |
+
const urlId = await forkChat(db, chatId.get()!, messageId);
|
37 |
+
window.location.href = `/chat/${urlId}`;
|
38 |
+
} catch (error) {
|
39 |
+
toast.error('Failed to fork chat: ' + (error as Error).message);
|
40 |
+
}
|
41 |
+
};
|
42 |
|
43 |
return (
|
44 |
+
<Tooltip.Provider delayDuration={200}>
|
45 |
+
<div id={id} ref={ref} className={props.className}>
|
46 |
+
{messages.length > 0
|
47 |
+
? messages.map((message, index) => {
|
48 |
+
const { role, content, id: messageId } = message;
|
49 |
+
const isUserMessage = role === 'user';
|
50 |
+
const isFirst = index === 0;
|
51 |
+
const isLast = index === messages.length - 1;
|
52 |
+
|
53 |
+
return (
|
54 |
+
<div
|
55 |
+
key={index}
|
56 |
+
className={classNames('flex gap-4 p-6 w-full rounded-[calc(0.75rem-1px)]', {
|
57 |
+
'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),
|
58 |
+
'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':
|
59 |
+
isStreaming && isLast,
|
60 |
+
'mt-4': !isFirst,
|
61 |
+
})}
|
62 |
+
>
|
63 |
+
{isUserMessage && (
|
64 |
+
<div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">
|
65 |
+
<div className="i-ph:user-fill text-xl"></div>
|
66 |
+
</div>
|
67 |
+
)}
|
68 |
+
<div className="grid grid-col-1 w-full">
|
69 |
+
{isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
|
70 |
</div>
|
71 |
+
{!isUserMessage && (
|
72 |
+
<div className="flex gap-2 flex-col lg:flex-row">
|
73 |
+
<Tooltip.Root>
|
74 |
+
<Tooltip.Trigger asChild>
|
75 |
+
{messageId && (
|
76 |
+
<button
|
77 |
+
onClick={() => handleRewind(messageId)}
|
78 |
+
key="i-ph:arrow-u-up-left"
|
79 |
+
className={classNames(
|
80 |
+
'i-ph:arrow-u-up-left',
|
81 |
+
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
|
82 |
+
)}
|
83 |
+
/>
|
84 |
+
)}
|
85 |
+
</Tooltip.Trigger>
|
86 |
+
<Tooltip.Portal>
|
87 |
+
<Tooltip.Content
|
88 |
+
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
|
89 |
+
sideOffset={5}
|
90 |
+
style={{ zIndex: 1000 }}
|
91 |
+
>
|
92 |
+
Revert to this message
|
93 |
+
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
|
94 |
+
</Tooltip.Content>
|
95 |
+
</Tooltip.Portal>
|
96 |
+
</Tooltip.Root>
|
97 |
+
|
98 |
+
<Tooltip.Root>
|
99 |
+
<Tooltip.Trigger asChild>
|
100 |
+
<button
|
101 |
+
onClick={() => handleFork(messageId)}
|
102 |
+
key="i-ph:git-fork"
|
103 |
+
className={classNames(
|
104 |
+
'i-ph:git-fork',
|
105 |
+
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
|
106 |
+
)}
|
107 |
+
/>
|
108 |
+
</Tooltip.Trigger>
|
109 |
+
<Tooltip.Portal>
|
110 |
+
<Tooltip.Content
|
111 |
+
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
|
112 |
+
sideOffset={5}
|
113 |
+
style={{ zIndex: 1000 }}
|
114 |
+
>
|
115 |
+
Fork chat from this message
|
116 |
+
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
|
117 |
+
</Tooltip.Content>
|
118 |
+
</Tooltip.Portal>
|
119 |
+
</Tooltip.Root>
|
120 |
+
</div>
|
121 |
+
)}
|
122 |
</div>
|
123 |
+
);
|
124 |
+
})
|
125 |
+
: null}
|
126 |
+
{isStreaming && (
|
127 |
+
<div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div>
|
128 |
+
)}
|
129 |
+
</div>
|
130 |
+
</Tooltip.Provider>
|
131 |
);
|
132 |
});
|
app/components/header/HeaderActionButtons.client.tsx
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import { useStore } from '@nanostores/react';
|
|
|
2 |
import { chatStore } from '~/lib/stores/chat';
|
3 |
import { workbenchStore } from '~/lib/stores/workbench';
|
4 |
import { classNames } from '~/utils/classNames';
|
@@ -9,6 +10,8 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
|
|
9 |
const showWorkbench = useStore(workbenchStore.showWorkbench);
|
10 |
const { showChat } = useStore(chatStore);
|
11 |
|
|
|
|
|
12 |
const canHideChat = showWorkbench || !showChat;
|
13 |
|
14 |
return (
|
@@ -16,7 +19,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
|
|
16 |
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
|
17 |
<Button
|
18 |
active={showChat}
|
19 |
-
disabled={!canHideChat}
|
20 |
onClick={() => {
|
21 |
if (canHideChat) {
|
22 |
chatStore.setKey('showChat', !showChat);
|
|
|
1 |
import { useStore } from '@nanostores/react';
|
2 |
+
import useViewport from '~/lib/hooks';
|
3 |
import { chatStore } from '~/lib/stores/chat';
|
4 |
import { workbenchStore } from '~/lib/stores/workbench';
|
5 |
import { classNames } from '~/utils/classNames';
|
|
|
10 |
const showWorkbench = useStore(workbenchStore.showWorkbench);
|
11 |
const { showChat } = useStore(chatStore);
|
12 |
|
13 |
+
const isSmallViewport = useViewport(1024);
|
14 |
+
|
15 |
const canHideChat = showWorkbench || !showChat;
|
16 |
|
17 |
return (
|
|
|
19 |
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
|
20 |
<Button
|
21 |
active={showChat}
|
22 |
+
disabled={!canHideChat || isSmallViewport} // expand button is disabled on mobile as it's needed
|
23 |
onClick={() => {
|
24 |
if (canHideChat) {
|
25 |
chatStore.setKey('showChat', !showChat);
|
app/components/sidebar/HistoryItem.tsx
CHANGED
@@ -5,9 +5,10 @@ import { type ChatHistoryItem } from '~/lib/persistence';
|
|
5 |
interface HistoryItemProps {
|
6 |
item: ChatHistoryItem;
|
7 |
onDelete?: (event: React.UIEvent) => void;
|
|
|
8 |
}
|
9 |
|
10 |
-
export function HistoryItem({ item, onDelete }: HistoryItemProps) {
|
11 |
const [hovering, setHovering] = useState(false);
|
12 |
const hoverRef = useRef<HTMLDivElement>(null);
|
13 |
|
@@ -44,7 +45,14 @@ export function HistoryItem({ item, onDelete }: HistoryItemProps) {
|
|
44 |
{item.description}
|
45 |
<div className="absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 to-transparent w-10 flex justify-end group-hover:w-15 group-hover:from-45%">
|
46 |
{hovering && (
|
47 |
-
<div className="flex items-center p-1 text-bolt-elements-textSecondary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
<Dialog.Trigger asChild>
|
49 |
<button
|
50 |
className="i-ph:trash scale-110"
|
|
|
5 |
interface HistoryItemProps {
|
6 |
item: ChatHistoryItem;
|
7 |
onDelete?: (event: React.UIEvent) => void;
|
8 |
+
onDuplicate?: (id: string) => void;
|
9 |
}
|
10 |
|
11 |
+
export function HistoryItem({ item, onDelete, onDuplicate }: HistoryItemProps) {
|
12 |
const [hovering, setHovering] = useState(false);
|
13 |
const hoverRef = useRef<HTMLDivElement>(null);
|
14 |
|
|
|
45 |
{item.description}
|
46 |
<div className="absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 to-transparent w-10 flex justify-end group-hover:w-15 group-hover:from-45%">
|
47 |
{hovering && (
|
48 |
+
<div className="flex items-center p-1 text-bolt-elements-textSecondary">
|
49 |
+
{onDuplicate && (
|
50 |
+
<button
|
51 |
+
className="i-ph:copy scale-110 mr-2"
|
52 |
+
onClick={() => onDuplicate?.(item.id)}
|
53 |
+
title="Duplicate chat"
|
54 |
+
/>
|
55 |
+
)}
|
56 |
<Dialog.Trigger asChild>
|
57 |
<button
|
58 |
className="i-ph:trash scale-110"
|
app/components/sidebar/Menu.client.tsx
CHANGED
@@ -4,7 +4,7 @@ import { toast } from 'react-toastify';
|
|
4 |
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
|
5 |
import { IconButton } from '~/components/ui/IconButton';
|
6 |
import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
|
7 |
-
import { db, deleteById, getAll, chatId, type ChatHistoryItem } from '~/lib/persistence';
|
8 |
import { cubicEasingFn } from '~/utils/easings';
|
9 |
import { logger } from '~/utils/logger';
|
10 |
import { HistoryItem } from './HistoryItem';
|
@@ -34,6 +34,7 @@ const menuVariants = {
|
|
34 |
type DialogContent = { type: 'delete'; item: ChatHistoryItem } | null;
|
35 |
|
36 |
export function Menu() {
|
|
|
37 |
const menuRef = useRef<HTMLDivElement>(null);
|
38 |
const [list, setList] = useState<ChatHistoryItem[]>([]);
|
39 |
const [open, setOpen] = useState(false);
|
@@ -99,6 +100,17 @@ export function Menu() {
|
|
99 |
};
|
100 |
}, []);
|
101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
return (
|
103 |
<motion.div
|
104 |
ref={menuRef}
|
@@ -128,7 +140,12 @@ export function Menu() {
|
|
128 |
{category}
|
129 |
</div>
|
130 |
{items.map((item) => (
|
131 |
-
<HistoryItem
|
|
|
|
|
|
|
|
|
|
|
132 |
))}
|
133 |
</div>
|
134 |
))}
|
|
|
4 |
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
|
5 |
import { IconButton } from '~/components/ui/IconButton';
|
6 |
import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
|
7 |
+
import { db, deleteById, getAll, chatId, type ChatHistoryItem, useChatHistory } from '~/lib/persistence';
|
8 |
import { cubicEasingFn } from '~/utils/easings';
|
9 |
import { logger } from '~/utils/logger';
|
10 |
import { HistoryItem } from './HistoryItem';
|
|
|
34 |
type DialogContent = { type: 'delete'; item: ChatHistoryItem } | null;
|
35 |
|
36 |
export function Menu() {
|
37 |
+
const { duplicateCurrentChat } = useChatHistory();
|
38 |
const menuRef = useRef<HTMLDivElement>(null);
|
39 |
const [list, setList] = useState<ChatHistoryItem[]>([]);
|
40 |
const [open, setOpen] = useState(false);
|
|
|
100 |
};
|
101 |
}, []);
|
102 |
|
103 |
+
const handleDeleteClick = (event: React.UIEvent, item: ChatHistoryItem) => {
|
104 |
+
event.preventDefault();
|
105 |
+
|
106 |
+
setDialogContent({ type: 'delete', item });
|
107 |
+
};
|
108 |
+
|
109 |
+
const handleDuplicate = async (id: string) => {
|
110 |
+
await duplicateCurrentChat(id);
|
111 |
+
loadEntries(); // Reload the list after duplication
|
112 |
+
};
|
113 |
+
|
114 |
return (
|
115 |
<motion.div
|
116 |
ref={menuRef}
|
|
|
140 |
{category}
|
141 |
</div>
|
142 |
{items.map((item) => (
|
143 |
+
<HistoryItem
|
144 |
+
key={item.id}
|
145 |
+
item={item}
|
146 |
+
onDelete={(event) => handleDeleteClick(event, item)}
|
147 |
+
onDuplicate={() => handleDuplicate(item.id)}
|
148 |
+
/>
|
149 |
))}
|
150 |
</div>
|
151 |
))}
|
app/components/workbench/Workbench.client.tsx
CHANGED
@@ -16,6 +16,7 @@ import { cubicEasingFn } from '~/utils/easings';
|
|
16 |
import { renderLogger } from '~/utils/logger';
|
17 |
import { EditorPanel } from './EditorPanel';
|
18 |
import { Preview } from './Preview';
|
|
|
19 |
|
20 |
interface WorkspaceProps {
|
21 |
chatStarted?: boolean;
|
@@ -65,6 +66,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
|
65 |
const files = useStore(workbenchStore.files);
|
66 |
const selectedView = useStore(workbenchStore.currentView);
|
67 |
|
|
|
|
|
68 |
const setSelectedView = (view: WorkbenchViewType) => {
|
69 |
workbenchStore.currentView.set(view);
|
70 |
};
|
@@ -128,18 +131,20 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
|
128 |
className={classNames(
|
129 |
'fixed top-[calc(var(--header-height)+1.5rem)] bottom-6 w-[var(--workbench-inner-width)] mr-4 z-0 transition-[left,width] duration-200 bolt-ease-cubic-bezier',
|
130 |
{
|
|
|
|
|
131 |
'left-[var(--workbench-left)]': showWorkbench,
|
132 |
'left-[100%]': !showWorkbench,
|
133 |
},
|
134 |
)}
|
135 |
>
|
136 |
-
<div className="absolute inset-0 px-6">
|
137 |
<div className="h-full flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden">
|
138 |
<div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
|
139 |
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
|
140 |
<div className="ml-auto" />
|
141 |
{selectedView === 'code' && (
|
142 |
-
|
143 |
<PanelHeaderButton
|
144 |
className="mr-1 text-sm"
|
145 |
onClick={() => {
|
@@ -165,29 +170,32 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
|
165 |
<PanelHeaderButton
|
166 |
className="mr-1 text-sm"
|
167 |
onClick={() => {
|
168 |
-
const repoName = prompt(
|
|
|
|
|
|
|
169 |
if (!repoName) {
|
170 |
-
alert(
|
171 |
return;
|
172 |
}
|
173 |
-
const githubUsername = prompt(
|
174 |
if (!githubUsername) {
|
175 |
-
alert(
|
176 |
return;
|
177 |
}
|
178 |
-
const githubToken = prompt(
|
179 |
if (!githubToken) {
|
180 |
-
alert(
|
181 |
return;
|
182 |
}
|
183 |
-
|
184 |
-
|
185 |
}}
|
186 |
>
|
187 |
<div className="i-ph:github-logo" />
|
188 |
Push to GitHub
|
189 |
</PanelHeaderButton>
|
190 |
-
|
191 |
)}
|
192 |
<IconButton
|
193 |
icon="i-ph:x-circle"
|
|
|
16 |
import { renderLogger } from '~/utils/logger';
|
17 |
import { EditorPanel } from './EditorPanel';
|
18 |
import { Preview } from './Preview';
|
19 |
+
import useViewport from '~/lib/hooks';
|
20 |
|
21 |
interface WorkspaceProps {
|
22 |
chatStarted?: boolean;
|
|
|
66 |
const files = useStore(workbenchStore.files);
|
67 |
const selectedView = useStore(workbenchStore.currentView);
|
68 |
|
69 |
+
const isSmallViewport = useViewport(1024);
|
70 |
+
|
71 |
const setSelectedView = (view: WorkbenchViewType) => {
|
72 |
workbenchStore.currentView.set(view);
|
73 |
};
|
|
|
131 |
className={classNames(
|
132 |
'fixed top-[calc(var(--header-height)+1.5rem)] bottom-6 w-[var(--workbench-inner-width)] mr-4 z-0 transition-[left,width] duration-200 bolt-ease-cubic-bezier',
|
133 |
{
|
134 |
+
'w-full': isSmallViewport,
|
135 |
+
'left-0': showWorkbench && isSmallViewport,
|
136 |
'left-[var(--workbench-left)]': showWorkbench,
|
137 |
'left-[100%]': !showWorkbench,
|
138 |
},
|
139 |
)}
|
140 |
>
|
141 |
+
<div className="absolute inset-0 px-2 lg:px-6">
|
142 |
<div className="h-full flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden">
|
143 |
<div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
|
144 |
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
|
145 |
<div className="ml-auto" />
|
146 |
{selectedView === 'code' && (
|
147 |
+
<div className="flex overflow-y-auto">
|
148 |
<PanelHeaderButton
|
149 |
className="mr-1 text-sm"
|
150 |
onClick={() => {
|
|
|
170 |
<PanelHeaderButton
|
171 |
className="mr-1 text-sm"
|
172 |
onClick={() => {
|
173 |
+
const repoName = prompt(
|
174 |
+
'Please enter a name for your new GitHub repository:',
|
175 |
+
'bolt-generated-project',
|
176 |
+
);
|
177 |
if (!repoName) {
|
178 |
+
alert('Repository name is required. Push to GitHub cancelled.');
|
179 |
return;
|
180 |
}
|
181 |
+
const githubUsername = prompt('Please enter your GitHub username:');
|
182 |
if (!githubUsername) {
|
183 |
+
alert('GitHub username is required. Push to GitHub cancelled.');
|
184 |
return;
|
185 |
}
|
186 |
+
const githubToken = prompt('Please enter your GitHub personal access token:');
|
187 |
if (!githubToken) {
|
188 |
+
alert('GitHub token is required. Push to GitHub cancelled.');
|
189 |
return;
|
190 |
}
|
191 |
+
|
192 |
+
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
|
193 |
}}
|
194 |
>
|
195 |
<div className="i-ph:github-logo" />
|
196 |
Push to GitHub
|
197 |
</PanelHeaderButton>
|
198 |
+
</div>
|
199 |
)}
|
200 |
<IconButton
|
201 |
icon="i-ph:x-circle"
|
app/lib/.server/llm/api-key.ts
CHANGED
@@ -23,6 +23,8 @@ export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Re
|
|
23 |
return env.GOOGLE_GENERATIVE_AI_API_KEY || cloudflareEnv.GOOGLE_GENERATIVE_AI_API_KEY;
|
24 |
case 'Groq':
|
25 |
return env.GROQ_API_KEY || cloudflareEnv.GROQ_API_KEY;
|
|
|
|
|
26 |
case 'OpenRouter':
|
27 |
return env.OPEN_ROUTER_API_KEY || cloudflareEnv.OPEN_ROUTER_API_KEY;
|
28 |
case 'Deepseek':
|
@@ -33,6 +35,8 @@ export function getAPIKey(cloudflareEnv: Env, provider: string, userApiKeys?: Re
|
|
33 |
return env.OPENAI_LIKE_API_KEY || cloudflareEnv.OPENAI_LIKE_API_KEY;
|
34 |
case "xAI":
|
35 |
return env.XAI_API_KEY || cloudflareEnv.XAI_API_KEY;
|
|
|
|
|
36 |
default:
|
37 |
return "";
|
38 |
}
|
|
|
23 |
return env.GOOGLE_GENERATIVE_AI_API_KEY || cloudflareEnv.GOOGLE_GENERATIVE_AI_API_KEY;
|
24 |
case 'Groq':
|
25 |
return env.GROQ_API_KEY || cloudflareEnv.GROQ_API_KEY;
|
26 |
+
case 'HuggingFace':
|
27 |
+
return env.HuggingFace_API_KEY || cloudflareEnv.HuggingFace_API_KEY;
|
28 |
case 'OpenRouter':
|
29 |
return env.OPEN_ROUTER_API_KEY || cloudflareEnv.OPEN_ROUTER_API_KEY;
|
30 |
case 'Deepseek':
|
|
|
35 |
return env.OPENAI_LIKE_API_KEY || cloudflareEnv.OPENAI_LIKE_API_KEY;
|
36 |
case "xAI":
|
37 |
return env.XAI_API_KEY || cloudflareEnv.XAI_API_KEY;
|
38 |
+
case "Cohere":
|
39 |
+
return env.COHERE_API_KEY;
|
40 |
default:
|
41 |
return "";
|
42 |
}
|
app/lib/.server/llm/model.ts
CHANGED
@@ -7,6 +7,7 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
7 |
import { ollama } from 'ollama-ai-provider';
|
8 |
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
9 |
import { createMistral } from '@ai-sdk/mistral';
|
|
|
10 |
|
11 |
export const DEFAULT_NUM_CTX = process.env.DEFAULT_NUM_CTX ?
|
12 |
parseInt(process.env.DEFAULT_NUM_CTX, 10) :
|
@@ -27,6 +28,15 @@ export function getOpenAILikeModel(baseURL:string,apiKey: string, model: string)
|
|
27 |
|
28 |
return openai(model);
|
29 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
export function getOpenAIModel(apiKey: string, model: string) {
|
31 |
const openai = createOpenAI({
|
32 |
apiKey,
|
@@ -60,6 +70,15 @@ export function getGroqModel(apiKey: string, model: string) {
|
|
60 |
return openai(model);
|
61 |
}
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
export function getOllamaModel(baseURL: string, model: string) {
|
64 |
let Ollama = ollama(model, {
|
65 |
numCtx: DEFAULT_NUM_CTX,
|
@@ -103,6 +122,8 @@ export function getXAIModel(apiKey: string, model: string) {
|
|
103 |
|
104 |
return openai(model);
|
105 |
}
|
|
|
|
|
106 |
export function getModel(provider: string, model: string, env: Env, apiKeys?: Record<string, string>) {
|
107 |
const apiKey = getAPIKey(env, provider, apiKeys);
|
108 |
const baseURL = getBaseURL(env, provider);
|
@@ -114,6 +135,8 @@ export function getModel(provider: string, model: string, env: Env, apiKeys?: Re
|
|
114 |
return getOpenAIModel(apiKey, model);
|
115 |
case 'Groq':
|
116 |
return getGroqModel(apiKey, model);
|
|
|
|
|
117 |
case 'OpenRouter':
|
118 |
return getOpenRouterModel(apiKey, model);
|
119 |
case 'Google':
|
@@ -128,6 +151,8 @@ export function getModel(provider: string, model: string, env: Env, apiKeys?: Re
|
|
128 |
return getLMStudioModel(baseURL, model);
|
129 |
case 'xAI':
|
130 |
return getXAIModel(apiKey, model);
|
|
|
|
|
131 |
default:
|
132 |
return getOllamaModel(baseURL, model);
|
133 |
}
|
|
|
7 |
import { ollama } from 'ollama-ai-provider';
|
8 |
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
9 |
import { createMistral } from '@ai-sdk/mistral';
|
10 |
+
import { createCohere } from '@ai-sdk/cohere'
|
11 |
|
12 |
export const DEFAULT_NUM_CTX = process.env.DEFAULT_NUM_CTX ?
|
13 |
parseInt(process.env.DEFAULT_NUM_CTX, 10) :
|
|
|
28 |
|
29 |
return openai(model);
|
30 |
}
|
31 |
+
|
32 |
+
export function getCohereAIModel(apiKey:string, model: string){
|
33 |
+
const cohere = createCohere({
|
34 |
+
apiKey,
|
35 |
+
});
|
36 |
+
|
37 |
+
return cohere(model);
|
38 |
+
}
|
39 |
+
|
40 |
export function getOpenAIModel(apiKey: string, model: string) {
|
41 |
const openai = createOpenAI({
|
42 |
apiKey,
|
|
|
70 |
return openai(model);
|
71 |
}
|
72 |
|
73 |
+
export function getHuggingFaceModel(apiKey: string, model: string) {
|
74 |
+
const openai = createOpenAI({
|
75 |
+
baseURL: 'https://api-inference.huggingface.co/v1/',
|
76 |
+
apiKey,
|
77 |
+
});
|
78 |
+
|
79 |
+
return openai(model);
|
80 |
+
}
|
81 |
+
|
82 |
export function getOllamaModel(baseURL: string, model: string) {
|
83 |
let Ollama = ollama(model, {
|
84 |
numCtx: DEFAULT_NUM_CTX,
|
|
|
122 |
|
123 |
return openai(model);
|
124 |
}
|
125 |
+
|
126 |
+
|
127 |
export function getModel(provider: string, model: string, env: Env, apiKeys?: Record<string, string>) {
|
128 |
const apiKey = getAPIKey(env, provider, apiKeys);
|
129 |
const baseURL = getBaseURL(env, provider);
|
|
|
135 |
return getOpenAIModel(apiKey, model);
|
136 |
case 'Groq':
|
137 |
return getGroqModel(apiKey, model);
|
138 |
+
case 'HuggingFace':
|
139 |
+
return getHuggingFaceModel(apiKey, model);
|
140 |
case 'OpenRouter':
|
141 |
return getOpenRouterModel(apiKey, model);
|
142 |
case 'Google':
|
|
|
151 |
return getLMStudioModel(baseURL, model);
|
152 |
case 'xAI':
|
153 |
return getXAIModel(apiKey, model);
|
154 |
+
case 'Cohere':
|
155 |
+
return getCohereAIModel(apiKey, model);
|
156 |
default:
|
157 |
return getOllamaModel(baseURL, model);
|
158 |
}
|
app/lib/.server/llm/prompts.ts
CHANGED
@@ -88,7 +88,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
|
|
88 |
Example:
|
89 |
|
90 |
<${MODIFICATIONS_TAG_NAME}>
|
91 |
-
<diff path="/
|
92 |
@@ -2,7 +2,10 @@
|
93 |
return a + b;
|
94 |
}
|
@@ -103,7 +103,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
|
|
103 |
+
|
104 |
+console.log('The End');
|
105 |
</diff>
|
106 |
-
<file path="/
|
107 |
// full file content here
|
108 |
</file>
|
109 |
</${MODIFICATIONS_TAG_NAME}>
|
|
|
88 |
Example:
|
89 |
|
90 |
<${MODIFICATIONS_TAG_NAME}>
|
91 |
+
<diff path="${WORK_DIR}/src/main.js">
|
92 |
@@ -2,7 +2,10 @@
|
93 |
return a + b;
|
94 |
}
|
|
|
103 |
+
|
104 |
+console.log('The End');
|
105 |
</diff>
|
106 |
+
<file path="${WORK_DIR}/package.json">
|
107 |
// full file content here
|
108 |
</file>
|
109 |
</${MODIFICATIONS_TAG_NAME}>
|
app/lib/.server/llm/stream-text.ts
CHANGED
@@ -41,10 +41,9 @@ function extractPropertiesFromMessage(message: Message): { model: string; provid
|
|
41 |
|
42 |
return { model, provider, content: cleanedContent };
|
43 |
}
|
44 |
-
|
45 |
export function streamText(
|
46 |
-
messages: Messages,
|
47 |
-
env: Env,
|
48 |
options?: StreamingOptions,
|
49 |
apiKeys?: Record<string, string>
|
50 |
) {
|
@@ -64,13 +63,22 @@ export function streamText(
|
|
64 |
return { ...message, content };
|
65 |
}
|
66 |
|
67 |
-
return message;
|
68 |
});
|
69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
return _streamText({
|
71 |
model: getModel(currentProvider, currentModel, env, apiKeys),
|
72 |
system: getSystemPrompt(),
|
73 |
-
maxTokens:
|
74 |
messages: convertToCoreMessages(processedMessages),
|
75 |
...options,
|
76 |
});
|
|
|
41 |
|
42 |
return { model, provider, content: cleanedContent };
|
43 |
}
|
|
|
44 |
export function streamText(
|
45 |
+
messages: Messages,
|
46 |
+
env: Env,
|
47 |
options?: StreamingOptions,
|
48 |
apiKeys?: Record<string, string>
|
49 |
) {
|
|
|
63 |
return { ...message, content };
|
64 |
}
|
65 |
|
66 |
+
return message;
|
67 |
});
|
68 |
|
69 |
+
const modelDetails = MODEL_LIST.find((m) => m.name === currentModel);
|
70 |
+
|
71 |
+
|
72 |
+
|
73 |
+
const dynamicMaxTokens =
|
74 |
+
modelDetails && modelDetails.maxTokenAllowed
|
75 |
+
? modelDetails.maxTokenAllowed
|
76 |
+
: MAX_TOKENS;
|
77 |
+
|
78 |
return _streamText({
|
79 |
model: getModel(currentProvider, currentModel, env, apiKeys),
|
80 |
system: getSystemPrompt(),
|
81 |
+
maxTokens: dynamicMaxTokens,
|
82 |
messages: convertToCoreMessages(processedMessages),
|
83 |
...options,
|
84 |
});
|
app/lib/hooks/index.ts
CHANGED
@@ -2,3 +2,4 @@ export * from './useMessageParser';
|
|
2 |
export * from './usePromptEnhancer';
|
3 |
export * from './useShortcuts';
|
4 |
export * from './useSnapScroll';
|
|
|
|
2 |
export * from './usePromptEnhancer';
|
3 |
export * from './useShortcuts';
|
4 |
export * from './useSnapScroll';
|
5 |
+
export { default } from './useViewport';
|
app/lib/hooks/usePromptEnhancer.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import { useState } from 'react';
|
|
|
2 |
import { createScopedLogger } from '~/utils/logger';
|
3 |
|
4 |
const logger = createScopedLogger('usePromptEnhancement');
|
@@ -13,54 +14,54 @@ export function usePromptEnhancer() {
|
|
13 |
};
|
14 |
|
15 |
const enhancePrompt = async (
|
16 |
-
input: string,
|
17 |
setInput: (value: string) => void,
|
18 |
model: string,
|
19 |
-
provider:
|
20 |
-
apiKeys?: Record<string, string
|
21 |
) => {
|
22 |
setEnhancingPrompt(true);
|
23 |
setPromptEnhanced(false);
|
24 |
-
|
25 |
const requestBody: any = {
|
26 |
message: input,
|
27 |
model,
|
28 |
provider,
|
29 |
};
|
30 |
-
|
31 |
if (apiKeys) {
|
32 |
requestBody.apiKeys = apiKeys;
|
33 |
}
|
34 |
-
|
35 |
const response = await fetch('/api/enhancer', {
|
36 |
method: 'POST',
|
37 |
body: JSON.stringify(requestBody),
|
38 |
});
|
39 |
-
|
40 |
const reader = response.body?.getReader();
|
41 |
-
|
42 |
const originalInput = input;
|
43 |
-
|
44 |
if (reader) {
|
45 |
const decoder = new TextDecoder();
|
46 |
-
|
47 |
let _input = '';
|
48 |
let _error;
|
49 |
-
|
50 |
try {
|
51 |
setInput('');
|
52 |
-
|
53 |
while (true) {
|
54 |
const { value, done } = await reader.read();
|
55 |
-
|
56 |
if (done) {
|
57 |
break;
|
58 |
}
|
59 |
-
|
60 |
_input += decoder.decode(value);
|
61 |
-
|
62 |
logger.trace('Set input', _input);
|
63 |
-
|
64 |
setInput(_input);
|
65 |
}
|
66 |
} catch (error) {
|
@@ -70,10 +71,10 @@ export function usePromptEnhancer() {
|
|
70 |
if (_error) {
|
71 |
logger.error(_error);
|
72 |
}
|
73 |
-
|
74 |
setEnhancingPrompt(false);
|
75 |
setPromptEnhanced(true);
|
76 |
-
|
77 |
setTimeout(() => {
|
78 |
setInput(_input);
|
79 |
});
|
|
|
1 |
import { useState } from 'react';
|
2 |
+
import type { ProviderInfo } from '~/types/model';
|
3 |
import { createScopedLogger } from '~/utils/logger';
|
4 |
|
5 |
const logger = createScopedLogger('usePromptEnhancement');
|
|
|
14 |
};
|
15 |
|
16 |
const enhancePrompt = async (
|
17 |
+
input: string,
|
18 |
setInput: (value: string) => void,
|
19 |
model: string,
|
20 |
+
provider: ProviderInfo,
|
21 |
+
apiKeys?: Record<string, string>,
|
22 |
) => {
|
23 |
setEnhancingPrompt(true);
|
24 |
setPromptEnhanced(false);
|
25 |
+
|
26 |
const requestBody: any = {
|
27 |
message: input,
|
28 |
model,
|
29 |
provider,
|
30 |
};
|
31 |
+
|
32 |
if (apiKeys) {
|
33 |
requestBody.apiKeys = apiKeys;
|
34 |
}
|
35 |
+
|
36 |
const response = await fetch('/api/enhancer', {
|
37 |
method: 'POST',
|
38 |
body: JSON.stringify(requestBody),
|
39 |
});
|
40 |
+
|
41 |
const reader = response.body?.getReader();
|
42 |
+
|
43 |
const originalInput = input;
|
44 |
+
|
45 |
if (reader) {
|
46 |
const decoder = new TextDecoder();
|
47 |
+
|
48 |
let _input = '';
|
49 |
let _error;
|
50 |
+
|
51 |
try {
|
52 |
setInput('');
|
53 |
+
|
54 |
while (true) {
|
55 |
const { value, done } = await reader.read();
|
56 |
+
|
57 |
if (done) {
|
58 |
break;
|
59 |
}
|
60 |
+
|
61 |
_input += decoder.decode(value);
|
62 |
+
|
63 |
logger.trace('Set input', _input);
|
64 |
+
|
65 |
setInput(_input);
|
66 |
}
|
67 |
} catch (error) {
|
|
|
71 |
if (_error) {
|
72 |
logger.error(_error);
|
73 |
}
|
74 |
+
|
75 |
setEnhancingPrompt(false);
|
76 |
setPromptEnhanced(true);
|
77 |
+
|
78 |
setTimeout(() => {
|
79 |
setInput(_input);
|
80 |
});
|
app/lib/hooks/useViewport.ts
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useEffect } from 'react';
|
2 |
+
|
3 |
+
const useViewport = (threshold = 1024) => {
|
4 |
+
const [isSmallViewport, setIsSmallViewport] = useState(window.innerWidth < threshold);
|
5 |
+
|
6 |
+
useEffect(() => {
|
7 |
+
const handleResize = () => setIsSmallViewport(window.innerWidth < threshold);
|
8 |
+
window.addEventListener('resize', handleResize);
|
9 |
+
|
10 |
+
return () => {
|
11 |
+
window.removeEventListener('resize', handleResize);
|
12 |
+
};
|
13 |
+
}, [threshold]);
|
14 |
+
|
15 |
+
return isSmallViewport;
|
16 |
+
};
|
17 |
+
|
18 |
+
export default useViewport;
|
app/lib/persistence/db.ts
CHANGED
@@ -158,3 +158,50 @@ async function getUrlIds(db: IDBDatabase): Promise<string[]> {
|
|
158 |
};
|
159 |
});
|
160 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
};
|
159 |
});
|
160 |
}
|
161 |
+
|
162 |
+
export async function forkChat(db: IDBDatabase, chatId: string, messageId: string): Promise<string> {
|
163 |
+
const chat = await getMessages(db, chatId);
|
164 |
+
if (!chat) throw new Error('Chat not found');
|
165 |
+
|
166 |
+
// Find the index of the message to fork at
|
167 |
+
const messageIndex = chat.messages.findIndex(msg => msg.id === messageId);
|
168 |
+
if (messageIndex === -1) throw new Error('Message not found');
|
169 |
+
|
170 |
+
// Get messages up to and including the selected message
|
171 |
+
const messages = chat.messages.slice(0, messageIndex + 1);
|
172 |
+
|
173 |
+
// Generate new IDs
|
174 |
+
const newId = await getNextId(db);
|
175 |
+
const urlId = await getUrlId(db, newId);
|
176 |
+
|
177 |
+
// Create the forked chat
|
178 |
+
await setMessages(
|
179 |
+
db,
|
180 |
+
newId,
|
181 |
+
messages,
|
182 |
+
urlId,
|
183 |
+
chat.description ? `${chat.description} (fork)` : 'Forked chat'
|
184 |
+
);
|
185 |
+
|
186 |
+
return urlId;
|
187 |
+
}
|
188 |
+
|
189 |
+
export async function duplicateChat(db: IDBDatabase, id: string): Promise<string> {
|
190 |
+
const chat = await getMessages(db, id);
|
191 |
+
if (!chat) {
|
192 |
+
throw new Error('Chat not found');
|
193 |
+
}
|
194 |
+
|
195 |
+
const newId = await getNextId(db);
|
196 |
+
const newUrlId = await getUrlId(db, newId); // Get a new urlId for the duplicated chat
|
197 |
+
|
198 |
+
await setMessages(
|
199 |
+
db,
|
200 |
+
newId,
|
201 |
+
chat.messages,
|
202 |
+
newUrlId, // Use the new urlId
|
203 |
+
`${chat.description || 'Chat'} (copy)`
|
204 |
+
);
|
205 |
+
|
206 |
+
return newUrlId; // Return the urlId instead of id for navigation
|
207 |
+
}
|
app/lib/persistence/useChatHistory.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
-
import { useLoaderData, useNavigate } from '@remix-run/react';
|
2 |
import { useState, useEffect } from 'react';
|
3 |
import { atom } from 'nanostores';
|
4 |
import type { Message } from 'ai';
|
5 |
import { toast } from 'react-toastify';
|
6 |
import { workbenchStore } from '~/lib/stores/workbench';
|
7 |
-
import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db';
|
8 |
|
9 |
export interface ChatHistoryItem {
|
10 |
id: string;
|
@@ -24,6 +24,7 @@ export const description = atom<string | undefined>(undefined);
|
|
24 |
export function useChatHistory() {
|
25 |
const navigate = useNavigate();
|
26 |
const { id: mixedId } = useLoaderData<{ id?: string }>();
|
|
|
27 |
|
28 |
const [initialMessages, setInitialMessages] = useState<Message[]>([]);
|
29 |
const [ready, setReady] = useState<boolean>(false);
|
@@ -44,7 +45,12 @@ export function useChatHistory() {
|
|
44 |
getMessages(db, mixedId)
|
45 |
.then((storedMessages) => {
|
46 |
if (storedMessages && storedMessages.messages.length > 0) {
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
48 |
setUrlId(storedMessages.urlId);
|
49 |
description.set(storedMessages.description);
|
50 |
chatId.set(storedMessages.id);
|
@@ -93,6 +99,19 @@ export function useChatHistory() {
|
|
93 |
|
94 |
await setMessages(db, chatId.get() as string, messages, urlId, description.get());
|
95 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
};
|
97 |
}
|
98 |
|
|
|
1 |
+
import { useLoaderData, useNavigate, useSearchParams } from '@remix-run/react';
|
2 |
import { useState, useEffect } from 'react';
|
3 |
import { atom } from 'nanostores';
|
4 |
import type { Message } from 'ai';
|
5 |
import { toast } from 'react-toastify';
|
6 |
import { workbenchStore } from '~/lib/stores/workbench';
|
7 |
+
import { getMessages, getNextId, getUrlId, openDatabase, setMessages, duplicateChat } from './db';
|
8 |
|
9 |
export interface ChatHistoryItem {
|
10 |
id: string;
|
|
|
24 |
export function useChatHistory() {
|
25 |
const navigate = useNavigate();
|
26 |
const { id: mixedId } = useLoaderData<{ id?: string }>();
|
27 |
+
const [searchParams] = useSearchParams();
|
28 |
|
29 |
const [initialMessages, setInitialMessages] = useState<Message[]>([]);
|
30 |
const [ready, setReady] = useState<boolean>(false);
|
|
|
45 |
getMessages(db, mixedId)
|
46 |
.then((storedMessages) => {
|
47 |
if (storedMessages && storedMessages.messages.length > 0) {
|
48 |
+
const rewindId = searchParams.get('rewindTo');
|
49 |
+
const filteredMessages = rewindId
|
50 |
+
? storedMessages.messages.slice(0, storedMessages.messages.findIndex((m) => m.id === rewindId) + 1)
|
51 |
+
: storedMessages.messages;
|
52 |
+
|
53 |
+
setInitialMessages(filteredMessages);
|
54 |
setUrlId(storedMessages.urlId);
|
55 |
description.set(storedMessages.description);
|
56 |
chatId.set(storedMessages.id);
|
|
|
99 |
|
100 |
await setMessages(db, chatId.get() as string, messages, urlId, description.get());
|
101 |
},
|
102 |
+
duplicateCurrentChat: async (listItemId:string) => {
|
103 |
+
if (!db || (!mixedId && !listItemId)) {
|
104 |
+
return;
|
105 |
+
}
|
106 |
+
|
107 |
+
try {
|
108 |
+
const newId = await duplicateChat(db, mixedId || listItemId);
|
109 |
+
navigate(`/chat/${newId}`);
|
110 |
+
toast.success('Chat duplicated successfully');
|
111 |
+
} catch (error) {
|
112 |
+
toast.error('Failed to duplicate chat');
|
113 |
+
}
|
114 |
+
}
|
115 |
};
|
116 |
}
|
117 |
|
app/lib/runtime/action-runner.ts
CHANGED
@@ -94,7 +94,7 @@ export class ActionRunner {
|
|
94 |
|
95 |
this.#updateAction(actionId, { ...action, ...data.action, executed: !isStreaming });
|
96 |
|
97 |
-
this.#currentExecutionPromise = this.#currentExecutionPromise
|
98 |
.then(() => {
|
99 |
return this.#executeAction(actionId, isStreaming);
|
100 |
})
|
@@ -119,7 +119,14 @@ export class ActionRunner {
|
|
119 |
break;
|
120 |
}
|
121 |
case 'start': {
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
break;
|
124 |
}
|
125 |
}
|
|
|
94 |
|
95 |
this.#updateAction(actionId, { ...action, ...data.action, executed: !isStreaming });
|
96 |
|
97 |
+
return this.#currentExecutionPromise = this.#currentExecutionPromise
|
98 |
.then(() => {
|
99 |
return this.#executeAction(actionId, isStreaming);
|
100 |
})
|
|
|
119 |
break;
|
120 |
}
|
121 |
case 'start': {
|
122 |
+
// making the start app non blocking
|
123 |
+
|
124 |
+
this.#runStartAction(action).then(()=>this.#updateAction(actionId, { status: 'complete' }))
|
125 |
+
.catch(()=>this.#updateAction(actionId, { status: 'failed', error: 'Action failed' }))
|
126 |
+
// adding a delay to avoid any race condition between 2 start actions
|
127 |
+
// i am up for a better approch
|
128 |
+
await new Promise(resolve=>setTimeout(resolve,2000))
|
129 |
+
return
|
130 |
break;
|
131 |
}
|
132 |
}
|
app/lib/stores/workbench.ts
CHANGED
@@ -14,6 +14,7 @@ import { saveAs } from 'file-saver';
|
|
14 |
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
|
15 |
import * as nodePath from 'node:path';
|
16 |
import type { WebContainerProcess } from '@webcontainer/api';
|
|
|
17 |
|
18 |
export interface ArtifactState {
|
19 |
id: string;
|
@@ -42,7 +43,7 @@ export class WorkbenchStore {
|
|
42 |
modifiedFiles = new Set<string>();
|
43 |
artifactIdList: string[] = [];
|
44 |
#boltTerminal: { terminal: ITerminal; process: WebContainerProcess } | undefined;
|
45 |
-
|
46 |
constructor() {
|
47 |
if (import.meta.hot) {
|
48 |
import.meta.hot.data.artifacts = this.artifacts;
|
@@ -52,6 +53,10 @@ export class WorkbenchStore {
|
|
52 |
}
|
53 |
}
|
54 |
|
|
|
|
|
|
|
|
|
55 |
get previews() {
|
56 |
return this.#previewsStore.previews;
|
57 |
}
|
@@ -255,8 +260,11 @@ export class WorkbenchStore {
|
|
255 |
|
256 |
this.artifacts.setKey(messageId, { ...artifact, ...state });
|
257 |
}
|
258 |
-
|
259 |
-
|
|
|
|
|
|
|
260 |
const { messageId } = data;
|
261 |
|
262 |
const artifact = this.#getArtifact(messageId);
|
@@ -265,10 +273,18 @@ export class WorkbenchStore {
|
|
265 |
unreachable('Artifact not found');
|
266 |
}
|
267 |
|
268 |
-
artifact.runner.addAction(data);
|
269 |
}
|
270 |
|
271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
const { messageId } = data;
|
273 |
|
274 |
const artifact = this.#getArtifact(messageId);
|
@@ -293,11 +309,11 @@ export class WorkbenchStore {
|
|
293 |
this.#editorStore.updateFile(fullPath, data.action.content);
|
294 |
|
295 |
if (!isStreaming) {
|
296 |
-
this.resetCurrentDocument();
|
297 |
await artifact.runner.runAction(data);
|
|
|
298 |
}
|
299 |
} else {
|
300 |
-
artifact.runner.runAction(data);
|
301 |
}
|
302 |
}
|
303 |
|
@@ -312,8 +328,7 @@ export class WorkbenchStore {
|
|
312 |
|
313 |
for (const [filePath, dirent] of Object.entries(files)) {
|
314 |
if (dirent?.type === 'file' && !dirent.isBinary) {
|
315 |
-
|
316 |
-
const relativePath = filePath.replace(/^\/home\/project\//, '');
|
317 |
|
318 |
// split the path into segments
|
319 |
const pathSegments = relativePath.split('/');
|
@@ -343,7 +358,7 @@ export class WorkbenchStore {
|
|
343 |
|
344 |
for (const [filePath, dirent] of Object.entries(files)) {
|
345 |
if (dirent?.type === 'file' && !dirent.isBinary) {
|
346 |
-
const relativePath = filePath
|
347 |
const pathSegments = relativePath.split('/');
|
348 |
let currentHandle = targetHandle;
|
349 |
|
@@ -417,7 +432,7 @@ export class WorkbenchStore {
|
|
417 |
content: Buffer.from(dirent.content).toString('base64'),
|
418 |
encoding: 'base64',
|
419 |
});
|
420 |
-
return { path: filePath
|
421 |
}
|
422 |
})
|
423 |
);
|
|
|
14 |
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
|
15 |
import * as nodePath from 'node:path';
|
16 |
import type { WebContainerProcess } from '@webcontainer/api';
|
17 |
+
import { extractRelativePath } from '~/utils/diff';
|
18 |
|
19 |
export interface ArtifactState {
|
20 |
id: string;
|
|
|
43 |
modifiedFiles = new Set<string>();
|
44 |
artifactIdList: string[] = [];
|
45 |
#boltTerminal: { terminal: ITerminal; process: WebContainerProcess } | undefined;
|
46 |
+
#globalExecutionQueue=Promise.resolve();
|
47 |
constructor() {
|
48 |
if (import.meta.hot) {
|
49 |
import.meta.hot.data.artifacts = this.artifacts;
|
|
|
53 |
}
|
54 |
}
|
55 |
|
56 |
+
addToExecutionQueue(callback: () => Promise<void>) {
|
57 |
+
this.#globalExecutionQueue=this.#globalExecutionQueue.then(()=>callback())
|
58 |
+
}
|
59 |
+
|
60 |
get previews() {
|
61 |
return this.#previewsStore.previews;
|
62 |
}
|
|
|
260 |
|
261 |
this.artifacts.setKey(messageId, { ...artifact, ...state });
|
262 |
}
|
263 |
+
addAction(data: ActionCallbackData) {
|
264 |
+
this._addAction(data)
|
265 |
+
// this.addToExecutionQueue(()=>this._addAction(data))
|
266 |
+
}
|
267 |
+
async _addAction(data: ActionCallbackData) {
|
268 |
const { messageId } = data;
|
269 |
|
270 |
const artifact = this.#getArtifact(messageId);
|
|
|
273 |
unreachable('Artifact not found');
|
274 |
}
|
275 |
|
276 |
+
return artifact.runner.addAction(data);
|
277 |
}
|
278 |
|
279 |
+
runAction(data: ActionCallbackData, isStreaming: boolean = false) {
|
280 |
+
if(isStreaming) {
|
281 |
+
this._runAction(data, isStreaming)
|
282 |
+
}
|
283 |
+
else{
|
284 |
+
this.addToExecutionQueue(()=>this._runAction(data, isStreaming))
|
285 |
+
}
|
286 |
+
}
|
287 |
+
async _runAction(data: ActionCallbackData, isStreaming: boolean = false) {
|
288 |
const { messageId } = data;
|
289 |
|
290 |
const artifact = this.#getArtifact(messageId);
|
|
|
309 |
this.#editorStore.updateFile(fullPath, data.action.content);
|
310 |
|
311 |
if (!isStreaming) {
|
|
|
312 |
await artifact.runner.runAction(data);
|
313 |
+
this.resetAllFileModifications();
|
314 |
}
|
315 |
} else {
|
316 |
+
await artifact.runner.runAction(data);
|
317 |
}
|
318 |
}
|
319 |
|
|
|
328 |
|
329 |
for (const [filePath, dirent] of Object.entries(files)) {
|
330 |
if (dirent?.type === 'file' && !dirent.isBinary) {
|
331 |
+
const relativePath = extractRelativePath(filePath);
|
|
|
332 |
|
333 |
// split the path into segments
|
334 |
const pathSegments = relativePath.split('/');
|
|
|
358 |
|
359 |
for (const [filePath, dirent] of Object.entries(files)) {
|
360 |
if (dirent?.type === 'file' && !dirent.isBinary) {
|
361 |
+
const relativePath = extractRelativePath(filePath);
|
362 |
const pathSegments = relativePath.split('/');
|
363 |
let currentHandle = targetHandle;
|
364 |
|
|
|
432 |
content: Buffer.from(dirent.content).toString('base64'),
|
433 |
encoding: 'base64',
|
434 |
});
|
435 |
+
return { path: extractRelativePath(filePath), sha: blob.sha };
|
436 |
}
|
437 |
})
|
438 |
);
|
app/routes/api.enhancer.ts
CHANGED
@@ -2,7 +2,7 @@ import { type ActionFunctionArgs } from '@remix-run/cloudflare';
|
|
2 |
import { StreamingTextResponse, parseStreamPart } from 'ai';
|
3 |
import { streamText } from '~/lib/.server/llm/stream-text';
|
4 |
import { stripIndents } from '~/utils/stripIndent';
|
5 |
-
import type {
|
6 |
|
7 |
const encoder = new TextEncoder();
|
8 |
const decoder = new TextDecoder();
|
@@ -12,25 +12,27 @@ export async function action(args: ActionFunctionArgs) {
|
|
12 |
}
|
13 |
|
14 |
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
15 |
-
const { message, model, provider, apiKeys } = await request.json<{
|
16 |
message: string;
|
17 |
model: string;
|
18 |
-
provider:
|
19 |
apiKeys?: Record<string, string>;
|
20 |
}>();
|
21 |
|
22 |
-
|
|
|
|
|
23 |
if (!model || typeof model !== 'string') {
|
24 |
throw new Response('Invalid or missing model', {
|
25 |
status: 400,
|
26 |
-
statusText: 'Bad Request'
|
27 |
});
|
28 |
}
|
29 |
|
30 |
-
if (!
|
31 |
throw new Response('Invalid or missing provider', {
|
32 |
status: 400,
|
33 |
-
statusText: 'Bad Request'
|
34 |
});
|
35 |
}
|
36 |
|
@@ -39,7 +41,9 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
|
39 |
[
|
40 |
{
|
41 |
role: 'user',
|
42 |
-
content:
|
|
|
|
|
43 |
I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
|
44 |
|
45 |
IMPORTANT: Only respond with the improved prompt and nothing else!
|
@@ -52,23 +56,24 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
|
52 |
],
|
53 |
context.cloudflare.env,
|
54 |
undefined,
|
55 |
-
apiKeys
|
56 |
);
|
57 |
|
58 |
const transformStream = new TransformStream({
|
59 |
transform(chunk, controller) {
|
60 |
const text = decoder.decode(chunk);
|
61 |
-
const lines = text.split('\n').filter(line => line.trim() !== '');
|
62 |
-
|
63 |
for (const line of lines) {
|
64 |
try {
|
65 |
const parsed = parseStreamPart(line);
|
|
|
66 |
if (parsed.type === 'text') {
|
67 |
controller.enqueue(encoder.encode(parsed.value));
|
68 |
}
|
69 |
} catch (e) {
|
70 |
-
//
|
71 |
-
console.warn('Failed to parse stream part:', line);
|
72 |
}
|
73 |
}
|
74 |
},
|
@@ -83,7 +88,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
|
83 |
if (error instanceof Error && error.message?.includes('API key')) {
|
84 |
throw new Response('Invalid or missing API key', {
|
85 |
status: 401,
|
86 |
-
statusText: 'Unauthorized'
|
87 |
});
|
88 |
}
|
89 |
|
|
|
2 |
import { StreamingTextResponse, parseStreamPart } from 'ai';
|
3 |
import { streamText } from '~/lib/.server/llm/stream-text';
|
4 |
import { stripIndents } from '~/utils/stripIndent';
|
5 |
+
import type { ProviderInfo } from '~/types/model';
|
6 |
|
7 |
const encoder = new TextEncoder();
|
8 |
const decoder = new TextDecoder();
|
|
|
12 |
}
|
13 |
|
14 |
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
15 |
+
const { message, model, provider, apiKeys } = await request.json<{
|
16 |
message: string;
|
17 |
model: string;
|
18 |
+
provider: ProviderInfo;
|
19 |
apiKeys?: Record<string, string>;
|
20 |
}>();
|
21 |
|
22 |
+
const { name: providerName } = provider;
|
23 |
+
|
24 |
+
// validate 'model' and 'provider' fields
|
25 |
if (!model || typeof model !== 'string') {
|
26 |
throw new Response('Invalid or missing model', {
|
27 |
status: 400,
|
28 |
+
statusText: 'Bad Request',
|
29 |
});
|
30 |
}
|
31 |
|
32 |
+
if (!providerName || typeof providerName !== 'string') {
|
33 |
throw new Response('Invalid or missing provider', {
|
34 |
status: 400,
|
35 |
+
statusText: 'Bad Request',
|
36 |
});
|
37 |
}
|
38 |
|
|
|
41 |
[
|
42 |
{
|
43 |
role: 'user',
|
44 |
+
content:
|
45 |
+
`[Model: ${model}]\n\n[Provider: ${providerName}]\n\n` +
|
46 |
+
stripIndents`
|
47 |
I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
|
48 |
|
49 |
IMPORTANT: Only respond with the improved prompt and nothing else!
|
|
|
56 |
],
|
57 |
context.cloudflare.env,
|
58 |
undefined,
|
59 |
+
apiKeys,
|
60 |
);
|
61 |
|
62 |
const transformStream = new TransformStream({
|
63 |
transform(chunk, controller) {
|
64 |
const text = decoder.decode(chunk);
|
65 |
+
const lines = text.split('\n').filter((line) => line.trim() !== '');
|
66 |
+
|
67 |
for (const line of lines) {
|
68 |
try {
|
69 |
const parsed = parseStreamPart(line);
|
70 |
+
|
71 |
if (parsed.type === 'text') {
|
72 |
controller.enqueue(encoder.encode(parsed.value));
|
73 |
}
|
74 |
} catch (e) {
|
75 |
+
// skip invalid JSON lines
|
76 |
+
console.warn('Failed to parse stream part:', line, e);
|
77 |
}
|
78 |
}
|
79 |
},
|
|
|
88 |
if (error instanceof Error && error.message?.includes('API key')) {
|
89 |
throw new Response('Invalid or missing API key', {
|
90 |
status: 401,
|
91 |
+
statusText: 'Unauthorized',
|
92 |
});
|
93 |
}
|
94 |
|
app/utils/constants.ts
CHANGED
@@ -12,12 +12,12 @@ const PROVIDER_LIST: ProviderInfo[] = [
|
|
12 |
{
|
13 |
name: 'Anthropic',
|
14 |
staticModels: [
|
15 |
-
{ name: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet (new)', provider: 'Anthropic' },
|
16 |
-
{ name: 'claude-3-5-sonnet-20240620', label: 'Claude 3.5 Sonnet (old)', provider: 'Anthropic' },
|
17 |
-
{ name: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku (new)', provider: 'Anthropic' },
|
18 |
-
{ name: 'claude-3-opus-latest', label: 'Claude 3 Opus', provider: 'Anthropic' },
|
19 |
-
{ name: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', provider: 'Anthropic' },
|
20 |
-
{ name: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', provider: 'Anthropic' }
|
21 |
],
|
22 |
getApiKeyLink: "https://console.anthropic.com/settings/keys",
|
23 |
},
|
@@ -33,23 +33,40 @@ const PROVIDER_LIST: ProviderInfo[] = [
|
|
33 |
staticModels: [],
|
34 |
getDynamicModels: getOpenAILikeModels
|
35 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
{
|
37 |
name: 'OpenRouter',
|
38 |
staticModels: [
|
39 |
-
{ name: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI' },
|
40 |
{
|
41 |
name: 'anthropic/claude-3.5-sonnet',
|
42 |
label: 'Anthropic: Claude 3.5 Sonnet (OpenRouter)',
|
43 |
provider: 'OpenRouter'
|
|
|
44 |
},
|
45 |
-
{ name: 'anthropic/claude-3-haiku', label: 'Anthropic: Claude 3 Haiku (OpenRouter)', provider: 'OpenRouter' },
|
46 |
-
{ name: 'deepseek/deepseek-coder', label: 'Deepseek-Coder V2 236B (OpenRouter)', provider: 'OpenRouter' },
|
47 |
-
{ name: 'google/gemini-flash-1.5', label: 'Google Gemini Flash 1.5 (OpenRouter)', provider: 'OpenRouter' },
|
48 |
-
{ name: 'google/gemini-pro-1.5', label: 'Google Gemini Pro 1.5 (OpenRouter)', provider: 'OpenRouter' },
|
49 |
-
{ name: 'x-ai/grok-beta', label: 'xAI Grok Beta (OpenRouter)', provider: 'OpenRouter' },
|
50 |
-
{ name: 'mistralai/mistral-nemo', label: 'OpenRouter Mistral Nemo (OpenRouter)', provider: 'OpenRouter' },
|
51 |
-
{ name: 'qwen/qwen-110b-chat', label: 'OpenRouter Qwen 110b Chat (OpenRouter)', provider: 'OpenRouter' },
|
52 |
-
{ name: 'cohere/command', label: 'Cohere Command (OpenRouter)', provider: 'OpenRouter' }
|
53 |
],
|
54 |
getDynamicModels: getOpenRouterModels,
|
55 |
getApiKeyLink: 'https://openrouter.ai/settings/keys',
|
@@ -57,54 +74,70 @@ const PROVIDER_LIST: ProviderInfo[] = [
|
|
57 |
}, {
|
58 |
name: 'Google',
|
59 |
staticModels: [
|
60 |
-
{ name: 'gemini-1.5-flash-latest', label: 'Gemini 1.5 Flash', provider: 'Google' },
|
61 |
-
{ name: 'gemini-1.5-
|
|
|
|
|
|
|
|
|
62 |
],
|
63 |
getApiKeyLink: 'https://aistudio.google.com/app/apikey'
|
64 |
}, {
|
65 |
name: 'Groq',
|
66 |
staticModels: [
|
67 |
-
{ name: 'llama-3.1-70b-versatile', label: 'Llama 3.1 70b (Groq)', provider: 'Groq' },
|
68 |
-
{ name: 'llama-3.1-8b-instant', label: 'Llama 3.1 8b (Groq)', provider: 'Groq' },
|
69 |
-
{ name: 'llama-3.2-11b-vision-preview', label: 'Llama 3.2 11b (Groq)', provider: 'Groq' },
|
70 |
-
{ name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq' },
|
71 |
-
{ name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq' }
|
72 |
],
|
73 |
getApiKeyLink: 'https://console.groq.com/keys'
|
74 |
-
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
name: 'OpenAI',
|
76 |
staticModels: [
|
77 |
-
{ name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI' },
|
78 |
-
{ name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'OpenAI' },
|
79 |
-
{ name: 'gpt-4', label: 'GPT-4', provider: 'OpenAI' },
|
80 |
-
{ name: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', provider: 'OpenAI' }
|
81 |
],
|
82 |
getApiKeyLink: "https://platform.openai.com/api-keys",
|
83 |
}, {
|
84 |
name: 'xAI',
|
85 |
staticModels: [
|
86 |
-
{ name: 'grok-beta', label: 'xAI Grok Beta', provider: 'xAI' }
|
87 |
],
|
88 |
getApiKeyLink: 'https://docs.x.ai/docs/quickstart#creating-an-api-key'
|
89 |
}, {
|
90 |
name: 'Deepseek',
|
91 |
staticModels: [
|
92 |
-
{ name: 'deepseek-coder', label: 'Deepseek-Coder', provider: 'Deepseek' },
|
93 |
-
{ name: 'deepseek-chat', label: 'Deepseek-Chat', provider: 'Deepseek' }
|
94 |
],
|
95 |
getApiKeyLink: 'https://platform.deepseek.com/api_keys'
|
96 |
}, {
|
97 |
name: 'Mistral',
|
98 |
staticModels: [
|
99 |
-
{ name: 'open-mistral-7b', label: 'Mistral 7B', provider: 'Mistral' },
|
100 |
-
{ name: 'open-mixtral-8x7b', label: 'Mistral 8x7B', provider: 'Mistral' },
|
101 |
-
{ name: 'open-mixtral-8x22b', label: 'Mistral 8x22B', provider: 'Mistral' },
|
102 |
-
{ name: 'open-codestral-mamba', label: 'Codestral Mamba', provider: 'Mistral' },
|
103 |
-
{ name: 'open-mistral-nemo', label: 'Mistral Nemo', provider: 'Mistral' },
|
104 |
-
{ name: 'ministral-8b-latest', label: 'Mistral 8B', provider: 'Mistral' },
|
105 |
-
{ name: 'mistral-small-latest', label: 'Mistral Small', provider: 'Mistral' },
|
106 |
-
{ name: 'codestral-latest', label: 'Codestral', provider: 'Mistral' },
|
107 |
-
{ name: 'mistral-large-latest', label: 'Mistral Large Latest', provider: 'Mistral' }
|
108 |
],
|
109 |
getApiKeyLink: 'https://console.mistral.ai/api-keys/'
|
110 |
}, {
|
@@ -148,7 +181,8 @@ async function getOllamaModels(): Promise<ModelInfo[]> {
|
|
148 |
return data.models.map((model: OllamaModel) => ({
|
149 |
name: model.name,
|
150 |
label: `${model.name} (${model.details.parameter_size})`,
|
151 |
-
provider: 'Ollama'
|
|
|
152 |
}));
|
153 |
} catch (e) {
|
154 |
return [];
|
@@ -201,8 +235,9 @@ async function getOpenRouterModels(): Promise<ModelInfo[]> {
|
|
201 |
name: m.id,
|
202 |
label: `${m.name} - in:$${(m.pricing.prompt * 1_000_000).toFixed(
|
203 |
2)} out:$${(m.pricing.completion * 1_000_000).toFixed(2)} - context ${Math.floor(
|
204 |
-
|
205 |
-
provider: 'OpenRouter'
|
|
|
206 |
}));
|
207 |
}
|
208 |
|
|
|
12 |
{
|
13 |
name: 'Anthropic',
|
14 |
staticModels: [
|
15 |
+
{ name: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet (new)', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
16 |
+
{ name: 'claude-3-5-sonnet-20240620', label: 'Claude 3.5 Sonnet (old)', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
17 |
+
{ name: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku (new)', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
18 |
+
{ name: 'claude-3-opus-latest', label: 'Claude 3 Opus', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
19 |
+
{ name: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
20 |
+
{ name: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', provider: 'Anthropic', maxTokenAllowed: 8000 }
|
21 |
],
|
22 |
getApiKeyLink: "https://console.anthropic.com/settings/keys",
|
23 |
},
|
|
|
33 |
staticModels: [],
|
34 |
getDynamicModels: getOpenAILikeModels
|
35 |
},
|
36 |
+
{
|
37 |
+
name: 'Cohere',
|
38 |
+
staticModels: [
|
39 |
+
{ name: 'command-r-plus-08-2024', label: 'Command R plus Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
|
40 |
+
{ name: 'command-r-08-2024', label: 'Command R Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
|
41 |
+
{ name: 'command-r-plus', label: 'Command R plus', provider: 'Cohere', maxTokenAllowed: 4096 },
|
42 |
+
{ name: 'command-r', label: 'Command R', provider: 'Cohere', maxTokenAllowed: 4096 },
|
43 |
+
{ name: 'command', label: 'Command', provider: 'Cohere', maxTokenAllowed: 4096 },
|
44 |
+
{ name: 'command-nightly', label: 'Command Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
|
45 |
+
{ name: 'command-light', label: 'Command Light', provider: 'Cohere', maxTokenAllowed: 4096 },
|
46 |
+
{ name: 'command-light-nightly', label: 'Command Light Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
|
47 |
+
{ name: 'c4ai-aya-expanse-8b', label: 'c4AI Aya Expanse 8b', provider: 'Cohere', maxTokenAllowed: 4096 },
|
48 |
+
{ name: 'c4ai-aya-expanse-32b', label: 'c4AI Aya Expanse 32b', provider: 'Cohere', maxTokenAllowed: 4096 },
|
49 |
+
],
|
50 |
+
getApiKeyLink: 'https://dashboard.cohere.com/api-keys'
|
51 |
+
},
|
52 |
{
|
53 |
name: 'OpenRouter',
|
54 |
staticModels: [
|
55 |
+
{ name: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
56 |
{
|
57 |
name: 'anthropic/claude-3.5-sonnet',
|
58 |
label: 'Anthropic: Claude 3.5 Sonnet (OpenRouter)',
|
59 |
provider: 'OpenRouter'
|
60 |
+
, maxTokenAllowed: 8000
|
61 |
},
|
62 |
+
{ name: 'anthropic/claude-3-haiku', label: 'Anthropic: Claude 3 Haiku (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
63 |
+
{ name: 'deepseek/deepseek-coder', label: 'Deepseek-Coder V2 236B (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
64 |
+
{ name: 'google/gemini-flash-1.5', label: 'Google Gemini Flash 1.5 (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
65 |
+
{ name: 'google/gemini-pro-1.5', label: 'Google Gemini Pro 1.5 (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
66 |
+
{ name: 'x-ai/grok-beta', label: 'xAI Grok Beta (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
67 |
+
{ name: 'mistralai/mistral-nemo', label: 'OpenRouter Mistral Nemo (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
68 |
+
{ name: 'qwen/qwen-110b-chat', label: 'OpenRouter Qwen 110b Chat (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
69 |
+
{ name: 'cohere/command', label: 'Cohere Command (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 4096 }
|
70 |
],
|
71 |
getDynamicModels: getOpenRouterModels,
|
72 |
getApiKeyLink: 'https://openrouter.ai/settings/keys',
|
|
|
74 |
}, {
|
75 |
name: 'Google',
|
76 |
staticModels: [
|
77 |
+
{ name: 'gemini-1.5-flash-latest', label: 'Gemini 1.5 Flash', provider: 'Google', maxTokenAllowed: 8192 },
|
78 |
+
{ name: 'gemini-1.5-flash-002', label: 'Gemini 1.5 Flash-002', provider: 'Google', maxTokenAllowed: 8192 },
|
79 |
+
{ name: 'gemini-1.5-flash-8b', label: 'Gemini 1.5 Flash-8b', provider: 'Google', maxTokenAllowed: 8192 },
|
80 |
+
{ name: 'gemini-1.5-pro-latest', label: 'Gemini 1.5 Pro', provider: 'Google', maxTokenAllowed: 8192 },
|
81 |
+
{ name: 'gemini-1.5-pro-002', label: 'Gemini 1.5 Pro-002', provider: 'Google', maxTokenAllowed: 8192 },
|
82 |
+
{ name: 'gemini-exp-1114', label: 'Gemini exp-1114', provider: 'Google', maxTokenAllowed: 8192 }
|
83 |
],
|
84 |
getApiKeyLink: 'https://aistudio.google.com/app/apikey'
|
85 |
}, {
|
86 |
name: 'Groq',
|
87 |
staticModels: [
|
88 |
+
{ name: 'llama-3.1-70b-versatile', label: 'Llama 3.1 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
89 |
+
{ name: 'llama-3.1-8b-instant', label: 'Llama 3.1 8b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
90 |
+
{ name: 'llama-3.2-11b-vision-preview', label: 'Llama 3.2 11b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
91 |
+
{ name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
92 |
+
{ name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 }
|
93 |
],
|
94 |
getApiKeyLink: 'https://console.groq.com/keys'
|
95 |
+
},
|
96 |
+
{
|
97 |
+
name: 'HuggingFace',
|
98 |
+
staticModels: [
|
99 |
+
{ name: 'Qwen/Qwen2.5-Coder-32B-Instruct', label: 'Qwen2.5-Coder-32B-Instruct (HuggingFace)', provider: 'HuggingFace', maxTokenAllowed: 8000 },
|
100 |
+
{ name: '01-ai/Yi-1.5-34B-Chat', label: 'Yi-1.5-34B-Chat (HuggingFace)', provider: 'HuggingFace', maxTokenAllowed: 8000 },
|
101 |
+
{ name: 'codellama/CodeLlama-34b-Instruct-hf', label: 'CodeLlama-34b-Instruct (HuggingFace)', provider: 'HuggingFace', maxTokenAllowed: 8000 },
|
102 |
+
{ name: 'NousResearch/Hermes-3-Llama-3.1-8B', label: 'Hermes-3-Llama-3.1-8B (HuggingFace)', provider: 'HuggingFace', maxTokenAllowed: 8000 }
|
103 |
+
],
|
104 |
+
getApiKeyLink: 'https://huggingface.co/settings/tokens'
|
105 |
+
},
|
106 |
+
|
107 |
+
{
|
108 |
name: 'OpenAI',
|
109 |
staticModels: [
|
110 |
+
{ name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
111 |
+
{ name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
112 |
+
{ name: 'gpt-4', label: 'GPT-4', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
113 |
+
{ name: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 }
|
114 |
],
|
115 |
getApiKeyLink: "https://platform.openai.com/api-keys",
|
116 |
}, {
|
117 |
name: 'xAI',
|
118 |
staticModels: [
|
119 |
+
{ name: 'grok-beta', label: 'xAI Grok Beta', provider: 'xAI', maxTokenAllowed: 8000 }
|
120 |
],
|
121 |
getApiKeyLink: 'https://docs.x.ai/docs/quickstart#creating-an-api-key'
|
122 |
}, {
|
123 |
name: 'Deepseek',
|
124 |
staticModels: [
|
125 |
+
{ name: 'deepseek-coder', label: 'Deepseek-Coder', provider: 'Deepseek', maxTokenAllowed: 8000 },
|
126 |
+
{ name: 'deepseek-chat', label: 'Deepseek-Chat', provider: 'Deepseek', maxTokenAllowed: 8000 }
|
127 |
],
|
128 |
getApiKeyLink: 'https://platform.deepseek.com/api_keys'
|
129 |
}, {
|
130 |
name: 'Mistral',
|
131 |
staticModels: [
|
132 |
+
{ name: 'open-mistral-7b', label: 'Mistral 7B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
133 |
+
{ name: 'open-mixtral-8x7b', label: 'Mistral 8x7B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
134 |
+
{ name: 'open-mixtral-8x22b', label: 'Mistral 8x22B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
135 |
+
{ name: 'open-codestral-mamba', label: 'Codestral Mamba', provider: 'Mistral', maxTokenAllowed: 8000 },
|
136 |
+
{ name: 'open-mistral-nemo', label: 'Mistral Nemo', provider: 'Mistral', maxTokenAllowed: 8000 },
|
137 |
+
{ name: 'ministral-8b-latest', label: 'Mistral 8B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
138 |
+
{ name: 'mistral-small-latest', label: 'Mistral Small', provider: 'Mistral', maxTokenAllowed: 8000 },
|
139 |
+
{ name: 'codestral-latest', label: 'Codestral', provider: 'Mistral', maxTokenAllowed: 8000 },
|
140 |
+
{ name: 'mistral-large-latest', label: 'Mistral Large Latest', provider: 'Mistral', maxTokenAllowed: 8000 }
|
141 |
],
|
142 |
getApiKeyLink: 'https://console.mistral.ai/api-keys/'
|
143 |
}, {
|
|
|
181 |
return data.models.map((model: OllamaModel) => ({
|
182 |
name: model.name,
|
183 |
label: `${model.name} (${model.details.parameter_size})`,
|
184 |
+
provider: 'Ollama',
|
185 |
+
maxTokenAllowed:8000,
|
186 |
}));
|
187 |
} catch (e) {
|
188 |
return [];
|
|
|
235 |
name: m.id,
|
236 |
label: `${m.name} - in:$${(m.pricing.prompt * 1_000_000).toFixed(
|
237 |
2)} out:$${(m.pricing.completion * 1_000_000).toFixed(2)} - context ${Math.floor(
|
238 |
+
m.context_length / 1000)}k`,
|
239 |
+
provider: 'OpenRouter',
|
240 |
+
maxTokenAllowed:8000,
|
241 |
}));
|
242 |
}
|
243 |
|
app/utils/diff.spec.ts
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { describe, expect, it } from 'vitest';
|
2 |
+
import { extractRelativePath } from './diff';
|
3 |
+
import { WORK_DIR } from './constants';
|
4 |
+
|
5 |
+
describe('Diff', () => {
|
6 |
+
it('should strip out Work_dir', () => {
|
7 |
+
const filePath = `${WORK_DIR}/index.js`;
|
8 |
+
const result = extractRelativePath(filePath);
|
9 |
+
expect(result).toBe('index.js');
|
10 |
+
});
|
11 |
+
});
|
app/utils/diff.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import { createTwoFilesPatch } from 'diff';
|
2 |
import type { FileMap } from '~/lib/stores/files';
|
3 |
-
import { MODIFICATIONS_TAG_NAME } from './constants';
|
4 |
|
5 |
export const modificationsRegex = new RegExp(
|
6 |
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,
|
@@ -75,6 +75,15 @@ export function diffFiles(fileName: string, oldFileContent: string, newFileConte
|
|
75 |
return unifiedDiff;
|
76 |
}
|
77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
/**
|
79 |
* Converts the unified diff to HTML.
|
80 |
*
|
|
|
1 |
import { createTwoFilesPatch } from 'diff';
|
2 |
import type { FileMap } from '~/lib/stores/files';
|
3 |
+
import { MODIFICATIONS_TAG_NAME, WORK_DIR } from './constants';
|
4 |
|
5 |
export const modificationsRegex = new RegExp(
|
6 |
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,
|
|
|
75 |
return unifiedDiff;
|
76 |
}
|
77 |
|
78 |
+
const regex = new RegExp(`^${WORK_DIR}\/`);
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Strips out the work directory from the file path.
|
82 |
+
*/
|
83 |
+
export function extractRelativePath(filePath: string) {
|
84 |
+
return filePath.replace(regex, '');
|
85 |
+
}
|
86 |
+
|
87 |
/**
|
88 |
* Converts the unified diff to HTML.
|
89 |
*
|
app/utils/types.ts
CHANGED
@@ -25,6 +25,7 @@ export interface ModelInfo {
|
|
25 |
name: string;
|
26 |
label: string;
|
27 |
provider: string;
|
|
|
28 |
}
|
29 |
|
30 |
export interface ProviderInfo {
|
|
|
25 |
name: string;
|
26 |
label: string;
|
27 |
provider: string;
|
28 |
+
maxTokenAllowed: number;
|
29 |
}
|
30 |
|
31 |
export interface ProviderInfo {
|
docker-compose.yaml
CHANGED
@@ -14,6 +14,7 @@ services:
|
|
14 |
# No strictly neded but serving as hints for Coolify
|
15 |
- PORT=5173
|
16 |
- GROQ_API_KEY=${GROQ_API_KEY}
|
|
|
17 |
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
18 |
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
19 |
- OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY}
|
@@ -41,6 +42,7 @@ services:
|
|
41 |
- WATCHPACK_POLLING=true
|
42 |
- PORT=5173
|
43 |
- GROQ_API_KEY=${GROQ_API_KEY}
|
|
|
44 |
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
45 |
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
46 |
- OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY}
|
|
|
14 |
# No strictly neded 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}
|
|
|
42 |
- WATCHPACK_POLLING=true
|
43 |
- PORT=5173
|
44 |
- GROQ_API_KEY=${GROQ_API_KEY}
|
45 |
+
- HuggingFace_API_KEY=${HuggingFace_API_KEY}
|
46 |
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
47 |
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
48 |
- OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY}
|
eslint.config.mjs
CHANGED
@@ -4,7 +4,7 @@ import { getNamingConventionRule, tsFileExtensions } from '@blitz/eslint-plugin/
|
|
4 |
|
5 |
export default [
|
6 |
{
|
7 |
-
ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build'],
|
8 |
},
|
9 |
...blitzPlugin.configs.recommended(),
|
10 |
{
|
|
|
4 |
|
5 |
export default [
|
6 |
{
|
7 |
+
ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build', '**/.history'],
|
8 |
},
|
9 |
...blitzPlugin.configs.recommended(),
|
10 |
{
|
package.json
CHANGED
@@ -27,6 +27,7 @@
|
|
27 |
},
|
28 |
"dependencies": {
|
29 |
"@ai-sdk/anthropic": "^0.0.39",
|
|
|
30 |
"@ai-sdk/google": "^0.0.52",
|
31 |
"@ai-sdk/mistral": "^0.0.43",
|
32 |
"@ai-sdk/openai": "^0.0.66",
|
@@ -54,6 +55,7 @@
|
|
54 |
"@openrouter/ai-sdk-provider": "^0.0.5",
|
55 |
"@radix-ui/react-dialog": "^1.1.1",
|
56 |
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
|
|
57 |
"@remix-run/cloudflare": "^2.10.2",
|
58 |
"@remix-run/cloudflare-pages": "^2.10.2",
|
59 |
"@remix-run/react": "^2.10.2",
|
|
|
27 |
},
|
28 |
"dependencies": {
|
29 |
"@ai-sdk/anthropic": "^0.0.39",
|
30 |
+
"@ai-sdk/cohere": "^1.0.1",
|
31 |
"@ai-sdk/google": "^0.0.52",
|
32 |
"@ai-sdk/mistral": "^0.0.43",
|
33 |
"@ai-sdk/openai": "^0.0.66",
|
|
|
55 |
"@openrouter/ai-sdk-provider": "^0.0.5",
|
56 |
"@radix-ui/react-dialog": "^1.1.1",
|
57 |
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
58 |
+
"@radix-ui/react-tooltip": "^1.1.4",
|
59 |
"@remix-run/cloudflare": "^2.10.2",
|
60 |
"@remix-run/cloudflare-pages": "^2.10.2",
|
61 |
"@remix-run/react": "^2.10.2",
|
pnpm-lock.yaml
CHANGED
@@ -14,6 +14,9 @@ importers:
|
|
14 |
'@ai-sdk/anthropic':
|
15 |
specifier: ^0.0.39
|
16 |
version: 0.0.39([email protected])
|
|
|
|
|
|
|
17 |
'@ai-sdk/google':
|
18 |
specifier: ^0.0.52
|
19 |
version: 0.0.52([email protected])
|
@@ -95,6 +98,9 @@ importers:
|
|
95 |
'@radix-ui/react-dropdown-menu':
|
96 |
specifier: ^2.1.1
|
97 |
version: 2.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
|
|
|
|
|
|
98 |
'@remix-run/cloudflare':
|
99 |
specifier: ^2.10.2
|
100 |
version: 2.10.2(@cloudflare/[email protected])([email protected])
|
@@ -276,6 +282,12 @@ packages:
|
|
276 |
peerDependencies:
|
277 |
zod: ^3.0.0
|
278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
'@ai-sdk/[email protected]':
|
280 |
resolution: {integrity: sha512-bfsA/1Ae0SQ6NfLwWKs5SU4MBwlzJjVhK6bTVBicYFjUxg9liK/W76P1Tq/qK9OlrODACz3i1STOIWsFPpIOuQ==}
|
281 |
engines: {node: '>=18'}
|
@@ -321,6 +333,15 @@ packages:
|
|
321 |
zod:
|
322 |
optional: true
|
323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
'@ai-sdk/[email protected]':
|
325 |
resolution: {integrity: sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==}
|
326 |
engines: {node: '>=18'}
|
@@ -333,6 +354,10 @@ packages:
|
|
333 |
resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==}
|
334 |
engines: {node: '>=18'}
|
335 |
|
|
|
|
|
|
|
|
|
336 |
'@ai-sdk/[email protected]':
|
337 |
resolution: {integrity: sha512-1asDpxgmeHWL0/EZPCLENxfOHT+0jce0z/zasRhascodm2S6f6/KZn5doLG9jdmarcb+GjMjFmmwyOVXz3W1xg==}
|
338 |
engines: {node: '>=18'}
|
@@ -1377,6 +1402,15 @@ packages:
|
|
1377 |
'@types/react':
|
1378 |
optional: true
|
1379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1380 |
'@radix-ui/[email protected]':
|
1381 |
resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==}
|
1382 |
peerDependencies:
|
@@ -1412,6 +1446,19 @@ packages:
|
|
1412 |
'@types/react-dom':
|
1413 |
optional: true
|
1414 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1415 |
'@radix-ui/[email protected]':
|
1416 |
resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==}
|
1417 |
peerDependencies:
|
@@ -1495,6 +1542,19 @@ packages:
|
|
1495 |
'@types/react-dom':
|
1496 |
optional: true
|
1497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1498 |
'@radix-ui/[email protected]':
|
1499 |
resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==}
|
1500 |
peerDependencies:
|
@@ -1508,6 +1568,19 @@ packages:
|
|
1508 |
'@types/react-dom':
|
1509 |
optional: true
|
1510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1511 |
'@radix-ui/[email protected]':
|
1512 |
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
|
1513 |
peerDependencies:
|
@@ -1543,6 +1616,19 @@ packages:
|
|
1543 |
'@types/react':
|
1544 |
optional: true
|
1545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1546 |
'@radix-ui/[email protected]':
|
1547 |
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
1548 |
peerDependencies:
|
@@ -1597,6 +1683,19 @@ packages:
|
|
1597 |
'@types/react':
|
1598 |
optional: true
|
1599 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1600 |
'@radix-ui/[email protected]':
|
1601 |
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
1602 |
|
@@ -2956,6 +3055,10 @@ packages:
|
|
2956 |
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
|
2957 |
engines: {node: '>=14.18'}
|
2958 |
|
|
|
|
|
|
|
|
|
2959 | |
2960 |
resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
|
2961 |
|
@@ -5610,6 +5713,12 @@ snapshots:
|
|
5610 |
'@ai-sdk/provider-utils': 1.0.9([email protected])
|
5611 |
zod: 3.23.8
|
5612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
5613 |
'@ai-sdk/[email protected]([email protected])':
|
5614 |
dependencies:
|
5615 |
'@ai-sdk/provider': 0.0.24
|
@@ -5656,6 +5765,15 @@ snapshots:
|
|
5656 |
optionalDependencies:
|
5657 |
zod: 3.23.8
|
5658 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5659 |
'@ai-sdk/[email protected]':
|
5660 |
dependencies:
|
5661 |
json-schema: 0.4.0
|
@@ -5668,6 +5786,10 @@ snapshots:
|
|
5668 |
dependencies:
|
5669 |
json-schema: 0.4.0
|
5670 |
|
|
|
|
|
|
|
|
|
5671 |
'@ai-sdk/[email protected]([email protected])([email protected])':
|
5672 |
dependencies:
|
5673 |
'@ai-sdk/provider-utils': 1.0.20([email protected])
|
@@ -6712,6 +6834,12 @@ snapshots:
|
|
6712 |
optionalDependencies:
|
6713 |
'@types/react': 18.3.3
|
6714 |
|
|
|
|
|
|
|
|
|
|
|
|
|
6715 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6716 |
dependencies:
|
6717 |
'@radix-ui/primitive': 1.1.0
|
@@ -6753,6 +6881,19 @@ snapshots:
|
|
6753 |
'@types/react': 18.3.3
|
6754 |
'@types/react-dom': 18.3.0
|
6755 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6756 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6757 |
dependencies:
|
6758 |
'@radix-ui/primitive': 1.1.0
|
@@ -6846,6 +6987,16 @@ snapshots:
|
|
6846 |
'@types/react': 18.3.3
|
6847 |
'@types/react-dom': 18.3.0
|
6848 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6849 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6850 |
dependencies:
|
6851 |
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
|
@@ -6856,6 +7007,16 @@ snapshots:
|
|
6856 |
'@types/react': 18.3.3
|
6857 |
'@types/react-dom': 18.3.0
|
6858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6859 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6860 |
dependencies:
|
6861 |
'@radix-ui/react-slot': 1.1.0(@types/[email protected])([email protected])
|
@@ -6889,6 +7050,26 @@ snapshots:
|
|
6889 |
optionalDependencies:
|
6890 |
'@types/react': 18.3.3
|
6891 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6892 |
'@radix-ui/[email protected](@types/[email protected])([email protected])':
|
6893 |
dependencies:
|
6894 |
react: 18.3.1
|
@@ -6929,6 +7110,15 @@ snapshots:
|
|
6929 |
optionalDependencies:
|
6930 |
'@types/react': 18.3.3
|
6931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6932 |
'@radix-ui/[email protected]': {}
|
6933 |
|
6934 |
'@remix-run/[email protected](@cloudflare/[email protected])([email protected])':
|
@@ -8606,6 +8796,8 @@ snapshots:
|
|
8606 |
|
8607 | |
8608 |
|
|
|
|
|
8609 | |
8610 |
dependencies:
|
8611 |
md5.js: 1.3.5
|
|
|
14 |
'@ai-sdk/anthropic':
|
15 |
specifier: ^0.0.39
|
16 |
version: 0.0.39([email protected])
|
17 |
+
'@ai-sdk/cohere':
|
18 |
+
specifier: ^1.0.1
|
19 |
+
version: 1.0.1([email protected])
|
20 |
'@ai-sdk/google':
|
21 |
specifier: ^0.0.52
|
22 |
version: 0.0.52([email protected])
|
|
|
98 |
'@radix-ui/react-dropdown-menu':
|
99 |
specifier: ^2.1.1
|
100 |
version: 2.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
101 |
+
'@radix-ui/react-tooltip':
|
102 |
+
specifier: ^1.1.4
|
103 |
+
version: 1.1.4(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
104 |
'@remix-run/cloudflare':
|
105 |
specifier: ^2.10.2
|
106 |
version: 2.10.2(@cloudflare/[email protected])([email protected])
|
|
|
282 |
peerDependencies:
|
283 |
zod: ^3.0.0
|
284 |
|
285 |
+
'@ai-sdk/[email protected]':
|
286 |
+
resolution: {integrity: sha512-xLaSYl/hs9EqfpvT9PvqZrDWjJPQPZBd0iT32T6812vN6kwuEQ6sSgQvqHWczIqxeej2GNRgMQwDL6Lh0L5pZw==}
|
287 |
+
engines: {node: '>=18'}
|
288 |
+
peerDependencies:
|
289 |
+
zod: ^3.0.0
|
290 |
+
|
291 |
'@ai-sdk/[email protected]':
|
292 |
resolution: {integrity: sha512-bfsA/1Ae0SQ6NfLwWKs5SU4MBwlzJjVhK6bTVBicYFjUxg9liK/W76P1Tq/qK9OlrODACz3i1STOIWsFPpIOuQ==}
|
293 |
engines: {node: '>=18'}
|
|
|
333 |
zod:
|
334 |
optional: true
|
335 |
|
336 |
+
'@ai-sdk/[email protected]':
|
337 |
+
resolution: {integrity: sha512-TNg7rPhRtETB2Z9F0JpOvpGii9Fs8EWM8nYy1jEkvSXkrPJ6b/9zVnDdaJsmLFDyrMbOsPJlkblYtmYEQou36w==}
|
338 |
+
engines: {node: '>=18'}
|
339 |
+
peerDependencies:
|
340 |
+
zod: ^3.0.0
|
341 |
+
peerDependenciesMeta:
|
342 |
+
zod:
|
343 |
+
optional: true
|
344 |
+
|
345 |
'@ai-sdk/[email protected]':
|
346 |
resolution: {integrity: sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==}
|
347 |
engines: {node: '>=18'}
|
|
|
354 |
resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==}
|
355 |
engines: {node: '>=18'}
|
356 |
|
357 |
+
'@ai-sdk/[email protected]':
|
358 |
+
resolution: {integrity: sha512-Sj29AzooJ7SYvhPd+AAWt/E7j63E9+AzRnoMHUaJPRYzOd/WDrVNxxv85prF9gDcQ7XPVlSk9j6oAZV9/DXYpA==}
|
359 |
+
engines: {node: '>=18'}
|
360 |
+
|
361 |
'@ai-sdk/[email protected]':
|
362 |
resolution: {integrity: sha512-1asDpxgmeHWL0/EZPCLENxfOHT+0jce0z/zasRhascodm2S6f6/KZn5doLG9jdmarcb+GjMjFmmwyOVXz3W1xg==}
|
363 |
engines: {node: '>=18'}
|
|
|
1402 |
'@types/react':
|
1403 |
optional: true
|
1404 |
|
1405 |
+
'@radix-ui/[email protected]':
|
1406 |
+
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
1407 |
+
peerDependencies:
|
1408 |
+
'@types/react': '*'
|
1409 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1410 |
+
peerDependenciesMeta:
|
1411 |
+
'@types/react':
|
1412 |
+
optional: true
|
1413 |
+
|
1414 |
'@radix-ui/[email protected]':
|
1415 |
resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==}
|
1416 |
peerDependencies:
|
|
|
1446 |
'@types/react-dom':
|
1447 |
optional: true
|
1448 |
|
1449 |
+
'@radix-ui/[email protected]':
|
1450 |
+
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
|
1451 |
+
peerDependencies:
|
1452 |
+
'@types/react': '*'
|
1453 |
+
'@types/react-dom': '*'
|
1454 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1455 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1456 |
+
peerDependenciesMeta:
|
1457 |
+
'@types/react':
|
1458 |
+
optional: true
|
1459 |
+
'@types/react-dom':
|
1460 |
+
optional: true
|
1461 |
+
|
1462 |
'@radix-ui/[email protected]':
|
1463 |
resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==}
|
1464 |
peerDependencies:
|
|
|
1542 |
'@types/react-dom':
|
1543 |
optional: true
|
1544 |
|
1545 |
+
'@radix-ui/[email protected]':
|
1546 |
+
resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
|
1547 |
+
peerDependencies:
|
1548 |
+
'@types/react': '*'
|
1549 |
+
'@types/react-dom': '*'
|
1550 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1551 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1552 |
+
peerDependenciesMeta:
|
1553 |
+
'@types/react':
|
1554 |
+
optional: true
|
1555 |
+
'@types/react-dom':
|
1556 |
+
optional: true
|
1557 |
+
|
1558 |
'@radix-ui/[email protected]':
|
1559 |
resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==}
|
1560 |
peerDependencies:
|
|
|
1568 |
'@types/react-dom':
|
1569 |
optional: true
|
1570 |
|
1571 |
+
'@radix-ui/[email protected]':
|
1572 |
+
resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
|
1573 |
+
peerDependencies:
|
1574 |
+
'@types/react': '*'
|
1575 |
+
'@types/react-dom': '*'
|
1576 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1577 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1578 |
+
peerDependenciesMeta:
|
1579 |
+
'@types/react':
|
1580 |
+
optional: true
|
1581 |
+
'@types/react-dom':
|
1582 |
+
optional: true
|
1583 |
+
|
1584 |
'@radix-ui/[email protected]':
|
1585 |
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
|
1586 |
peerDependencies:
|
|
|
1616 |
'@types/react':
|
1617 |
optional: true
|
1618 |
|
1619 |
+
'@radix-ui/[email protected]':
|
1620 |
+
resolution: {integrity: sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==}
|
1621 |
+
peerDependencies:
|
1622 |
+
'@types/react': '*'
|
1623 |
+
'@types/react-dom': '*'
|
1624 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1625 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1626 |
+
peerDependenciesMeta:
|
1627 |
+
'@types/react':
|
1628 |
+
optional: true
|
1629 |
+
'@types/react-dom':
|
1630 |
+
optional: true
|
1631 |
+
|
1632 |
'@radix-ui/[email protected]':
|
1633 |
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
1634 |
peerDependencies:
|
|
|
1683 |
'@types/react':
|
1684 |
optional: true
|
1685 |
|
1686 |
+
'@radix-ui/[email protected]':
|
1687 |
+
resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==}
|
1688 |
+
peerDependencies:
|
1689 |
+
'@types/react': '*'
|
1690 |
+
'@types/react-dom': '*'
|
1691 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1692 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
1693 |
+
peerDependenciesMeta:
|
1694 |
+
'@types/react':
|
1695 |
+
optional: true
|
1696 |
+
'@types/react-dom':
|
1697 |
+
optional: true
|
1698 |
+
|
1699 |
'@radix-ui/[email protected]':
|
1700 |
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
1701 |
|
|
|
3055 |
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
|
3056 |
engines: {node: '>=14.18'}
|
3057 |
|
3058 | |
3059 |
+
resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==}
|
3060 |
+
engines: {node: '>=18.0.0'}
|
3061 |
+
|
3062 | |
3063 |
resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
|
3064 |
|
|
|
5713 |
'@ai-sdk/provider-utils': 1.0.9([email protected])
|
5714 |
zod: 3.23.8
|
5715 |
|
5716 |
+
'@ai-sdk/[email protected]([email protected])':
|
5717 |
+
dependencies:
|
5718 |
+
'@ai-sdk/provider': 1.0.0
|
5719 |
+
'@ai-sdk/provider-utils': 2.0.1([email protected])
|
5720 |
+
zod: 3.23.8
|
5721 |
+
|
5722 |
'@ai-sdk/[email protected]([email protected])':
|
5723 |
dependencies:
|
5724 |
'@ai-sdk/provider': 0.0.24
|
|
|
5765 |
optionalDependencies:
|
5766 |
zod: 3.23.8
|
5767 |
|
5768 |
+
'@ai-sdk/[email protected]([email protected])':
|
5769 |
+
dependencies:
|
5770 |
+
'@ai-sdk/provider': 1.0.0
|
5771 |
+
eventsource-parser: 3.0.0
|
5772 |
+
nanoid: 3.3.7
|
5773 |
+
secure-json-parse: 2.7.0
|
5774 |
+
optionalDependencies:
|
5775 |
+
zod: 3.23.8
|
5776 |
+
|
5777 |
'@ai-sdk/[email protected]':
|
5778 |
dependencies:
|
5779 |
json-schema: 0.4.0
|
|
|
5786 |
dependencies:
|
5787 |
json-schema: 0.4.0
|
5788 |
|
5789 |
+
'@ai-sdk/[email protected]':
|
5790 |
+
dependencies:
|
5791 |
+
json-schema: 0.4.0
|
5792 |
+
|
5793 |
'@ai-sdk/[email protected]([email protected])([email protected])':
|
5794 |
dependencies:
|
5795 |
'@ai-sdk/provider-utils': 1.0.20([email protected])
|
|
|
6834 |
optionalDependencies:
|
6835 |
'@types/react': 18.3.3
|
6836 |
|
6837 |
+
'@radix-ui/[email protected](@types/[email protected])([email protected])':
|
6838 |
+
dependencies:
|
6839 |
+
react: 18.3.1
|
6840 |
+
optionalDependencies:
|
6841 |
+
'@types/react': 18.3.3
|
6842 |
+
|
6843 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6844 |
dependencies:
|
6845 |
'@radix-ui/primitive': 1.1.0
|
|
|
6881 |
'@types/react': 18.3.3
|
6882 |
'@types/react-dom': 18.3.0
|
6883 |
|
6884 |
+
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6885 |
+
dependencies:
|
6886 |
+
'@radix-ui/primitive': 1.1.0
|
6887 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
|
6888 |
+
'@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
6889 |
+
'@radix-ui/react-use-callback-ref': 1.1.0(@types/[email protected])([email protected])
|
6890 |
+
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/[email protected])([email protected])
|
6891 |
+
react: 18.3.1
|
6892 |
+
react-dom: 18.3.1([email protected])
|
6893 |
+
optionalDependencies:
|
6894 |
+
'@types/react': 18.3.3
|
6895 |
+
'@types/react-dom': 18.3.0
|
6896 |
+
|
6897 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6898 |
dependencies:
|
6899 |
'@radix-ui/primitive': 1.1.0
|
|
|
6987 |
'@types/react': 18.3.3
|
6988 |
'@types/react-dom': 18.3.0
|
6989 |
|
6990 |
+
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
6991 |
+
dependencies:
|
6992 |
+
'@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
6993 |
+
'@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
|
6994 |
+
react: 18.3.1
|
6995 |
+
react-dom: 18.3.1([email protected])
|
6996 |
+
optionalDependencies:
|
6997 |
+
'@types/react': 18.3.3
|
6998 |
+
'@types/react-dom': 18.3.0
|
6999 |
+
|
7000 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
7001 |
dependencies:
|
7002 |
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
|
|
|
7007 |
'@types/react': 18.3.3
|
7008 |
'@types/react-dom': 18.3.0
|
7009 |
|
7010 |
+
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
7011 |
+
dependencies:
|
7012 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
|
7013 |
+
'@radix-ui/react-use-layout-effect': 1.1.0(@types/[email protected])([email protected])
|
7014 |
+
react: 18.3.1
|
7015 |
+
react-dom: 18.3.1([email protected])
|
7016 |
+
optionalDependencies:
|
7017 |
+
'@types/react': 18.3.3
|
7018 |
+
'@types/react-dom': 18.3.0
|
7019 |
+
|
7020 |
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
7021 |
dependencies:
|
7022 |
'@radix-ui/react-slot': 1.1.0(@types/[email protected])([email protected])
|
|
|
7050 |
optionalDependencies:
|
7051 |
'@types/react': 18.3.3
|
7052 |
|
7053 |
+
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
7054 |
+
dependencies:
|
7055 |
+
'@radix-ui/primitive': 1.1.0
|
7056 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
|
7057 |
+
'@radix-ui/react-context': 1.1.1(@types/[email protected])([email protected])
|
7058 |
+
'@radix-ui/react-dismissable-layer': 1.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7059 |
+
'@radix-ui/react-id': 1.1.0(@types/[email protected])([email protected])
|
7060 |
+
'@radix-ui/react-popper': 1.2.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7061 |
+
'@radix-ui/react-portal': 1.1.2(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7062 |
+
'@radix-ui/react-presence': 1.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7063 |
+
'@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7064 |
+
'@radix-ui/react-slot': 1.1.0(@types/[email protected])([email protected])
|
7065 |
+
'@radix-ui/react-use-controllable-state': 1.1.0(@types/[email protected])([email protected])
|
7066 |
+
'@radix-ui/react-visually-hidden': 1.1.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7067 |
+
react: 18.3.1
|
7068 |
+
react-dom: 18.3.1([email protected])
|
7069 |
+
optionalDependencies:
|
7070 |
+
'@types/react': 18.3.3
|
7071 |
+
'@types/react-dom': 18.3.0
|
7072 |
+
|
7073 |
'@radix-ui/[email protected](@types/[email protected])([email protected])':
|
7074 |
dependencies:
|
7075 |
react: 18.3.1
|
|
|
7110 |
optionalDependencies:
|
7111 |
'@types/react': 18.3.3
|
7112 |
|
7113 |
+
'@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
|
7114 |
+
dependencies:
|
7115 |
+
'@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
7116 |
+
react: 18.3.1
|
7117 |
+
react-dom: 18.3.1([email protected])
|
7118 |
+
optionalDependencies:
|
7119 |
+
'@types/react': 18.3.3
|
7120 |
+
'@types/react-dom': 18.3.0
|
7121 |
+
|
7122 |
'@radix-ui/[email protected]': {}
|
7123 |
|
7124 |
'@remix-run/[email protected](@cloudflare/[email protected])([email protected])':
|
|
|
8796 |
|
8797 | |
8798 |
|
8799 |
+
[email protected]: {}
|
8800 |
+
|
8801 | |
8802 |
dependencies:
|
8803 |
md5.js: 1.3.5
|
worker-configuration.d.ts
CHANGED
@@ -2,6 +2,7 @@ interface Env {
|
|
2 |
ANTHROPIC_API_KEY: string;
|
3 |
OPENAI_API_KEY: string;
|
4 |
GROQ_API_KEY: string;
|
|
|
5 |
OPEN_ROUTER_API_KEY: string;
|
6 |
OLLAMA_API_BASE_URL: string;
|
7 |
OPENAI_LIKE_API_KEY: string;
|
|
|
2 |
ANTHROPIC_API_KEY: string;
|
3 |
OPENAI_API_KEY: string;
|
4 |
GROQ_API_KEY: string;
|
5 |
+
HuggingFace_API_KEY: string;
|
6 |
OPEN_ROUTER_API_KEY: string;
|
7 |
OLLAMA_API_BASE_URL: string;
|
8 |
OPENAI_LIKE_API_KEY: string;
|