Merge remote-tracking branch 'upstream/main'
Browse files- .env.example +7 -1
- .husky/commit-msg +0 -7
- CONTRIBUTING.md +1 -0
- Dockerfile +4 -0
- app/components/chat/Artifact.tsx +13 -1
- app/components/chat/Messages.client.tsx +108 -32
- app/components/sidebar/HistoryItem.tsx +10 -2
- app/components/sidebar/Menu.client.tsx +19 -2
- app/lib/.server/llm/api-key.ts +2 -0
- app/lib/.server/llm/model.ts +11 -0
- app/lib/.server/llm/prompts.ts +2 -2
- 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/utils/constants.ts +13 -1
- app/utils/diff.spec.ts +11 -0
- app/utils/diff.ts +10 -1
- docker-compose.yaml +2 -0
- eslint.config.mjs +1 -1
- package.json +1 -0
- pnpm-lock.yaml +145 -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
|
|
@@ -55,4 +61,4 @@ LMSTUDIO_API_BASE_URL=
|
|
| 55 |
XAI_API_KEY=
|
| 56 |
|
| 57 |
# Include this environment variable if you want more logging for debugging locally
|
| 58 |
-
VITE_LOG_LEVEL=debug
|
|
|
|
| 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
|
|
|
|
| 61 |
XAI_API_KEY=
|
| 62 |
|
| 63 |
# Include this environment variable if you want more logging for debugging locally
|
| 64 |
+
VITE_LOG_LEVEL=debug
|
.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
|
@@ -72,6 +72,7 @@ pnpm install
|
|
| 72 |
- Add your LLM API keys (only set the ones you plan to use):
|
| 73 |
```bash
|
| 74 |
GROQ_API_KEY=XXX
|
|
|
|
| 75 |
OPENAI_API_KEY=XXX
|
| 76 |
ANTHROPIC_API_KEY=XXX
|
| 77 |
...
|
|
|
|
| 72 |
- Add your LLM API keys (only set the ones you plan to use):
|
| 73 |
```bash
|
| 74 |
GROQ_API_KEY=XXX
|
| 75 |
+
HuggingFace_API_KEY=XXX
|
| 76 |
OPENAI_API_KEY=XXX
|
| 77 |
ANTHROPIC_API_KEY=XXX
|
| 78 |
...
|
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
|
|
@@ -28,6 +29,7 @@ ARG VITE_LOG_LEVEL=debug
|
|
| 28 |
|
| 29 |
ENV WRANGLER_SEND_METRICS=false \
|
| 30 |
GROQ_API_KEY=${GROQ_API_KEY} \
|
|
|
|
| 31 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
| 32 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
| 33 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
|
@@ -48,6 +50,7 @@ FROM base AS bolt-ai-development
|
|
| 48 |
|
| 49 |
# Define the same environment variables for development
|
| 50 |
ARG GROQ_API_KEY
|
|
|
|
| 51 |
ARG OPENAI_API_KEY
|
| 52 |
ARG ANTHROPIC_API_KEY
|
| 53 |
ARG OPEN_ROUTER_API_KEY
|
|
@@ -56,6 +59,7 @@ ARG OLLAMA_API_BASE_URL
|
|
| 56 |
ARG VITE_LOG_LEVEL=debug
|
| 57 |
|
| 58 |
ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
|
|
|
| 59 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
| 60 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
| 61 |
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
|
|
|
|
| 29 |
|
| 30 |
ENV WRANGLER_SEND_METRICS=false \
|
| 31 |
GROQ_API_KEY=${GROQ_API_KEY} \
|
| 32 |
+
HuggingFace_KEY=${HuggingFace_API_KEY} \
|
| 33 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
| 34 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
| 35 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
|
|
|
| 50 |
|
| 51 |
# Define the same environment variables for development
|
| 52 |
ARG GROQ_API_KEY
|
| 53 |
+
ARG HuggingFace
|
| 54 |
ARG OPENAI_API_KEY
|
| 55 |
ARG ANTHROPIC_API_KEY
|
| 56 |
ARG OPEN_ROUTER_API_KEY
|
|
|
|
| 59 |
ARG VITE_LOG_LEVEL=debug
|
| 60 |
|
| 61 |
ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
| 62 |
+
HuggingFace_API_KEY=${HuggingFace_API_KEY} \
|
| 63 |
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
| 64 |
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
| 65 |
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
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/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,112 @@ 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, useNavigate } 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 |
+
const navigate = useNavigate();
|
| 23 |
+
|
| 24 |
+
const handleRewind = (messageId: string) => {
|
| 25 |
+
const searchParams = new URLSearchParams(location.search);
|
| 26 |
+
searchParams.set('rewindTo', messageId);
|
| 27 |
+
window.location.search = searchParams.toString();
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const handleFork = async (messageId: string) => {
|
| 31 |
+
try {
|
| 32 |
+
if (!db || !chatId.get()) {
|
| 33 |
+
toast.error('Chat persistence is not available');
|
| 34 |
+
return;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
const urlId = await forkChat(db, chatId.get()!, messageId);
|
| 38 |
+
window.location.href = `/chat/${urlId}`;
|
| 39 |
+
} catch (error) {
|
| 40 |
+
toast.error('Failed to fork chat: ' + (error as Error).message);
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
|
| 44 |
return (
|
| 45 |
+
<Tooltip.Provider delayDuration={200}>
|
| 46 |
+
<div id={id} ref={ref} className={props.className}>
|
| 47 |
+
{messages.length > 0
|
| 48 |
+
? messages.map((message, index) => {
|
| 49 |
+
const { role, content, id: messageId } = message;
|
| 50 |
+
const isUserMessage = role === 'user';
|
| 51 |
+
const isFirst = index === 0;
|
| 52 |
+
const isLast = index === messages.length - 1;
|
| 53 |
+
|
| 54 |
+
return (
|
| 55 |
+
<div
|
| 56 |
+
key={index}
|
| 57 |
+
className={classNames('flex gap-4 p-6 w-full rounded-[calc(0.75rem-1px)]', {
|
| 58 |
+
'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),
|
| 59 |
+
'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':
|
| 60 |
+
isStreaming && isLast,
|
| 61 |
+
'mt-4': !isFirst,
|
| 62 |
+
})}
|
| 63 |
+
>
|
| 64 |
+
{isUserMessage && (
|
| 65 |
+
<div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">
|
| 66 |
+
<div className="i-ph:user-fill text-xl"></div>
|
| 67 |
+
</div>
|
| 68 |
+
)}
|
| 69 |
+
<div className="grid grid-col-1 w-full">
|
| 70 |
+
{isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
|
| 71 |
</div>
|
| 72 |
+
{!isUserMessage && (<div className="flex gap-2">
|
| 73 |
+
<Tooltip.Root>
|
| 74 |
+
<Tooltip.Trigger asChild>
|
| 75 |
+
{messageId && (<button
|
| 76 |
+
onClick={() => handleRewind(messageId)}
|
| 77 |
+
key='i-ph:arrow-u-up-left'
|
| 78 |
+
className={classNames(
|
| 79 |
+
'i-ph:arrow-u-up-left',
|
| 80 |
+
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
|
| 81 |
+
)}
|
| 82 |
+
/>)}
|
| 83 |
+
</Tooltip.Trigger>
|
| 84 |
+
<Tooltip.Portal>
|
| 85 |
+
<Tooltip.Content
|
| 86 |
+
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
|
| 87 |
+
sideOffset={5}
|
| 88 |
+
style={{zIndex: 1000}}
|
| 89 |
+
>
|
| 90 |
+
Revert to this message
|
| 91 |
+
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
|
| 92 |
+
</Tooltip.Content>
|
| 93 |
+
</Tooltip.Portal>
|
| 94 |
+
</Tooltip.Root>
|
| 95 |
+
|
| 96 |
+
<Tooltip.Root>
|
| 97 |
+
<Tooltip.Trigger asChild>
|
| 98 |
+
<button
|
| 99 |
+
onClick={() => handleFork(messageId)}
|
| 100 |
+
key='i-ph:git-fork'
|
| 101 |
+
className={classNames(
|
| 102 |
+
'i-ph:git-fork',
|
| 103 |
+
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
|
| 104 |
+
)}
|
| 105 |
+
/>
|
| 106 |
+
</Tooltip.Trigger>
|
| 107 |
+
<Tooltip.Portal>
|
| 108 |
+
<Tooltip.Content
|
| 109 |
+
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
|
| 110 |
+
sideOffset={5}
|
| 111 |
+
style={{zIndex: 1000}}
|
| 112 |
+
>
|
| 113 |
+
Fork chat from this message
|
| 114 |
+
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
|
| 115 |
+
</Tooltip.Content>
|
| 116 |
+
</Tooltip.Portal>
|
| 117 |
+
</Tooltip.Root>
|
| 118 |
+
</div>)}
|
| 119 |
</div>
|
| 120 |
+
);
|
| 121 |
+
})
|
| 122 |
+
: null}
|
| 123 |
+
{isStreaming && (
|
| 124 |
+
<div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div>
|
| 125 |
+
)}
|
| 126 |
+
</div>
|
| 127 |
+
</Tooltip.Provider>
|
| 128 |
);
|
| 129 |
});
|
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 const Menu = () => {
|
|
|
|
| 37 |
const menuRef = useRef<HTMLDivElement>(null);
|
| 38 |
const [list, setList] = useState<ChatHistoryItem[]>([]);
|
| 39 |
const [open, setOpen] = useState(false);
|
|
@@ -99,6 +100,17 @@ export const Menu = () => {
|
|
| 99 |
};
|
| 100 |
}, []);
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
return (
|
| 103 |
<motion.div
|
| 104 |
ref={menuRef}
|
|
@@ -128,7 +140,12 @@ export const 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 const 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/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':
|
|
|
|
| 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':
|
app/lib/.server/llm/model.ts
CHANGED
|
@@ -56,6 +56,15 @@ export function getGroqModel(apiKey: string, model: string) {
|
|
| 56 |
return openai(model);
|
| 57 |
}
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
export function getOllamaModel(baseURL: string, model: string) {
|
| 60 |
let Ollama = ollama(model, {
|
| 61 |
numCtx: 32768,
|
|
@@ -110,6 +119,8 @@ export function getModel(provider: string, model: string, env: Env, apiKeys?: Re
|
|
| 110 |
return getOpenAIModel(apiKey, model);
|
| 111 |
case 'Groq':
|
| 112 |
return getGroqModel(apiKey, model);
|
|
|
|
|
|
|
| 113 |
case 'OpenRouter':
|
| 114 |
return getOpenRouterModel(apiKey, model);
|
| 115 |
case 'Google':
|
|
|
|
| 56 |
return openai(model);
|
| 57 |
}
|
| 58 |
|
| 59 |
+
export function getHuggingFaceModel(apiKey: string, model: string) {
|
| 60 |
+
const openai = createOpenAI({
|
| 61 |
+
baseURL: 'https://api-inference.huggingface.co/v1/',
|
| 62 |
+
apiKey,
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
return openai(model);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
export function getOllamaModel(baseURL: string, model: string) {
|
| 69 |
let Ollama = ollama(model, {
|
| 70 |
numCtx: 32768,
|
|
|
|
| 119 |
return getOpenAIModel(apiKey, model);
|
| 120 |
case 'Groq':
|
| 121 |
return getGroqModel(apiKey, model);
|
| 122 |
+
case 'HuggingFace':
|
| 123 |
+
return getHuggingFaceModel(apiKey, model);
|
| 124 |
case 'OpenRouter':
|
| 125 |
return getOpenRouterModel(apiKey, model);
|
| 126 |
case 'Google':
|
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/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/utils/constants.ts
CHANGED
|
@@ -71,7 +71,19 @@ const PROVIDER_LIST: ProviderInfo[] = [
|
|
| 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' },
|
|
|
|
| 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 |
+
{
|
| 76 |
+
name: 'HuggingFace',
|
| 77 |
+
staticModels: [
|
| 78 |
+
{ name: 'Qwen/Qwen2.5-Coder-32B-Instruct', label: 'Qwen2.5-Coder-32B-Instruct (HuggingFace)', provider: 'HuggingFace' },
|
| 79 |
+
{ name: '01-ai/Yi-1.5-34B-Chat', label: 'Yi-1.5-34B-Chat (HuggingFace)', provider: 'HuggingFace' },
|
| 80 |
+
{ name: 'codellama/CodeLlama-34b-Instruct-hf', label: 'CodeLlama-34b-Instruct (HuggingFace)', provider: 'HuggingFace' },
|
| 81 |
+
{ name: 'NousResearch/Hermes-3-Llama-3.1-8B', label: 'Hermes-3-Llama-3.1-8B (HuggingFace)', provider: 'HuggingFace' }
|
| 82 |
+
],
|
| 83 |
+
getApiKeyLink: 'https://huggingface.co/settings/tokens'
|
| 84 |
+
},
|
| 85 |
+
|
| 86 |
+
{
|
| 87 |
name: 'OpenAI',
|
| 88 |
staticModels: [
|
| 89 |
{ name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI' },
|
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 |
*
|
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}
|
|
@@ -40,6 +41,7 @@ services:
|
|
| 40 |
- WATCHPACK_POLLING=true
|
| 41 |
- PORT=5173
|
| 42 |
- GROQ_API_KEY=${GROQ_API_KEY}
|
|
|
|
| 43 |
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
| 44 |
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
| 45 |
- 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}
|
|
|
|
| 41 |
- WATCHPACK_POLLING=true
|
| 42 |
- PORT=5173
|
| 43 |
- GROQ_API_KEY=${GROQ_API_KEY}
|
| 44 |
+
- HuggingFace_API_KEY=${HuggingFace_API_KEY}
|
| 45 |
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
| 46 |
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
| 47 |
- 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
|
@@ -54,6 +54,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",
|
|
|
|
| 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 |
+
"@radix-ui/react-tooltip": "^1.1.4",
|
| 58 |
"@remix-run/cloudflare": "^2.10.2",
|
| 59 |
"@remix-run/cloudflare-pages": "^2.10.2",
|
| 60 |
"@remix-run/react": "^2.10.2",
|
pnpm-lock.yaml
CHANGED
|
@@ -95,6 +95,9 @@ importers:
|
|
| 95 |
'@radix-ui/react-dropdown-menu':
|
| 96 |
specifier: ^2.1.1
|
| 97 |
version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
|
|
|
|
|
|
|
|
| 98 |
'@remix-run/cloudflare':
|
| 99 |
specifier: ^2.10.2
|
| 100 |
version: 2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2)
|
|
@@ -1380,6 +1383,15 @@ packages:
|
|
| 1380 |
'@types/react':
|
| 1381 |
optional: true
|
| 1382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1383 |
'@radix-ui/react-dialog@1.1.1':
|
| 1384 |
resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==}
|
| 1385 |
peerDependencies:
|
|
@@ -1415,6 +1427,19 @@ packages:
|
|
| 1415 |
'@types/react-dom':
|
| 1416 |
optional: true
|
| 1417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1418 |
'@radix-ui/react-dropdown-menu@2.1.1':
|
| 1419 |
resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==}
|
| 1420 |
peerDependencies:
|
|
@@ -1498,6 +1523,19 @@ packages:
|
|
| 1498 |
'@types/react-dom':
|
| 1499 |
optional: true
|
| 1500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1501 |
'@radix-ui/react-presence@1.1.0':
|
| 1502 |
resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==}
|
| 1503 |
peerDependencies:
|
|
@@ -1511,6 +1549,19 @@ packages:
|
|
| 1511 |
'@types/react-dom':
|
| 1512 |
optional: true
|
| 1513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1514 |
'@radix-ui/react-primitive@2.0.0':
|
| 1515 |
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
|
| 1516 |
peerDependencies:
|
|
@@ -1546,6 +1597,19 @@ packages:
|
|
| 1546 |
'@types/react':
|
| 1547 |
optional: true
|
| 1548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1549 |
'@radix-ui/react-use-callback-ref@1.1.0':
|
| 1550 |
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
| 1551 |
peerDependencies:
|
|
@@ -1600,6 +1664,19 @@ packages:
|
|
| 1600 |
'@types/react':
|
| 1601 |
optional: true
|
| 1602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
'@radix-ui/rect@1.1.0':
|
| 1604 |
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
| 1605 |
|
|
@@ -6720,6 +6797,12 @@ snapshots:
|
|
| 6720 |
optionalDependencies:
|
| 6721 |
'@types/react': 18.3.3
|
| 6722 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6723 |
'@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6724 |
dependencies:
|
| 6725 |
'@radix-ui/primitive': 1.1.0
|
|
@@ -6761,6 +6844,19 @@ snapshots:
|
|
| 6761 |
'@types/react': 18.3.3
|
| 6762 |
'@types/react-dom': 18.3.0
|
| 6763 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6764 |
'@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6765 |
dependencies:
|
| 6766 |
'@radix-ui/primitive': 1.1.0
|
|
@@ -6854,6 +6950,16 @@ snapshots:
|
|
| 6854 |
'@types/react': 18.3.3
|
| 6855 |
'@types/react-dom': 18.3.0
|
| 6856 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6857 |
'@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6858 |
dependencies:
|
| 6859 |
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
|
@@ -6864,6 +6970,16 @@ snapshots:
|
|
| 6864 |
'@types/react': 18.3.3
|
| 6865 |
'@types/react-dom': 18.3.0
|
| 6866 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6867 |
'@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6868 |
dependencies:
|
| 6869 |
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
|
@@ -6897,6 +7013,26 @@ snapshots:
|
|
| 6897 |
optionalDependencies:
|
| 6898 |
'@types/react': 18.3.3
|
| 6899 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6900 |
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)':
|
| 6901 |
dependencies:
|
| 6902 |
react: 18.3.1
|
|
@@ -6937,6 +7073,15 @@ snapshots:
|
|
| 6937 |
optionalDependencies:
|
| 6938 |
'@types/react': 18.3.3
|
| 6939 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6940 |
'@radix-ui/rect@1.1.0': {}
|
| 6941 |
|
| 6942 |
'@remix-run/cloudflare-pages@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2)':
|
|
|
|
| 95 |
'@radix-ui/react-dropdown-menu':
|
| 96 |
specifier: ^2.1.1
|
| 97 |
version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 98 |
+
'@radix-ui/react-tooltip':
|
| 99 |
+
specifier: ^1.1.4
|
| 100 |
+
version: 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 101 |
'@remix-run/cloudflare':
|
| 102 |
specifier: ^2.10.2
|
| 103 |
version: 2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2)
|
|
|
|
| 1383 |
'@types/react':
|
| 1384 |
optional: true
|
| 1385 |
|
| 1386 |
+
'@radix-ui/react-context@1.1.1':
|
| 1387 |
+
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
| 1388 |
+
peerDependencies:
|
| 1389 |
+
'@types/react': '*'
|
| 1390 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1391 |
+
peerDependenciesMeta:
|
| 1392 |
+
'@types/react':
|
| 1393 |
+
optional: true
|
| 1394 |
+
|
| 1395 |
'@radix-ui/react-dialog@1.1.1':
|
| 1396 |
resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==}
|
| 1397 |
peerDependencies:
|
|
|
|
| 1427 |
'@types/react-dom':
|
| 1428 |
optional: true
|
| 1429 |
|
| 1430 |
+
'@radix-ui/react-dismissable-layer@1.1.1':
|
| 1431 |
+
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
|
| 1432 |
+
peerDependencies:
|
| 1433 |
+
'@types/react': '*'
|
| 1434 |
+
'@types/react-dom': '*'
|
| 1435 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1436 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1437 |
+
peerDependenciesMeta:
|
| 1438 |
+
'@types/react':
|
| 1439 |
+
optional: true
|
| 1440 |
+
'@types/react-dom':
|
| 1441 |
+
optional: true
|
| 1442 |
+
|
| 1443 |
'@radix-ui/react-dropdown-menu@2.1.1':
|
| 1444 |
resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==}
|
| 1445 |
peerDependencies:
|
|
|
|
| 1523 |
'@types/react-dom':
|
| 1524 |
optional: true
|
| 1525 |
|
| 1526 |
+
'@radix-ui/react-portal@1.1.2':
|
| 1527 |
+
resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
|
| 1528 |
+
peerDependencies:
|
| 1529 |
+
'@types/react': '*'
|
| 1530 |
+
'@types/react-dom': '*'
|
| 1531 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1532 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1533 |
+
peerDependenciesMeta:
|
| 1534 |
+
'@types/react':
|
| 1535 |
+
optional: true
|
| 1536 |
+
'@types/react-dom':
|
| 1537 |
+
optional: true
|
| 1538 |
+
|
| 1539 |
'@radix-ui/react-presence@1.1.0':
|
| 1540 |
resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==}
|
| 1541 |
peerDependencies:
|
|
|
|
| 1549 |
'@types/react-dom':
|
| 1550 |
optional: true
|
| 1551 |
|
| 1552 |
+
'@radix-ui/react-presence@1.1.1':
|
| 1553 |
+
resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
|
| 1554 |
+
peerDependencies:
|
| 1555 |
+
'@types/react': '*'
|
| 1556 |
+
'@types/react-dom': '*'
|
| 1557 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1558 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1559 |
+
peerDependenciesMeta:
|
| 1560 |
+
'@types/react':
|
| 1561 |
+
optional: true
|
| 1562 |
+
'@types/react-dom':
|
| 1563 |
+
optional: true
|
| 1564 |
+
|
| 1565 |
'@radix-ui/react-primitive@2.0.0':
|
| 1566 |
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
|
| 1567 |
peerDependencies:
|
|
|
|
| 1597 |
'@types/react':
|
| 1598 |
optional: true
|
| 1599 |
|
| 1600 |
+
'@radix-ui/react-tooltip@1.1.4':
|
| 1601 |
+
resolution: {integrity: sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==}
|
| 1602 |
+
peerDependencies:
|
| 1603 |
+
'@types/react': '*'
|
| 1604 |
+
'@types/react-dom': '*'
|
| 1605 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1606 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1607 |
+
peerDependenciesMeta:
|
| 1608 |
+
'@types/react':
|
| 1609 |
+
optional: true
|
| 1610 |
+
'@types/react-dom':
|
| 1611 |
+
optional: true
|
| 1612 |
+
|
| 1613 |
'@radix-ui/react-use-callback-ref@1.1.0':
|
| 1614 |
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
| 1615 |
peerDependencies:
|
|
|
|
| 1664 |
'@types/react':
|
| 1665 |
optional: true
|
| 1666 |
|
| 1667 |
+
'@radix-ui/react-visually-hidden@1.1.0':
|
| 1668 |
+
resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==}
|
| 1669 |
+
peerDependencies:
|
| 1670 |
+
'@types/react': '*'
|
| 1671 |
+
'@types/react-dom': '*'
|
| 1672 |
+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1673 |
+
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
| 1674 |
+
peerDependenciesMeta:
|
| 1675 |
+
'@types/react':
|
| 1676 |
+
optional: true
|
| 1677 |
+
'@types/react-dom':
|
| 1678 |
+
optional: true
|
| 1679 |
+
|
| 1680 |
'@radix-ui/rect@1.1.0':
|
| 1681 |
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
| 1682 |
|
|
|
|
| 6797 |
optionalDependencies:
|
| 6798 |
'@types/react': 18.3.3
|
| 6799 |
|
| 6800 |
+
'@radix-ui/react-context@1.1.1(@types/react@18.3.3)(react@18.3.1)':
|
| 6801 |
+
dependencies:
|
| 6802 |
+
react: 18.3.1
|
| 6803 |
+
optionalDependencies:
|
| 6804 |
+
'@types/react': 18.3.3
|
| 6805 |
+
|
| 6806 |
'@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6807 |
dependencies:
|
| 6808 |
'@radix-ui/primitive': 1.1.0
|
|
|
|
| 6844 |
'@types/react': 18.3.3
|
| 6845 |
'@types/react-dom': 18.3.0
|
| 6846 |
|
| 6847 |
+
'@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6848 |
+
dependencies:
|
| 6849 |
+
'@radix-ui/primitive': 1.1.0
|
| 6850 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6851 |
+
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 6852 |
+
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6853 |
+
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6854 |
+
react: 18.3.1
|
| 6855 |
+
react-dom: 18.3.1(react@18.3.1)
|
| 6856 |
+
optionalDependencies:
|
| 6857 |
+
'@types/react': 18.3.3
|
| 6858 |
+
'@types/react-dom': 18.3.0
|
| 6859 |
+
|
| 6860 |
'@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6861 |
dependencies:
|
| 6862 |
'@radix-ui/primitive': 1.1.0
|
|
|
|
| 6950 |
'@types/react': 18.3.3
|
| 6951 |
'@types/react-dom': 18.3.0
|
| 6952 |
|
| 6953 |
+
'@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6954 |
+
dependencies:
|
| 6955 |
+
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 6956 |
+
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6957 |
+
react: 18.3.1
|
| 6958 |
+
react-dom: 18.3.1(react@18.3.1)
|
| 6959 |
+
optionalDependencies:
|
| 6960 |
+
'@types/react': 18.3.3
|
| 6961 |
+
'@types/react-dom': 18.3.0
|
| 6962 |
+
|
| 6963 |
'@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6964 |
dependencies:
|
| 6965 |
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
|
|
|
| 6970 |
'@types/react': 18.3.3
|
| 6971 |
'@types/react-dom': 18.3.0
|
| 6972 |
|
| 6973 |
+
'@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6974 |
+
dependencies:
|
| 6975 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6976 |
+
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 6977 |
+
react: 18.3.1
|
| 6978 |
+
react-dom: 18.3.1(react@18.3.1)
|
| 6979 |
+
optionalDependencies:
|
| 6980 |
+
'@types/react': 18.3.3
|
| 6981 |
+
'@types/react-dom': 18.3.0
|
| 6982 |
+
|
| 6983 |
'@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 6984 |
dependencies:
|
| 6985 |
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
|
|
|
| 7013 |
optionalDependencies:
|
| 7014 |
'@types/react': 18.3.3
|
| 7015 |
|
| 7016 |
+
'@radix-ui/react-tooltip@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 7017 |
+
dependencies:
|
| 7018 |
+
'@radix-ui/primitive': 1.1.0
|
| 7019 |
+
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 7020 |
+
'@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1)
|
| 7021 |
+
'@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7022 |
+
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 7023 |
+
'@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7024 |
+
'@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7025 |
+
'@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7026 |
+
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7027 |
+
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 7028 |
+
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
|
| 7029 |
+
'@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7030 |
+
react: 18.3.1
|
| 7031 |
+
react-dom: 18.3.1(react@18.3.1)
|
| 7032 |
+
optionalDependencies:
|
| 7033 |
+
'@types/react': 18.3.3
|
| 7034 |
+
'@types/react-dom': 18.3.0
|
| 7035 |
+
|
| 7036 |
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)':
|
| 7037 |
dependencies:
|
| 7038 |
react: 18.3.1
|
|
|
|
| 7073 |
optionalDependencies:
|
| 7074 |
'@types/react': 18.3.3
|
| 7075 |
|
| 7076 |
+
'@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
| 7077 |
+
dependencies:
|
| 7078 |
+
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
| 7079 |
+
react: 18.3.1
|
| 7080 |
+
react-dom: 18.3.1(react@18.3.1)
|
| 7081 |
+
optionalDependencies:
|
| 7082 |
+
'@types/react': 18.3.3
|
| 7083 |
+
'@types/react-dom': 18.3.0
|
| 7084 |
+
|
| 7085 |
'@radix-ui/rect@1.1.0': {}
|
| 7086 |
|
| 7087 |
'@remix-run/cloudflare-pages@2.10.2(@cloudflare/workers-types@4.20240620.0)(typescript@5.5.2)':
|
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;
|