Merge branch 'main' into copyMyFix

This commit is contained in:
Arne Durr 2024-12-16 18:25:08 +01:00 committed by GitHub
commit 329152f56d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 791 additions and 540 deletions

View File

@ -17,12 +17,18 @@ jobs:
- name: Checkout the code - name: Checkout the code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Get the latest commit hash - name: Get the latest commit hash
run: echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV run: |
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: Update commit file - name: Update commit file
run: | run: |
echo "{ \"commit\": \"$COMMIT_HASH\" }" > app/commit.json echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json
- name: Commit and push the update - name: Commit and push the update
run: | run: |

View File

@ -9,30 +9,7 @@ permissions:
contents: write contents: write
jobs: jobs:
update-commit:
if: contains(github.event.head_commit.message, '#release')
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: Get the latest commit hash
run: echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
- name: Update commit file
run: |
echo "{ \"commit\": \"$COMMIT_HASH\" }" > app/commit.json
- name: Commit and push the update
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add app/commit.json
git commit -m "chore: update commit hash to $COMMIT_HASH"
git push
prepare-release: prepare-release:
needs: update-commit
if: contains(github.event.head_commit.message, '#release') if: contains(github.event.head_commit.message, '#release')
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -181,10 +158,16 @@ jobs:
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
- name: Get the latest commit hash and version tag
run: |
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: Commit and Tag Release - name: Commit and Tag Release
run: | run: |
git pull git pull
git add package.json pnpm-lock.yaml changelog.md echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json
git add package.json pnpm-lock.yaml changelog.md app/commit.json
git commit -m "chore: release version ${{ steps.bump_version.outputs.new_version }}" git commit -m "chore: release version ${{ steps.bump_version.outputs.new_version }}"
git tag "v${{ steps.bump_version.outputs.new_version }}" git tag "v${{ steps.bump_version.outputs.new_version }}"
git push git push

View File

@ -43,6 +43,9 @@ https://thinktank.ottomator.ai
- ✅ Mobile friendly (@qwikode) - ✅ Mobile friendly (@qwikode)
- ✅ Better prompt enhancing (@SujalXplores) - ✅ Better prompt enhancing (@SujalXplores)
- ✅ Attach images to prompts (@atrokhym) - ✅ Attach images to prompts (@atrokhym)
- ✅ Added Git Clone button (@thecodacus)
- ✅ Git Import from url (@thecodacus)
- ✅ PromptLibrary to have different variations of prompts for different use cases (@thecodacus)
- ✅ Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er) - ✅ Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er)
- ✅ Selection tool to target changes visually (@emcconnell) - ✅ Selection tool to target changes visually (@emcconnell)
- ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs) - ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)

View File

@ -1,13 +1,30 @@
import { memo } from 'react'; import { memo } from 'react';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import type { JSONValue } from 'ai';
interface AssistantMessageProps { interface AssistantMessageProps {
content: string; content: string;
annotations?: JSONValue[];
} }
export const AssistantMessage = memo(({ content }: AssistantMessageProps) => { export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
const filteredAnnotations = (annotations?.filter(
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
) || []) as { type: string; value: any }[];
const usage: {
completionTokens: number;
promptTokens: number;
totalTokens: number;
} = filteredAnnotations.find((annotation) => annotation.type === 'usage')?.value;
return ( return (
<div className="overflow-hidden w-full"> <div className="overflow-hidden w-full">
{usage && (
<div className="text-sm text-bolt-elements-textSecondary mb-2">
Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
</div>
)}
<Markdown html>{content}</Markdown> <Markdown html>{content}</Markdown>
</div> </div>
); );

View File

@ -77,7 +77,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
input = '', input = '',
enhancingPrompt, enhancingPrompt,
handleInputChange, handleInputChange,
promptEnhanced,
// promptEnhanced,
enhancePrompt, enhancePrompt,
sendMessage, sendMessage,
handleStop, handleStop,
@ -490,10 +491,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<IconButton <IconButton
title="Enhance prompt" title="Enhance prompt"
disabled={input.length === 0 || enhancingPrompt} disabled={input.length === 0 || enhancingPrompt}
className={classNames( className={classNames('transition-all', enhancingPrompt ? 'opacity-100' : '')}
'transition-all',
enhancingPrompt ? 'opacity-100' : '',
)}
onClick={() => { onClick={() => {
enhancePrompt?.(); enhancePrompt?.();
toast.success('Prompt enhanced!'); toast.success('Prompt enhanced!');

View File

@ -93,7 +93,7 @@ export const ChatImpl = memo(
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
const files = useStore(workbenchStore.files); const files = useStore(workbenchStore.files);
const { activeProviders } = useSettings(); const { activeProviders, promptId } = useSettings();
const [model, setModel] = useState(() => { const [model, setModel] = useState(() => {
const savedModel = Cookies.get('selectedModel'); const savedModel = Cookies.get('selectedModel');
@ -115,14 +115,24 @@ export const ChatImpl = memo(
body: { body: {
apiKeys, apiKeys,
files, files,
promptId,
}, },
sendExtraMessageFields: true,
onError: (error) => { onError: (error) => {
logger.error('Request failed\n\n', error); logger.error('Request failed\n\n', error);
toast.error( toast.error(
'There was an error processing your request: ' + (error.message ? error.message : 'No details were returned'), 'There was an error processing your request: ' + (error.message ? error.message : 'No details were returned'),
); );
}, },
onFinish: () => { onFinish: (message, response) => {
const usage = response.usage;
if (usage) {
console.log('Token usage:', usage);
// You can now use the usage data as needed
}
logger.debug('Finished streaming'); logger.debug('Finished streaming');
}, },
initialMessages, initialMessages,

View File

@ -65,12 +65,16 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
</div> </div>
)} )}
<div className="grid grid-col-1 w-full"> <div className="grid grid-col-1 w-full">
{isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />} {isUserMessage ? (
<UserMessage content={content} />
) : (
<AssistantMessage content={content} annotations={message.annotations} />
)}
</div> </div>
{!isUserMessage && ( {!isUserMessage && (
<div className="flex gap-2 flex-col lg:flex-row"> <div className="flex gap-2 flex-col lg:flex-row">
<WithTooltip tooltip="Revert to this message">
{messageId && ( {messageId && (
<WithTooltip tooltip="Revert to this message">
<button <button
onClick={() => handleRewind(messageId)} onClick={() => handleRewind(messageId)}
key="i-ph:arrow-u-up-left" key="i-ph:arrow-u-up-left"
@ -79,8 +83,8 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors', 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
)} )}
/> />
)}
</WithTooltip> </WithTooltip>
)}
<WithTooltip tooltip="Fork chat from this message"> <WithTooltip tooltip="Fork chat from this message">
<button <button

View File

@ -12,42 +12,36 @@ interface UserMessageProps {
export function UserMessage({ content }: UserMessageProps) { export function UserMessage({ content }: UserMessageProps) {
if (Array.isArray(content)) { if (Array.isArray(content)) {
const textItem = content.find((item) => item.type === 'text'); const textItem = content.find((item) => item.type === 'text');
const textContent = sanitizeUserMessage(textItem?.text || ''); const textContent = stripMetadata(textItem?.text || '');
const images = content.filter((item) => item.type === 'image' && item.image); const images = content.filter((item) => item.type === 'image' && item.image);
return ( return (
<div className="overflow-hidden pt-[4px]"> <div className="overflow-hidden pt-[4px]">
<div className="flex items-start gap-4"> <div className="flex flex-col gap-4">
<div className="flex-1"> {textContent && <Markdown html>{textContent}</Markdown>}
<Markdown limitedMarkdown>{textContent}</Markdown>
</div>
{images.length > 0 && (
<div className="flex-shrink-0 w-[160px]">
{images.map((item, index) => ( {images.map((item, index) => (
<div key={index} className="relative">
<img <img
key={index}
src={item.image} src={item.image}
alt={`Uploaded image ${index + 1}`} alt={`Image ${index + 1}`}
className="w-full h-[160px] rounded-lg object-cover border border-bolt-elements-borderColor" className="max-w-full h-auto rounded-lg"
style={{ maxHeight: '512px', objectFit: 'contain' }}
/> />
</div>
))} ))}
</div> </div>
)}
</div>
</div> </div>
); );
} }
const textContent = sanitizeUserMessage(content); const textContent = stripMetadata(content);
return ( return (
<div className="overflow-hidden pt-[4px]"> <div className="overflow-hidden pt-[4px]">
<Markdown limitedMarkdown>{textContent}</Markdown> <Markdown html>{textContent}</Markdown>
</div> </div>
); );
} }
function sanitizeUserMessage(content: string) { function stripMetadata(content: string) {
return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''); return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
} }

View File

@ -22,7 +22,8 @@ export default function ChatHistoryTab() {
}; };
const handleDeleteAllChats = async () => { const handleDeleteAllChats = async () => {
const confirmDelete = window.confirm("Are you sure you want to delete all chats? This action cannot be undone."); const confirmDelete = window.confirm('Are you sure you want to delete all chats? This action cannot be undone.');
if (!confirmDelete) { if (!confirmDelete) {
return; // Exit if the user cancels return; // Exit if the user cancels
} }
@ -31,11 +32,13 @@ export default function ChatHistoryTab() {
const error = new Error('Database is not available'); const error = new Error('Database is not available');
logStore.logError('Failed to delete chats - DB unavailable', error); logStore.logError('Failed to delete chats - DB unavailable', error);
toast.error('Database is not available'); toast.error('Database is not available');
return; return;
} }
try { try {
setIsDeleting(true); setIsDeleting(true);
const allChats = await getAll(db); const allChats = await getAll(db);
await Promise.all(allChats.map((chat) => deleteById(db!, chat.id))); await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
logStore.logSystem('All chats deleted successfully', { count: allChats.length }); logStore.logSystem('All chats deleted successfully', { count: allChats.length });
@ -55,6 +58,7 @@ export default function ChatHistoryTab() {
const error = new Error('Database is not available'); const error = new Error('Database is not available');
logStore.logError('Failed to export chats - DB unavailable', error); logStore.logError('Failed to export chats - DB unavailable', error);
toast.error('Database is not available'); toast.error('Database is not available');
return; return;
} }

View File

@ -22,6 +22,12 @@ interface SystemInfo {
timezone: string; timezone: string;
memory: string; memory: string;
cores: number; cores: number;
deviceType: string;
colorDepth: string;
pixelRatio: number;
online: boolean;
cookiesEnabled: boolean;
doNotTrack: boolean;
} }
interface IProviderConfig { interface IProviderConfig {
@ -34,14 +40,19 @@ interface IProviderConfig {
interface CommitData { interface CommitData {
commit: string; commit: string;
version?: string;
} }
const connitJson: CommitData = commit;
const LOCAL_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike']; const LOCAL_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
const versionHash = commit.commit; const versionHash = connitJson.commit;
const versionTag = connitJson.version;
const GITHUB_URLS = { const GITHUB_URLS = {
original: 'https://api.github.com/repos/stackblitz-labs/bolt.diy/commits/main', original: 'https://api.github.com/repos/stackblitz-labs/bolt.diy/commits/main',
fork: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main', fork: 'https://api.github.com/repos/Stijnus/bolt.new-any-llm/commits/main',
commitJson: (branch: string) => `https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/${branch}/app/commit.json`, commitJson: (branch: string) =>
`https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/${branch}/app/commit.json`,
}; };
function getSystemInfo(): SystemInfo { function getSystemInfo(): SystemInfo {
@ -57,14 +68,100 @@ function getSystemInfo(): SystemInfo {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}; };
const getBrowserInfo = (): string => {
const ua = navigator.userAgent;
let browser = 'Unknown';
if (ua.includes('Firefox/')) {
browser = 'Firefox';
} else if (ua.includes('Chrome/')) {
if (ua.includes('Edg/')) {
browser = 'Edge';
} else if (ua.includes('OPR/')) {
browser = 'Opera';
} else {
browser = 'Chrome';
}
} else if (ua.includes('Safari/')) {
if (!ua.includes('Chrome')) {
browser = 'Safari';
}
}
// Extract version number
const match = ua.match(new RegExp(`${browser}\\/([\\d.]+)`));
const version = match ? ` ${match[1]}` : '';
return `${browser}${version}`;
};
const getOperatingSystem = (): string => {
const ua = navigator.userAgent;
const platform = navigator.platform;
if (ua.includes('Win')) {
return 'Windows';
}
if (ua.includes('Mac')) {
if (ua.includes('iPhone') || ua.includes('iPad')) {
return 'iOS';
}
return 'macOS';
}
if (ua.includes('Linux')) {
return 'Linux';
}
if (ua.includes('Android')) {
return 'Android';
}
return platform || 'Unknown';
};
const getDeviceType = (): string => {
const ua = navigator.userAgent;
if (ua.includes('Mobile')) {
return 'Mobile';
}
if (ua.includes('Tablet')) {
return 'Tablet';
}
return 'Desktop';
};
// Get more detailed memory info if available
const getMemoryInfo = (): string => {
if ('memory' in performance) {
const memory = (performance as any).memory;
return `${formatBytes(memory.jsHeapSizeLimit)} (Used: ${formatBytes(memory.usedJSHeapSize)})`;
}
return 'Not available';
};
return { return {
os: navigator.platform, os: getOperatingSystem(),
browser: navigator.userAgent.split(' ').slice(-1)[0], browser: getBrowserInfo(),
screen: `${window.screen.width}x${window.screen.height}`, screen: `${window.screen.width}x${window.screen.height}`,
language: navigator.language, language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
memory: formatBytes(performance?.memory?.jsHeapSizeLimit || 0), memory: getMemoryInfo(),
cores: navigator.hardwareConcurrency || 0, cores: navigator.hardwareConcurrency || 0,
deviceType: getDeviceType(),
// Add new fields
colorDepth: `${window.screen.colorDepth}-bit`,
pixelRatio: window.devicePixelRatio,
online: navigator.onLine,
cookiesEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack === '1',
}; };
} }
@ -206,7 +303,7 @@ const checkProviderStatus = async (url: string | null, providerName: string): Pr
}; };
export default function DebugTab() { export default function DebugTab() {
const { providers, useLatestBranch } = useSettings(); const { providers, isLatestBranch } = useSettings();
const [activeProviders, setActiveProviders] = useState<ProviderStatus[]>([]); const [activeProviders, setActiveProviders] = useState<ProviderStatus[]>([]);
const [updateMessage, setUpdateMessage] = useState<string>(''); const [updateMessage, setUpdateMessage] = useState<string>('');
const [systemInfo] = useState<SystemInfo>(getSystemInfo()); const [systemInfo] = useState<SystemInfo>(getSystemInfo());
@ -235,11 +332,12 @@ export default function DebugTab() {
console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`); console.log(`[Debug] Using URL for ${provider.name}:`, url, `(from ${envVarName})`);
const status = await checkProviderStatus(url, provider.name); const status = await checkProviderStatus(url, provider.name);
return { return {
...status, ...status,
enabled: provider.settings.enabled ?? false, enabled: provider.settings.enabled ?? false,
}; };
}) }),
); );
setActiveProviders(statuses); setActiveProviders(statuses);
@ -265,15 +363,16 @@ export default function DebugTab() {
setIsCheckingUpdate(true); setIsCheckingUpdate(true);
setUpdateMessage('Checking for updates...'); setUpdateMessage('Checking for updates...');
const branchToCheck = useLatestBranch ? 'main' : 'stable'; const branchToCheck = isLatestBranch ? 'main' : 'stable';
console.log(`[Debug] Checking for updates against ${branchToCheck} branch`); console.log(`[Debug] Checking for updates against ${branchToCheck} branch`);
const localCommitResponse = await fetch(GITHUB_URLS.commitJson(branchToCheck)); const localCommitResponse = await fetch(GITHUB_URLS.commitJson(branchToCheck));
if (!localCommitResponse.ok) { if (!localCommitResponse.ok) {
throw new Error('Failed to fetch local commit info'); throw new Error('Failed to fetch local commit info');
} }
const localCommitData = await localCommitResponse.json() as CommitData; const localCommitData = (await localCommitResponse.json()) as CommitData;
const remoteCommitHash = localCommitData.commit; const remoteCommitHash = localCommitData.commit;
const currentCommitHash = versionHash; const currentCommitHash = versionHash;
@ -281,7 +380,7 @@ export default function DebugTab() {
setUpdateMessage( setUpdateMessage(
`Update available from ${branchToCheck} branch!\n` + `Update available from ${branchToCheck} branch!\n` +
`Current: ${currentCommitHash.slice(0, 7)}\n` + `Current: ${currentCommitHash.slice(0, 7)}\n` +
`Latest: ${remoteCommitHash.slice(0, 7)}` `Latest: ${remoteCommitHash.slice(0, 7)}`,
); );
} else { } else {
setUpdateMessage(`You are on the latest version from the ${branchToCheck} branch`); setUpdateMessage(`You are on the latest version from the ${branchToCheck} branch`);
@ -292,7 +391,7 @@ export default function DebugTab() {
} finally { } finally {
setIsCheckingUpdate(false); setIsCheckingUpdate(false);
} }
}, [isCheckingUpdate, useLatestBranch]); }, [isCheckingUpdate, isLatestBranch]);
const handleCopyToClipboard = useCallback(() => { const handleCopyToClipboard = useCallback(() => {
const debugInfo = { const debugInfo = {
@ -309,7 +408,7 @@ export default function DebugTab() {
})), })),
Version: { Version: {
hash: versionHash.slice(0, 7), hash: versionHash.slice(0, 7),
branch: useLatestBranch ? 'main' : 'stable' branch: isLatestBranch ? 'main' : 'stable',
}, },
Timestamp: new Date().toISOString(), Timestamp: new Date().toISOString(),
}; };
@ -317,7 +416,7 @@ export default function DebugTab() {
navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => { navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => {
toast.success('Debug information copied to clipboard!'); toast.success('Debug information copied to clipboard!');
}); });
}, [activeProviders, systemInfo, useLatestBranch]); }, [activeProviders, systemInfo, isLatestBranch]);
return ( return (
<div className="p-4 space-y-6"> <div className="p-4 space-y-6">
@ -377,10 +476,31 @@ export default function DebugTab() {
<p className="text-xs text-bolt-elements-textSecondary">Operating System</p> <p className="text-xs text-bolt-elements-textSecondary">Operating System</p>
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.os}</p> <p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.os}</p>
</div> </div>
<div>
<p className="text-xs text-bolt-elements-textSecondary">Device Type</p>
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.deviceType}</p>
</div>
<div> <div>
<p className="text-xs text-bolt-elements-textSecondary">Browser</p> <p className="text-xs text-bolt-elements-textSecondary">Browser</p>
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.browser}</p> <p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.browser}</p>
</div> </div>
<div>
<p className="text-xs text-bolt-elements-textSecondary">Display</p>
<p className="text-sm font-medium text-bolt-elements-textPrimary">
{systemInfo.screen} ({systemInfo.colorDepth}) @{systemInfo.pixelRatio}x
</p>
</div>
<div>
<p className="text-xs text-bolt-elements-textSecondary">Connection</p>
<p className="text-sm font-medium flex items-center gap-2">
<span
className={`inline-block w-2 h-2 rounded-full ${systemInfo.online ? 'bg-green-500' : 'bg-red-500'}`}
/>
<span className={`${systemInfo.online ? 'text-green-600' : 'text-red-600'}`}>
{systemInfo.online ? 'Online' : 'Offline'}
</span>
</p>
</div>
<div> <div>
<p className="text-xs text-bolt-elements-textSecondary">Screen Resolution</p> <p className="text-xs text-bolt-elements-textSecondary">Screen Resolution</p>
<p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.screen}</p> <p className="text-sm font-medium text-bolt-elements-textPrimary">{systemInfo.screen}</p>
@ -403,7 +523,7 @@ export default function DebugTab() {
<p className="text-sm font-medium text-bolt-elements-textPrimary font-mono"> <p className="text-sm font-medium text-bolt-elements-textPrimary font-mono">
{versionHash.slice(0, 7)} {versionHash.slice(0, 7)}
<span className="ml-2 text-xs text-bolt-elements-textSecondary"> <span className="ml-2 text-xs text-bolt-elements-textSecondary">
({new Date().toLocaleDateString()}) (v{versionTag || '0.0.1'}) - {isLatestBranch ? 'nightly' : 'stable'}
</span> </span>
</p> </p>
</div> </div>

View File

@ -1,9 +1,20 @@
import React from 'react'; import React from 'react';
import { Switch } from '~/components/ui/Switch'; import { Switch } from '~/components/ui/Switch';
import { PromptLibrary } from '~/lib/common/prompt-library';
import { useSettings } from '~/lib/hooks/useSettings'; import { useSettings } from '~/lib/hooks/useSettings';
export default function FeaturesTab() { export default function FeaturesTab() {
const { debug, enableDebugMode, isLocalModel, enableLocalModels, eventLogs, enableEventLogs, useLatestBranch, enableLatestBranch } = useSettings(); const {
debug,
enableDebugMode,
isLocalModel,
enableLocalModels,
enableEventLogs,
isLatestBranch,
enableLatestBranch,
promptId,
setPromptId,
} = useSettings();
const handleToggle = (enabled: boolean) => { const handleToggle = (enabled: boolean) => {
enableDebugMode(enabled); enableDebugMode(enabled);
@ -22,9 +33,11 @@ export default function FeaturesTab() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<span className="text-bolt-elements-textPrimary">Use Main Branch</span> <span className="text-bolt-elements-textPrimary">Use Main Branch</span>
<p className="text-sm text-bolt-elements-textSecondary">Check for updates against the main branch instead of stable</p> <p className="text-sm text-bolt-elements-textSecondary">
Check for updates against the main branch instead of stable
</p>
</div> </div>
<Switch className="ml-auto" checked={useLatestBranch} onCheckedChange={enableLatestBranch} /> <Switch className="ml-auto" checked={isLatestBranch} onCheckedChange={enableLatestBranch} />
</div> </div>
</div> </div>
</div> </div>
@ -34,10 +47,28 @@ export default function FeaturesTab() {
<p className="text-sm text-bolt-elements-textSecondary mb-4"> <p className="text-sm text-bolt-elements-textSecondary mb-4">
Disclaimer: Experimental features may be unstable and are subject to change. Disclaimer: Experimental features may be unstable and are subject to change.
</p> </p>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-bolt-elements-textPrimary">Experimental Providers</span> <span className="text-bolt-elements-textPrimary">Experimental Providers</span>
<Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} /> <Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
</div> </div>
<div className="flex items-start justify-between pt-4 mb-2 gap-2">
<div className="flex-1 max-w-[200px]">
<span className="text-bolt-elements-textPrimary">Prompt Library</span>
<p className="text-sm text-bolt-elements-textSecondary mb-4">
Choose a prompt from the library to use as the system prompt.
</p>
</div>
<select
value={promptId}
onChange={(e) => setPromptId(e.target.value)}
className="flex-1 p-2 ml-auto rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all text-sm min-w-[100px]"
>
{PromptLibrary.getList().map((x) => (
<option value={x.id}>{x.label}</option>
))}
</select>
</div>
</div> </div>
</div> </div>
); );

View File

@ -56,7 +56,8 @@ export default function ProvidersTab() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<img <img
src={`/icons/${provider.name}.svg`} // Attempt to load the specific icon src={`/icons/${provider.name}.svg`} // Attempt to load the specific icon
onError={(e) => { // Fallback to default icon on error onError={(e) => {
// Fallback to default icon on error
e.currentTarget.src = DefaultIcon; e.currentTarget.src = DefaultIcon;
}} }}
alt={`${provider.name} icon`} alt={`${provider.name} icon`}

View File

@ -1,4 +1,4 @@
import { memo } from 'react'; import { memo, forwardRef, type ForwardedRef } from 'react';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
type IconSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; type IconSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
@ -25,8 +25,11 @@ type IconButtonWithChildrenProps = {
type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps; type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps;
// Componente IconButton com suporte a refs
export const IconButton = memo( export const IconButton = memo(
({ forwardRef(
(
{
icon, icon,
size = 'xl', size = 'xl',
className, className,
@ -36,9 +39,12 @@ export const IconButton = memo(
title, title,
onClick, onClick,
children, children,
}: IconButtonProps) => { }: IconButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
) => {
return ( return (
<button <button
ref={ref}
className={classNames( className={classNames(
'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed', 'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
{ {
@ -60,6 +66,7 @@ export const IconButton = memo(
</button> </button>
); );
}, },
),
); );
function getIconSize(size: IconSize) { function getIconSize(size: IconSize) {

View File

@ -1,8 +1,9 @@
import * as Tooltip from '@radix-ui/react-tooltip'; import * as Tooltip from '@radix-ui/react-tooltip';
import { forwardRef, type ForwardedRef, type ReactElement } from 'react';
interface TooltipProps { interface TooltipProps {
tooltip: React.ReactNode; tooltip: React.ReactNode;
children: React.ReactNode; children: ReactElement;
sideOffset?: number; sideOffset?: number;
className?: string; className?: string;
arrowClassName?: string; arrowClassName?: string;
@ -12,7 +13,9 @@ interface TooltipProps {
delay?: number; delay?: number;
} }
const WithTooltip = ({ const WithTooltip = forwardRef(
(
{
tooltip, tooltip,
children, children,
sideOffset = 5, sideOffset = 5,
@ -22,7 +25,9 @@ const WithTooltip = ({
position = 'top', position = 'top',
maxWidth = 250, maxWidth = 250,
delay = 0, delay = 0,
}: TooltipProps) => { }: TooltipProps,
_ref: ForwardedRef<HTMLElement>,
) => {
return ( return (
<Tooltip.Root delayDuration={delay}> <Tooltip.Root delayDuration={delay}>
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger> <Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
@ -68,6 +73,7 @@ const WithTooltip = ({
</Tooltip.Portal> </Tooltip.Portal>
</Tooltip.Root> </Tooltip.Root>
); );
}; },
);
export default WithTooltip; export default WithTooltip;

View File

@ -1,10 +1,20 @@
import { convertToCoreMessages, streamText as _streamText } from 'ai'; import { convertToCoreMessages, streamText as _streamText } from 'ai';
import { getModel } from '~/lib/.server/llm/model'; import { getModel } from '~/lib/.server/llm/model';
import { MAX_TOKENS } from './constants'; import { MAX_TOKENS } from './constants';
import { getSystemPrompt } from './prompts'; import { getSystemPrompt } from '~/lib/common/prompts/prompts';
import { DEFAULT_MODEL, DEFAULT_PROVIDER, getModelList, MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants'; import {
DEFAULT_MODEL,
DEFAULT_PROVIDER,
getModelList,
MODEL_REGEX,
MODIFICATIONS_TAG_NAME,
PROVIDER_REGEX,
WORK_DIR,
} from '~/utils/constants';
import ignore from 'ignore'; import ignore from 'ignore';
import type { IProviderSetting } from '~/types/model'; import type { IProviderSetting } from '~/types/model';
import { PromptLibrary } from '~/lib/common/prompt-library';
import { allowedHTMLElements } from '~/utils/markdown';
interface ToolResult<Name extends string, Args, Result> { interface ToolResult<Name extends string, Args, Result> {
toolCallId: string; toolCallId: string;
@ -139,8 +149,9 @@ export async function streamText(props: {
apiKeys?: Record<string, string>; apiKeys?: Record<string, string>;
files?: FileMap; files?: FileMap;
providerSettings?: Record<string, IProviderSetting>; providerSettings?: Record<string, IProviderSetting>;
promptId?: string;
}) { }) {
const { messages, env, options, apiKeys, files, providerSettings } = props; const { messages, env, options, apiKeys, files, providerSettings, promptId } = props;
let currentModel = DEFAULT_MODEL; let currentModel = DEFAULT_MODEL;
let currentProvider = DEFAULT_PROVIDER.name; let currentProvider = DEFAULT_PROVIDER.name;
const MODEL_LIST = await getModelList(apiKeys || {}, providerSettings); const MODEL_LIST = await getModelList(apiKeys || {}, providerSettings);
@ -170,11 +181,17 @@ export async function streamText(props: {
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS; const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
let systemPrompt = getSystemPrompt(); let systemPrompt =
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
cwd: WORK_DIR,
allowedHtmlElements: allowedHTMLElements,
modificationTagName: MODIFICATIONS_TAG_NAME,
}) ?? getSystemPrompt();
let codeContext = ''; let codeContext = '';
if (files) { if (files) {
codeContext = createFilesContext(files); codeContext = createFilesContext(files);
codeContext = '';
systemPrompt = `${systemPrompt}\n\n ${codeContext}`; systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
} }

View File

@ -0,0 +1,49 @@
import { getSystemPrompt } from './prompts/prompts';
import optimized from './prompts/optimized';
export interface PromptOptions {
cwd: string;
allowedHtmlElements: string[];
modificationTagName: string;
}
export class PromptLibrary {
static library: Record<
string,
{
label: string;
description: string;
get: (options: PromptOptions) => string;
}
> = {
default: {
label: 'Default Prompt',
description: 'This is the battle tested default system Prompt',
get: (options) => getSystemPrompt(options.cwd),
},
optimized: {
label: 'Optimized Prompt (experimental)',
description: 'an Experimental version of the prompt for lower token usage',
get: (options) => optimized(options),
},
};
static getList() {
return Object.entries(this.library).map(([key, value]) => {
const { label, description } = value;
return {
id: key,
label,
description,
};
});
}
static getPropmtFromLibrary(promptId: string, options: PromptOptions) {
const prompt = this.library[promptId];
if (!prompt) {
throw 'Prompt Now Found';
}
return this.library[promptId]?.get(options);
}
}

View File

@ -0,0 +1,199 @@
import type { PromptOptions } from '~/lib/common/prompt-library';
export default (options: PromptOptions) => {
const { cwd, allowedHtmlElements, modificationTagName } = options;
return `
You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices.
<system_constraints>
- Operating in WebContainer, an in-browser Node.js runtime
- Limited Python support: standard library only, no pip
- No C/C++ compiler, native binaries, or Git
- Prefer Node.js scripts over shell scripts
- Use Vite for web servers
- Databases: prefer libsql, sqlite, or non-native solutions
- When for react dont forget to write vite config and index.html to the project
Available shell commands: cat, cp, ls, mkdir, mv, rm, rmdir, touch, hostname, ps, pwd, uptime, env, node, python3, code, jq, curl, head, sort, tail, clear, which, export, chmod, scho, kill, ln, xxd, alias, getconf, loadenv, wasm, xdg-open, command, exit, source
</system_constraints>
<code_formatting_info>
Use 2 spaces for indentation
</code_formatting_info>
<message_formatting_info>
Available HTML elements: ${allowedHtmlElements.join(', ')}
</message_formatting_info>
<diff_spec>
File modifications in \`<${modificationTagName}>\` section:
- \`<diff path="/path/to/file">\`: GNU unified diff format
- \`<file path="/path/to/file">\`: Full new content
</diff_spec>
<chain_of_thought_instructions>
do not mention the phrase "chain of thought"
Before solutions, briefly outline implementation steps (2-4 lines max):
- List concrete steps
- Identify key components
- Note potential challenges
- Do not write the actual code just the plan and structure if needed
- Once completed planning start writing the artifacts
</chain_of_thought_instructions>
<artifact_info>
Create a single, comprehensive artifact for each project:
- Use \`<boltArtifact>\` tags with \`title\` and \`id\` attributes
- Use \`<boltAction>\` tags with \`type\` attribute:
- shell: Run commands
- file: Write/update files (use \`filePath\` attribute)
- start: Start dev server (only when necessary)
- Order actions logically
- Install dependencies first
- Provide full, updated content for all files
- Use coding best practices: modular, clean, readable code
</artifact_info>
# CRITICAL RULES - NEVER IGNORE
## File and Command Handling
1. ALWAYS use artifacts for file contents and commands - NO EXCEPTIONS
2. When writing a file, INCLUDE THE ENTIRE FILE CONTENT - NO PARTIAL UPDATES
3. For modifications, ONLY alter files that require changes - DO NOT touch unaffected files
## Response Format
4. Use markdown EXCLUSIVELY - HTML tags are ONLY allowed within artifacts
5. Be concise - Explain ONLY when explicitly requested
6. NEVER use the word "artifact" in responses
## Development Process
7. ALWAYS think and plan comprehensively before providing a solution
8. Current working directory: \`${cwd} \` - Use this for all file paths
9. Don't use cli scaffolding to steup the project, use cwd as Root of the project
11. For nodejs projects ALWAYS install dependencies after writing package.json file
## Coding Standards
10. ALWAYS create smaller, atomic components and modules
11. Modularity is PARAMOUNT - Break down functionality into logical, reusable parts
12. IMMEDIATELY refactor any file exceeding 250 lines
13. ALWAYS plan refactoring before implementation - Consider impacts on the entire system
## Artifact Usage
22. Use \`<boltArtifact>\` tags with \`title\` and \`id\` attributes for each project
23. Use \`<boltAction>\` tags with appropriate \`type\` attribute:
- \`shell\`: For running commands
- \`file\`: For writing/updating files (include \`filePath\` attribute)
- \`start\`: For starting dev servers (use only when necessary/ or new dependencies are installed)
24. Order actions logically - dependencies MUST be installed first
25. For Vite project must include vite config and index.html for entry point
26. Provide COMPLETE, up-to-date content for all files - NO placeholders or partial updates
CRITICAL: These rules are ABSOLUTE and MUST be followed WITHOUT EXCEPTION in EVERY response.
Examples:
<examples>
<example>
<user_query>Can you help me create a JavaScript function to calculate the factorial of a number?</user_query>
<assistant_response>
Certainly, I can help you create a JavaScript function to calculate the factorial of a number.
<boltArtifact id="factorial-function" title="JavaScript Factorial Function">
<boltAction type="file" filePath="index.js">
function factorial(n) {
...
}
...
</boltAction>
<boltAction type="shell">
node index.js
</boltAction>
</boltArtifact>
</assistant_response>
</example>
<example>
<user_query>Build a snake game</user_query>
<assistant_response>
Certainly! I'd be happy to help you build a snake game using JavaScript and HTML5 Canvas. This will be a basic implementation that you can later expand upon. Let's create the game step by step.
<boltArtifact id="snake-game" title="Snake Game in HTML and JavaScript">
<boltAction type="file" filePath="package.json">
{
"name": "snake",
"scripts": {
"dev": "vite"
}
...
}
</boltAction>
<boltAction type="shell">
npm install --save-dev vite
</boltAction>
<boltAction type="file" filePath="index.html">
...
</boltAction>
<boltAction type="start">
npm run dev
</boltAction>
</boltArtifact>
Now you can play the Snake game by opening the provided local server URL in your browser. Use the arrow keys to control the snake. Eat the red food to grow and increase your score. The game ends if you hit the wall or your own tail.
</assistant_response>
</example>
<example>
<user_query>Make a bouncing ball with real gravity using React</user_query>
<assistant_response>
Certainly! I'll create a bouncing ball with real gravity using React. We'll use the react-spring library for physics-based animations.
<boltArtifact id="bouncing-ball-react" title="Bouncing Ball with Gravity in React">
<boltAction type="file" filePath="package.json">
{
"name": "bouncing-ball",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-spring": "^9.7.1"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
</boltAction>
<boltAction type="file" filePath="index.html">
...
</boltAction>
<boltAction type="file" filePath="src/main.jsx">
...
</boltAction>
<boltAction type="file" filePath="src/index.css">
...
</boltAction>
<boltAction type="file" filePath="src/App.jsx">
...
</boltAction>
<boltAction type="start">
npm run dev
</boltAction>
</boltArtifact>
You can now view the bouncing ball animation in the preview. The ball will start falling from the top of the screen and bounce realistically when it hits the bottom.
</assistant_response>
</example>
</examples>
Always use artifacts for file contents and commands, following the format shown in these examples.
`;
};

View File

@ -4,8 +4,9 @@ import {
isEventLogsEnabled, isEventLogsEnabled,
isLocalModelsEnabled, isLocalModelsEnabled,
LOCAL_PROVIDERS, LOCAL_PROVIDERS,
promptStore,
providersStore, providersStore,
latestBranch, latestBranchStore,
} from '~/lib/stores/settings'; } from '~/lib/stores/settings';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
@ -15,25 +16,34 @@ import commit from '~/commit.json';
interface CommitData { interface CommitData {
commit: string; commit: string;
version?: string;
} }
const commitJson: CommitData = commit;
export function useSettings() { export function useSettings() {
const providers = useStore(providersStore); const providers = useStore(providersStore);
const debug = useStore(isDebugMode); const debug = useStore(isDebugMode);
const eventLogs = useStore(isEventLogsEnabled); const eventLogs = useStore(isEventLogsEnabled);
const promptId = useStore(promptStore);
const isLocalModel = useStore(isLocalModelsEnabled); const isLocalModel = useStore(isLocalModelsEnabled);
const useLatest = useStore(latestBranch); const isLatestBranch = useStore(latestBranchStore);
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]); const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
// Function to check if we're on stable version // Function to check if we're on stable version
const checkIsStableVersion = async () => { const checkIsStableVersion = async () => {
try { try {
const stableResponse = await fetch('https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/stable/app/commit.json'); const stableResponse = await fetch(
`https://raw.githubusercontent.com/stackblitz-labs/bolt.diy/refs/tags/v${commitJson.version}/app/commit.json`,
);
if (!stableResponse.ok) { if (!stableResponse.ok) {
console.warn('Failed to fetch stable commit info'); console.warn('Failed to fetch stable commit info');
return false; return false;
} }
const stableData = await stableResponse.json() as CommitData;
const stableData = (await stableResponse.json()) as CommitData;
return commit.commit === stableData.commit; return commit.commit === stableData.commit;
} catch (error) { } catch (error) {
console.warn('Error checking stable version:', error); console.warn('Error checking stable version:', error);
@ -84,17 +94,30 @@ export function useSettings() {
isLocalModelsEnabled.set(savedLocalModels === 'true'); isLocalModelsEnabled.set(savedLocalModels === 'true');
} }
const promptId = Cookies.get('promptId');
if (promptId) {
promptStore.set(promptId);
}
// load latest branch setting from cookies or determine based on version // load latest branch setting from cookies or determine based on version
const savedLatestBranch = Cookies.get('useLatestBranch'); const savedLatestBranch = Cookies.get('isLatestBranch');
if (savedLatestBranch === undefined) { let checkCommit = Cookies.get('commitHash');
if (checkCommit === undefined) {
checkCommit = commit.commit;
}
if (savedLatestBranch === undefined || checkCommit !== commit.commit) {
// If setting hasn't been set by user, check version // If setting hasn't been set by user, check version
checkIsStableVersion().then(isStable => { checkIsStableVersion().then((isStable) => {
const shouldUseLatest = !isStable; const shouldUseLatest = !isStable;
latestBranch.set(shouldUseLatest); latestBranchStore.set(shouldUseLatest);
Cookies.set('useLatestBranch', String(shouldUseLatest)); Cookies.set('isLatestBranch', String(shouldUseLatest));
Cookies.set('commitHash', String(commit.commit));
}); });
} else { } else {
latestBranch.set(savedLatestBranch === 'true'); latestBranchStore.set(savedLatestBranch === 'true');
} }
}, []); }, []);
@ -147,10 +170,14 @@ export function useSettings() {
Cookies.set('isLocalModelsEnabled', String(enabled)); Cookies.set('isLocalModelsEnabled', String(enabled));
}, []); }, []);
const setPromptId = useCallback((promptId: string) => {
promptStore.set(promptId);
Cookies.set('promptId', promptId);
}, []);
const enableLatestBranch = useCallback((enabled: boolean) => { const enableLatestBranch = useCallback((enabled: boolean) => {
latestBranch.set(enabled); latestBranchStore.set(enabled);
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`); logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
Cookies.set('useLatestBranch', String(enabled)); Cookies.set('isLatestBranch', String(enabled));
}, []); }, []);
return { return {
@ -163,7 +190,9 @@ export function useSettings() {
enableEventLogs, enableEventLogs,
isLocalModel, isLocalModel,
enableLocalModels, enableLocalModels,
useLatestBranch: useLatest, promptId,
setPromptId,
isLatestBranch,
enableLatestBranch, enableLatestBranch,
}; };
} }

View File

@ -47,4 +47,6 @@ export const isEventLogsEnabled = atom(false);
export const isLocalModelsEnabled = atom(true); export const isLocalModelsEnabled = atom(true);
export const latestBranch = atom(false); export const promptStore = atom<string>('default');
export const latestBranchStore = atom(false);

View File

@ -1,6 +1,7 @@
import { type ActionFunctionArgs } from '@remix-run/cloudflare'; import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { createDataStream } from 'ai';
import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants'; import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
import { CONTINUE_PROMPT } from '~/lib/.server/llm/prompts'; import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text'; import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
import SwitchableStream from '~/lib/.server/llm/switchable-stream'; import SwitchableStream from '~/lib/.server/llm/switchable-stream';
import type { IProviderSetting } from '~/types/model'; import type { IProviderSetting } from '~/types/model';
@ -9,17 +10,15 @@ export async function action(args: ActionFunctionArgs) {
return chatAction(args); return chatAction(args);
} }
function parseCookies(cookieHeader: string) { function parseCookies(cookieHeader: string): Record<string, string> {
const cookies: any = {}; const cookies: Record<string, string> = {};
// Split the cookie string by semicolons and spaces
const items = cookieHeader.split(';').map((cookie) => cookie.trim()); const items = cookieHeader.split(';').map((cookie) => cookie.trim());
items.forEach((item) => { items.forEach((item) => {
const [name, ...rest] = item.split('='); const [name, ...rest] = item.split('=');
if (name && rest) { if (name && rest) {
// Decode the name and value, and join value parts in case it contains '='
const decodedName = decodeURIComponent(name.trim()); const decodedName = decodeURIComponent(name.trim());
const decodedValue = decodeURIComponent(rest.join('=').trim()); const decodedValue = decodeURIComponent(rest.join('=').trim());
cookies[decodedName] = decodedValue; cookies[decodedName] = decodedValue;
@ -30,14 +29,13 @@ function parseCookies(cookieHeader: string) {
} }
async function chatAction({ context, request }: ActionFunctionArgs) { async function chatAction({ context, request }: ActionFunctionArgs) {
const { messages, files } = await request.json<{ const { messages, files, promptId } = await request.json<{
messages: Messages; messages: Messages;
files: any; files: any;
promptId?: string;
}>(); }>();
const cookieHeader = request.headers.get('Cookie'); const cookieHeader = request.headers.get('Cookie');
// Parse the cookie's value (returns an object or null if no cookie exists)
const apiKeys = JSON.parse(parseCookies(cookieHeader || '').apiKeys || '{}'); const apiKeys = JSON.parse(parseCookies(cookieHeader || '').apiKeys || '{}');
const providerSettings: Record<string, IProviderSetting> = JSON.parse( const providerSettings: Record<string, IProviderSetting> = JSON.parse(
parseCookies(cookieHeader || '').providers || '{}', parseCookies(cookieHeader || '').providers || '{}',
@ -45,12 +43,42 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
const stream = new SwitchableStream(); const stream = new SwitchableStream();
const cumulativeUsage = {
completionTokens: 0,
promptTokens: 0,
totalTokens: 0,
};
try { try {
const options: StreamingOptions = { const options: StreamingOptions = {
toolChoice: 'none', toolChoice: 'none',
onFinish: async ({ text: content, finishReason }) => { onFinish: async ({ text: content, finishReason, usage }) => {
console.log('usage', usage);
if (usage) {
cumulativeUsage.completionTokens += usage.completionTokens || 0;
cumulativeUsage.promptTokens += usage.promptTokens || 0;
cumulativeUsage.totalTokens += usage.totalTokens || 0;
}
if (finishReason !== 'length') { if (finishReason !== 'length') {
return stream.close(); return stream
.switchSource(
createDataStream({
async execute(dataStream) {
dataStream.writeMessageAnnotation({
type: 'usage',
value: {
completionTokens: cumulativeUsage.completionTokens,
promptTokens: cumulativeUsage.promptTokens,
totalTokens: cumulativeUsage.totalTokens,
},
});
},
onError: (error: any) => `Custom error: ${error.message}`,
}),
)
.then(() => stream.close());
} }
if (stream.switches >= MAX_RESPONSE_SEGMENTS) { if (stream.switches >= MAX_RESPONSE_SEGMENTS) {
@ -71,9 +99,10 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
apiKeys, apiKeys,
files, files,
providerSettings, providerSettings,
promptId,
}); });
return stream.switchSource(result.toAIStream()); return stream.switchSource(result.toDataStream());
}, },
}; };
@ -84,9 +113,10 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
apiKeys, apiKeys,
files, files,
providerSettings, providerSettings,
promptId,
}); });
stream.switchSource(result.toAIStream()); stream.switchSource(result.toDataStream());
return new Response(stream.readable, { return new Response(stream.readable, {
status: 200, status: 200,
@ -95,7 +125,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
}, },
}); });
} catch (error: any) { } catch (error: any) {
console.log(error); console.error(error);
if (error.message?.includes('API key')) { if (error.message?.includes('API key')) {
throw new Response('Invalid or missing API key', { throw new Response('Invalid or missing API key', {

View File

@ -1,5 +1,6 @@
import { type ActionFunctionArgs } from '@remix-run/cloudflare'; import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { StreamingTextResponse, parseStreamPart } from 'ai';
//import { StreamingTextResponse, parseStreamPart } from 'ai';
import { streamText } from '~/lib/.server/llm/stream-text'; import { streamText } from '~/lib/.server/llm/stream-text';
import { stripIndents } from '~/utils/stripIndent'; import { stripIndents } from '~/utils/stripIndent';
import type { IProviderSetting, ProviderInfo } from '~/types/model'; import type { IProviderSetting, ProviderInfo } from '~/types/model';
@ -86,7 +87,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
- Use professional language - Use professional language
For invalid or unclear prompts: For invalid or unclear prompts:
- Respond with a clear, professional guidance message - Respond with clear, professional guidance
- Keep responses concise and actionable - Keep responses concise and actionable
- Maintain a helpful, constructive tone - Maintain a helpful, constructive tone
- Focus on what the user should provide - Focus on what the user should provide
@ -113,7 +114,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
for (const line of lines) { for (const line of lines) {
try { try {
const parsed = parseStreamPart(line); const parsed = JSON.parse(line);
if (parsed.type === 'text') { if (parsed.type === 'text') {
controller.enqueue(encoder.encode(parsed.value)); controller.enqueue(encoder.encode(parsed.value));
@ -128,7 +129,12 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
const transformedStream = result.toDataStream().pipeThrough(transformStream); const transformedStream = result.toDataStream().pipeThrough(transformStream);
return new StreamingTextResponse(transformedStream); return new Response(transformedStream, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
} catch (error: unknown) { } catch (error: unknown) {
console.log(error); console.log(error);

View File

@ -73,7 +73,7 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"ai": "^3.4.33", "ai": "^4.0.13",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"diff": "^5.2.0", "diff": "^5.2.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",

View File

@ -141,8 +141,8 @@ importers:
specifier: ^5.5.0 specifier: ^5.5.0
version: 5.5.0 version: 5.5.0
ai: ai:
specifier: ^3.4.33 specifier: ^4.0.13
version: 3.4.33(react@18.3.1)(sswr@2.1.0(svelte@5.4.0))(svelte@5.4.0)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) version: 4.0.18(react@18.3.1)(zod@3.23.8)
date-fns: date-fns:
specifier: ^3.6.0 specifier: ^3.6.0
version: 3.6.0 version: 3.6.0
@ -351,15 +351,6 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/provider-utils@1.0.22':
resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
'@ai-sdk/provider-utils@1.0.9': '@ai-sdk/provider-utils@1.0.9':
resolution: {integrity: sha512-yfdanjUiCJbtGoRGXrcrmXn0pTyDfRIeY6ozDG96D66f2wupZaZvAgKptUa3zDYXtUCQQvcNJ+tipBBfQD/UYA==} resolution: {integrity: sha512-yfdanjUiCJbtGoRGXrcrmXn0pTyDfRIeY6ozDG96D66f2wupZaZvAgKptUa3zDYXtUCQQvcNJ+tipBBfQD/UYA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -378,6 +369,15 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/provider-utils@2.0.4':
resolution: {integrity: sha512-GMhcQCZbwM6RoZCri0MWeEWXRt/T+uCxsmHEsTwNvEH3GDjNzchfX25C8ftry2MeEOOn6KfqCLSKomcgK6RoOg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
'@ai-sdk/provider@0.0.12': '@ai-sdk/provider@0.0.12':
resolution: {integrity: sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==} resolution: {integrity: sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -390,16 +390,16 @@ packages:
resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==} resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@ai-sdk/provider@0.0.26':
resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==}
engines: {node: '>=18'}
'@ai-sdk/provider@1.0.1': '@ai-sdk/provider@1.0.1':
resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==} resolution: {integrity: sha512-mV+3iNDkzUsZ0pR2jG0sVzU6xtQY5DtSCBy3JFycLp6PwjyLw/iodfL3MwdmMCRJWgs3dadcHejRnMvF9nGTBg==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@ai-sdk/react@0.0.70': '@ai-sdk/provider@1.0.2':
resolution: {integrity: sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==} resolution: {integrity: sha512-YYtP6xWQyaAf5LiWLJ+ycGTOeBLWrED7LUrvc+SQIWhGaneylqbaGsyQL7VouQUeQ4JZ1qKYZuhmi3W56HADPA==}
engines: {node: '>=18'}
'@ai-sdk/react@1.0.6':
resolution: {integrity: sha512-8Hkserq0Ge6AEi7N4hlv2FkfglAGbkoAXEZ8YSp255c3PbnZz6+/5fppw+aROmZMOfNwallSRuy1i/iPa2rBpQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc react: ^18 || ^19 || ^19.0.0-rc
@ -410,26 +410,8 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/solid@0.0.54': '@ai-sdk/ui-utils@1.0.5':
resolution: {integrity: sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==} resolution: {integrity: sha512-DGJSbDf+vJyWmFNexSPUsS1AAy7gtsmFmoSyNbNbJjwl9hRIf2dknfA1V0ahx6pg3NNklNYFm53L8Nphjovfvg==}
engines: {node: '>=18'}
peerDependencies:
solid-js: ^1.7.7
peerDependenciesMeta:
solid-js:
optional: true
'@ai-sdk/svelte@0.0.57':
resolution: {integrity: sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==}
engines: {node: '>=18'}
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
peerDependenciesMeta:
svelte:
optional: true
'@ai-sdk/ui-utils@0.0.50':
resolution: {integrity: sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.0.0 zod: ^3.0.0
@ -437,15 +419,6 @@ packages:
zod: zod:
optional: true optional: true
'@ai-sdk/vue@0.0.59':
resolution: {integrity: sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==}
engines: {node: '>=18'}
peerDependencies:
vue: ^3.3.4
peerDependenciesMeta:
vue:
optional: true
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -2370,35 +2343,6 @@ packages:
'@vitest/utils@2.1.8': '@vitest/utils@2.1.8':
resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==}
'@vue/compiler-core@3.5.13':
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
'@vue/compiler-dom@3.5.13':
resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
'@vue/compiler-sfc@3.5.13':
resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
'@vue/compiler-ssr@3.5.13':
resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
'@vue/reactivity@3.5.13':
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
'@vue/runtime-core@3.5.13':
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
'@vue/runtime-dom@3.5.13':
resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
'@vue/server-renderer@3.5.13':
resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
peerDependencies:
vue: 3.5.13
'@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
'@web3-storage/multipart-parser@1.0.0': '@web3-storage/multipart-parser@1.0.0':
resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==}
@ -2434,11 +2378,6 @@ packages:
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn-typescript@1.4.13:
resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==}
peerDependencies:
acorn: '>=8.9.0'
acorn-walk@8.3.4: acorn-walk@8.3.4:
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@ -2452,24 +2391,15 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'} engines: {node: '>=8'}
ai@3.4.33: ai@4.0.18:
resolution: {integrity: sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==} resolution: {integrity: sha512-BTWzalLNE1LQphEka5xzJXDs5v4xXy1Uzr7dAVk+C/CnO3WNpuMBgrCymwUv0VrWaWc8xMQuh+OqsT7P7JyekQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
openai: ^4.42.0
react: ^18 || ^19 || ^19.0.0-rc react: ^18 || ^19 || ^19.0.0-rc
sswr: ^2.1.0
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
zod: ^3.0.0 zod: ^3.0.0
peerDependenciesMeta: peerDependenciesMeta:
openai:
optional: true
react: react:
optional: true optional: true
sswr:
optional: true
svelte:
optional: true
zod: zod:
optional: true optional: true
@ -2506,10 +2436,6 @@ packages:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'} engines: {node: '>=10'}
aria-query@5.3.2:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
array-flatten@1.1.1: array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
@ -2537,10 +2463,6 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
bail@2.0.2: bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@ -3149,9 +3071,6 @@ packages:
jiti: jiti:
optional: true optional: true
esm-env@1.2.1:
resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==}
espree@10.3.0: espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -3164,9 +3083,6 @@ packages:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
esrap@1.2.3:
resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==}
esrecurse@4.3.0: esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -3820,9 +3736,6 @@ packages:
resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
locate-path@6.0.0: locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5190,11 +5103,6 @@ packages:
resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
sswr@2.1.0:
resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==}
peerDependencies:
svelte: ^4.0.0 || ^5.0.0-next.0
stackback@0.0.2: stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@ -5285,23 +5193,11 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
svelte@5.4.0:
resolution: {integrity: sha512-2I/mjD8cXDpKfdfUK+T6yo/OzugMXIm8lhyJUFM5F/gICMYnkl3C/+4cOSpia8TqpDsi6Qfm5+fdmBNMNmaf2g==}
engines: {node: '>=18'}
swr@2.2.5: swr@2.2.5:
resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
peerDependencies: peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 react: ^16.11.0 || ^17.0.0 || ^18.0.0
swrev@4.0.0:
resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==}
swrv@1.0.4:
resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==}
peerDependencies:
vue: '>=3.2.26 < 4'
sync-child-process@1.0.2: sync-child-process@1.0.2:
resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -5721,14 +5617,6 @@ packages:
vm-browserify@1.1.2: vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
vue@3.5.13:
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
w3c-keyname@2.2.8: w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
@ -5843,9 +5731,6 @@ packages:
youch@3.3.4: youch@3.3.4:
resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==} resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==}
zimmerframe@1.1.2:
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
zod-to-json-schema@3.23.5: zod-to-json-schema@3.23.5:
resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==}
peerDependencies: peerDependencies:
@ -5908,15 +5793,6 @@ snapshots:
optionalDependencies: optionalDependencies:
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/provider-utils@1.0.22(zod@3.23.8)':
dependencies:
'@ai-sdk/provider': 0.0.26
eventsource-parser: 1.1.2
nanoid: 3.3.8
secure-json-parse: 2.7.0
optionalDependencies:
zod: 3.23.8
'@ai-sdk/provider-utils@1.0.9(zod@3.23.8)': '@ai-sdk/provider-utils@1.0.9(zod@3.23.8)':
dependencies: dependencies:
'@ai-sdk/provider': 0.0.17 '@ai-sdk/provider': 0.0.17
@ -5935,6 +5811,15 @@ snapshots:
optionalDependencies: optionalDependencies:
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/provider-utils@2.0.4(zod@3.23.8)':
dependencies:
'@ai-sdk/provider': 1.0.2
eventsource-parser: 3.0.0
nanoid: 3.3.8
secure-json-parse: 2.7.0
optionalDependencies:
zod: 3.23.8
'@ai-sdk/provider@0.0.12': '@ai-sdk/provider@0.0.12':
dependencies: dependencies:
json-schema: 0.4.0 json-schema: 0.4.0
@ -5947,61 +5832,32 @@ snapshots:
dependencies: dependencies:
json-schema: 0.4.0 json-schema: 0.4.0
'@ai-sdk/provider@0.0.26':
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@1.0.1': '@ai-sdk/provider@1.0.1':
dependencies: dependencies:
json-schema: 0.4.0 json-schema: 0.4.0
'@ai-sdk/react@0.0.70(react@18.3.1)(zod@3.23.8)': '@ai-sdk/provider@1.0.2':
dependencies: dependencies:
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) json-schema: 0.4.0
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8)
'@ai-sdk/react@1.0.6(react@18.3.1)(zod@3.23.8)':
dependencies:
'@ai-sdk/provider-utils': 2.0.4(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.5(zod@3.23.8)
swr: 2.2.5(react@18.3.1) swr: 2.2.5(react@18.3.1)
throttleit: 2.1.0 throttleit: 2.1.0
optionalDependencies: optionalDependencies:
react: 18.3.1 react: 18.3.1
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/solid@0.0.54(zod@3.23.8)': '@ai-sdk/ui-utils@1.0.5(zod@3.23.8)':
dependencies: dependencies:
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) '@ai-sdk/provider': 1.0.2
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) '@ai-sdk/provider-utils': 2.0.4(zod@3.23.8)
transitivePeerDependencies:
- zod
'@ai-sdk/svelte@0.0.57(svelte@5.4.0)(zod@3.23.8)':
dependencies:
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8)
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8)
sswr: 2.1.0(svelte@5.4.0)
optionalDependencies:
svelte: 5.4.0
transitivePeerDependencies:
- zod
'@ai-sdk/ui-utils@0.0.50(zod@3.23.8)':
dependencies:
'@ai-sdk/provider': 0.0.26
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8)
json-schema: 0.4.0
secure-json-parse: 2.7.0
zod-to-json-schema: 3.23.5(zod@3.23.8) zod-to-json-schema: 3.23.5(zod@3.23.8)
optionalDependencies: optionalDependencies:
zod: 3.23.8 zod: 3.23.8
'@ai-sdk/vue@0.0.59(vue@3.5.13(typescript@5.7.2))(zod@3.23.8)':
dependencies:
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8)
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8)
swrv: 1.0.4(vue@3.5.13(typescript@5.7.2))
optionalDependencies:
vue: 3.5.13(typescript@5.7.2)
transitivePeerDependencies:
- zod
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
dependencies: dependencies:
'@jridgewell/gen-mapping': 0.3.5 '@jridgewell/gen-mapping': 0.3.5
@ -8045,60 +7901,6 @@ snapshots:
loupe: 3.1.2 loupe: 3.1.2
tinyrainbow: 1.2.0 tinyrainbow: 1.2.0
'@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.26.2
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.13':
dependencies:
'@vue/compiler-core': 3.5.13
'@vue/shared': 3.5.13
'@vue/compiler-sfc@3.5.13':
dependencies:
'@babel/parser': 7.26.2
'@vue/compiler-core': 3.5.13
'@vue/compiler-dom': 3.5.13
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
estree-walker: 2.0.2
magic-string: 0.30.14
postcss: 8.4.49
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.13':
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/shared': 3.5.13
'@vue/reactivity@3.5.13':
dependencies:
'@vue/shared': 3.5.13
'@vue/runtime-core@3.5.13':
dependencies:
'@vue/reactivity': 3.5.13
'@vue/shared': 3.5.13
'@vue/runtime-dom@3.5.13':
dependencies:
'@vue/reactivity': 3.5.13
'@vue/runtime-core': 3.5.13
'@vue/shared': 3.5.13
csstype: 3.1.3
'@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
vue: 3.5.13(typescript@5.7.2)
'@vue/shared@3.5.13': {}
'@web3-storage/multipart-parser@1.0.0': {} '@web3-storage/multipart-parser@1.0.0': {}
'@webcontainer/api@1.3.0-internal.10': {} '@webcontainer/api@1.3.0-internal.10': {}
@ -8129,10 +7931,6 @@ snapshots:
dependencies: dependencies:
acorn: 8.14.0 acorn: 8.14.0
acorn-typescript@1.4.13(acorn@8.14.0):
dependencies:
acorn: 8.14.0
acorn-walk@8.3.4: acorn-walk@8.3.4:
dependencies: dependencies:
acorn: 8.14.0 acorn: 8.14.0
@ -8144,29 +7942,18 @@ snapshots:
clean-stack: 2.2.0 clean-stack: 2.2.0
indent-string: 4.0.0 indent-string: 4.0.0
ai@3.4.33(react@18.3.1)(sswr@2.1.0(svelte@5.4.0))(svelte@5.4.0)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8): ai@4.0.18(react@18.3.1)(zod@3.23.8):
dependencies: dependencies:
'@ai-sdk/provider': 0.0.26 '@ai-sdk/provider': 1.0.2
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) '@ai-sdk/provider-utils': 2.0.4(zod@3.23.8)
'@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.23.8) '@ai-sdk/react': 1.0.6(react@18.3.1)(zod@3.23.8)
'@ai-sdk/solid': 0.0.54(zod@3.23.8) '@ai-sdk/ui-utils': 1.0.5(zod@3.23.8)
'@ai-sdk/svelte': 0.0.57(svelte@5.4.0)(zod@3.23.8)
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8)
'@ai-sdk/vue': 0.0.59(vue@3.5.13(typescript@5.7.2))(zod@3.23.8)
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
eventsource-parser: 1.1.2
json-schema: 0.4.0
jsondiffpatch: 0.6.0 jsondiffpatch: 0.6.0
secure-json-parse: 2.7.0
zod-to-json-schema: 3.23.5(zod@3.23.8) zod-to-json-schema: 3.23.5(zod@3.23.8)
optionalDependencies: optionalDependencies:
react: 18.3.1 react: 18.3.1
sswr: 2.1.0(svelte@5.4.0)
svelte: 5.4.0
zod: 3.23.8 zod: 3.23.8
transitivePeerDependencies:
- solid-js
- vue
ajv@6.12.6: ajv@6.12.6:
dependencies: dependencies:
@ -8198,8 +7985,6 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
aria-query@5.3.2: {}
array-flatten@1.1.1: {} array-flatten@1.1.1: {}
as-table@1.0.55: as-table@1.0.55:
@ -8230,8 +8015,6 @@ snapshots:
dependencies: dependencies:
possible-typed-array-names: 1.0.0 possible-typed-array-names: 1.0.0
axobject-query@4.1.0: {}
bail@2.0.2: {} bail@2.0.2: {}
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
@ -8931,8 +8714,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
esm-env@1.2.1: {}
espree@10.3.0: espree@10.3.0:
dependencies: dependencies:
acorn: 8.14.0 acorn: 8.14.0
@ -8949,11 +8730,6 @@ snapshots:
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
esrap@1.2.3:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
'@types/estree': 1.0.6
esrecurse@4.3.0: esrecurse@4.3.0:
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
@ -9680,8 +9456,6 @@ snapshots:
mlly: 1.7.3 mlly: 1.7.3
pkg-types: 1.2.1 pkg-types: 1.2.1
locate-character@3.0.0: {}
locate-path@6.0.0: locate-path@6.0.0:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
@ -11492,11 +11266,6 @@ snapshots:
dependencies: dependencies:
minipass: 7.1.2 minipass: 7.1.2
sswr@2.1.0(svelte@5.4.0):
dependencies:
svelte: 5.4.0
swrev: 4.0.0
stackback@0.0.2: {} stackback@0.0.2: {}
stacktracey@2.1.8: stacktracey@2.1.8:
@ -11587,34 +11356,12 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
svelte@5.4.0:
dependencies:
'@ampproject/remapping': 2.3.0
'@jridgewell/sourcemap-codec': 1.5.0
'@types/estree': 1.0.6
acorn: 8.14.0
acorn-typescript: 1.4.13(acorn@8.14.0)
aria-query: 5.3.2
axobject-query: 4.1.0
esm-env: 1.2.1
esrap: 1.2.3
is-reference: 3.0.3
locate-character: 3.0.0
magic-string: 0.30.14
zimmerframe: 1.1.2
swr@2.2.5(react@18.3.1): swr@2.2.5(react@18.3.1):
dependencies: dependencies:
client-only: 0.0.1 client-only: 0.0.1
react: 18.3.1 react: 18.3.1
use-sync-external-store: 1.2.2(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1)
swrev@4.0.0: {}
swrv@1.0.4(vue@3.5.13(typescript@5.7.2)):
dependencies:
vue: 3.5.13(typescript@5.7.2)
sync-child-process@1.0.2: sync-child-process@1.0.2:
dependencies: dependencies:
sync-message-port: 1.1.3 sync-message-port: 1.1.3
@ -12092,16 +11839,6 @@ snapshots:
vm-browserify@1.1.2: {} vm-browserify@1.1.2: {}
vue@3.5.13(typescript@5.7.2):
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/compiler-sfc': 3.5.13
'@vue/runtime-dom': 3.5.13
'@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2))
'@vue/shared': 3.5.13
optionalDependencies:
typescript: 5.7.2
w3c-keyname@2.2.8: {} w3c-keyname@2.2.8: {}
wcwidth@1.0.1: wcwidth@1.0.1:
@ -12214,8 +11951,6 @@ snapshots:
mustache: 4.2.0 mustache: 4.2.0
stacktracey: 2.1.8 stacktracey: 2.1.8
zimmerframe@1.1.2: {}
zod-to-json-schema@3.23.5(zod@3.23.8): zod-to-json-schema@3.23.5(zod@3.23.8):
dependencies: dependencies:
zod: 3.23.8 zod: 3.23.8

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB