mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-01-23 11:17:02 +00:00
Merge branch 'main' into terminal-error-detection
This commit is contained in:
commit
42bde1cae4
14
.github/workflows/commit.yaml
vendored
14
.github/workflows/commit.yaml
vendored
@ -17,20 +17,18 @@ jobs:
|
||||
- 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: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
- name: Get the latest commit hash
|
||||
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
|
||||
run: |
|
||||
echo "CURRENT_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
|
||||
echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json
|
||||
echo "{ \"commit\": \"$COMMIT_HASH\", \"version\": \"$CURRENT_VERSION\" }" > app/commit.json
|
||||
|
||||
- name: Commit and push the update
|
||||
run: |
|
||||
|
32
.github/workflows/semantic-pr.yaml
vendored
Normal file
32
.github/workflows/semantic-pr.yaml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Semantic Pull Request
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
permissions:
|
||||
pull-requests: read
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# https://github.com/amannn/action-semantic-pull-request/releases/tag/v5.5.3
|
||||
- uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
subjectPatternError: |
|
||||
The subject "{subject}" found in the pull request title "{title}"
|
||||
didn't match the configured pattern. Please ensure that the subject
|
||||
doesn't start with an uppercase character.
|
||||
types: |
|
||||
fix
|
||||
feat
|
||||
chore
|
||||
build
|
||||
ci
|
||||
perf
|
||||
docs
|
||||
refactor
|
||||
revert
|
||||
test
|
9
.github/workflows/update-stable.yml
vendored
9
.github/workflows/update-stable.yml
vendored
@ -158,12 +158,15 @@ jobs:
|
||||
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and Tag Release
|
||||
- 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
|
||||
echo "NEW_VERSION=${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Commit and Tag Release
|
||||
run: |
|
||||
git pull
|
||||
echo "{ \"commit\": \"$COMMIT_HASH\" , \"version\": \"$CURRENT_VERSION\" }" > app/commit.json
|
||||
echo "{ \"commit\": \"$COMMIT_HASH\", \"version\": \"$NEW_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 tag "v${{ steps.bump_version.outputs.new_version }}"
|
||||
|
@ -43,6 +43,9 @@ https://thinktank.ottomator.ai
|
||||
- ✅ Mobile friendly (@qwikode)
|
||||
- ✅ Better prompt enhancing (@SujalXplores)
|
||||
- ✅ 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)
|
||||
- ✅ Selection tool to target changes visually (@emcconnell)
|
||||
- ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)
|
||||
|
@ -1 +1 @@
|
||||
{ "commit": "eb1d5417e77e699e0489f09814e87fb5afed9dd5" , "version": "" }
|
||||
{ "commit": "1e72d52278730f7d22448be9d5cf2daf12559486", "version": "0.0.2" }
|
||||
|
@ -77,6 +77,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
input = '',
|
||||
enhancingPrompt,
|
||||
handleInputChange,
|
||||
|
||||
// promptEnhanced,
|
||||
enhancePrompt,
|
||||
sendMessage,
|
||||
handleStop,
|
||||
|
@ -20,6 +20,7 @@ import Cookies from 'js-cookie';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
import { useSettings } from '~/lib/hooks/useSettings';
|
||||
import type { ProviderInfo } from '~/types/model';
|
||||
import { useSearchParams } from '@remix-run/react';
|
||||
|
||||
const toastAnimation = cssTransition({
|
||||
enter: 'animated fadeInRight',
|
||||
@ -92,8 +93,9 @@ export const ChatImpl = memo(
|
||||
const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]); // Move here
|
||||
const [imageDataList, setImageDataList] = useState<string[]>([]); // Move here
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const files = useStore(workbenchStore.files);
|
||||
const { activeProviders } = useSettings();
|
||||
const { activeProviders, promptId } = useSettings();
|
||||
|
||||
const [model, setModel] = useState(() => {
|
||||
const savedModel = Cookies.get('selectedModel');
|
||||
@ -115,6 +117,7 @@ export const ChatImpl = memo(
|
||||
body: {
|
||||
apiKeys,
|
||||
files,
|
||||
promptId,
|
||||
},
|
||||
sendExtraMessageFields: true,
|
||||
onError: (error) => {
|
||||
@ -137,6 +140,24 @@ export const ChatImpl = memo(
|
||||
initialMessages,
|
||||
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
|
||||
});
|
||||
useEffect(() => {
|
||||
const prompt = searchParams.get('prompt');
|
||||
console.log(prompt, searchParams, model, provider);
|
||||
|
||||
if (prompt) {
|
||||
setSearchParams({});
|
||||
runAnimation();
|
||||
append({
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${prompt}`,
|
||||
},
|
||||
] as any, // Type assertion to bypass compiler check
|
||||
});
|
||||
}
|
||||
}, [model, provider, searchParams]);
|
||||
|
||||
const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
|
||||
const { parsedMessages, parseMessages } = useMessageParser();
|
||||
|
@ -53,7 +53,7 @@ export const CodeBlock = memo(
|
||||
<div
|
||||
className={classNames(
|
||||
styles.CopyButtonContainer,
|
||||
'bg-white absolute top-[10px] right-[10px] rounded-md z-10 text-lg flex items-center justify-center opacity-0 group-hover:opacity-100',
|
||||
'bg-transparant absolute top-[10px] right-[10px] rounded-md z-10 text-lg flex items-center justify-center opacity-0 group-hover:opacity-100',
|
||||
{
|
||||
'rounded-l-0 opacity-100': copied,
|
||||
},
|
||||
@ -62,7 +62,7 @@ export const CodeBlock = memo(
|
||||
{!disableCopy && (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex items-center bg-transparent p-[6px] justify-center before:bg-white before:rounded-l-md before:text-gray-500 before:border-r before:border-gray-300',
|
||||
'flex items-center bg-accent-500 p-[6px] justify-center before:bg-white before:rounded-l-md before:text-gray-500 before:border-r before:border-gray-300 rounded-md transition-theme',
|
||||
{
|
||||
'before:opacity-0': !copied,
|
||||
'before:opacity-100': copied,
|
||||
|
@ -8,6 +8,8 @@ import { Chat } from '~/components/chat/Chat.client';
|
||||
import { useGit } from '~/lib/hooks/useGit';
|
||||
import { useChatHistory } from '~/lib/persistence';
|
||||
import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
|
||||
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const IGNORE_PATTERNS = [
|
||||
'node_modules/**',
|
||||
@ -38,6 +40,7 @@ export function GitUrlImport() {
|
||||
const { ready: historyReady, importChat } = useChatHistory();
|
||||
const { ready: gitReady, gitClone } = useGit();
|
||||
const [imported, setImported] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const importRepo = async (repoUrl?: string) => {
|
||||
if (!gitReady && !historyReady) {
|
||||
@ -109,9 +112,23 @@ ${file.content}
|
||||
return;
|
||||
}
|
||||
|
||||
importRepo(url);
|
||||
importRepo(url).catch((error) => {
|
||||
console.error('Error importing repo:', error);
|
||||
toast.error('Failed to import repository');
|
||||
setLoading(false);
|
||||
window.location.href = '/';
|
||||
});
|
||||
setImported(true);
|
||||
}, [searchParams, historyReady, gitReady, imported]);
|
||||
|
||||
return <ClientOnly fallback={<BaseChat />}>{() => <Chat />}</ClientOnly>;
|
||||
return (
|
||||
<ClientOnly fallback={<BaseChat />}>
|
||||
{() => (
|
||||
<>
|
||||
<Chat />
|
||||
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
|
||||
</>
|
||||
)}
|
||||
</ClientOnly>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,93 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import Cookies from 'js-cookie';
|
||||
import { logStore } from '~/lib/stores/logs';
|
||||
|
||||
interface GitHubUserResponse {
|
||||
login: string;
|
||||
id: number;
|
||||
[key: string]: any; // for other properties we don't explicitly need
|
||||
}
|
||||
|
||||
export default function ConnectionsTab() {
|
||||
const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
|
||||
const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
|
||||
const handleSaveConnection = () => {
|
||||
Cookies.set('githubUsername', githubUsername);
|
||||
Cookies.set('githubToken', githubToken);
|
||||
logStore.logSystem('GitHub connection settings updated', {
|
||||
username: githubUsername,
|
||||
hasToken: !!githubToken,
|
||||
});
|
||||
toast.success('GitHub credentials saved successfully!');
|
||||
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
|
||||
useEffect(() => {
|
||||
// Check if credentials exist and verify them
|
||||
if (githubUsername && githubToken) {
|
||||
verifyGitHubCredentials();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const verifyGitHubCredentials = async () => {
|
||||
setIsVerifying(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = (await response.json()) as GitHubUserResponse;
|
||||
|
||||
if (data.login === githubUsername) {
|
||||
setIsConnected(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
setIsConnected(false);
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error verifying GitHub credentials:', error);
|
||||
setIsConnected(false);
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveConnection = async () => {
|
||||
if (!githubUsername || !githubToken) {
|
||||
toast.error('Please provide both GitHub username and token');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsVerifying(true);
|
||||
|
||||
const isValid = await verifyGitHubCredentials();
|
||||
|
||||
if (isValid) {
|
||||
Cookies.set('githubUsername', githubUsername);
|
||||
Cookies.set('githubToken', githubToken);
|
||||
logStore.logSystem('GitHub connection settings updated', {
|
||||
username: githubUsername,
|
||||
hasToken: !!githubToken,
|
||||
});
|
||||
toast.success('GitHub credentials verified and saved successfully!');
|
||||
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
|
||||
setIsConnected(true);
|
||||
} else {
|
||||
toast.error('Invalid GitHub credentials. Please check your username and token.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
Cookies.remove('githubUsername');
|
||||
Cookies.remove('githubToken');
|
||||
Cookies.remove('git:github.com');
|
||||
setGithubUsername('');
|
||||
setGithubToken('');
|
||||
setIsConnected(false);
|
||||
logStore.logSystem('GitHub connection removed');
|
||||
toast.success('GitHub connection removed successfully!');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -28,7 +100,8 @@ export default function ConnectionsTab() {
|
||||
type="text"
|
||||
value={githubUsername}
|
||||
onChange={(e) => setGithubUsername(e.target.value)}
|
||||
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
||||
disabled={isVerifying}
|
||||
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@ -37,17 +110,41 @@ export default function ConnectionsTab() {
|
||||
type="password"
|
||||
value={githubToken}
|
||||
onChange={(e) => setGithubToken(e.target.value)}
|
||||
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
||||
disabled={isVerifying}
|
||||
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<button
|
||||
onClick={handleSaveConnection}
|
||||
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text"
|
||||
>
|
||||
Save Connection
|
||||
</button>
|
||||
<div className="flex mb-4 items-center">
|
||||
{!isConnected ? (
|
||||
<button
|
||||
onClick={handleSaveConnection}
|
||||
disabled={isVerifying || !githubUsername || !githubToken}
|
||||
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
|
||||
>
|
||||
{isVerifying ? (
|
||||
<>
|
||||
<div className="i-ph:spinner animate-spin mr-2" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
'Connect'
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
className="bg-bolt-elements-button-danger-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
)}
|
||||
{isConnected && (
|
||||
<span className="text-sm text-green-600 flex items-center">
|
||||
<div className="i-ph:check-circle mr-1" />
|
||||
Connected to GitHub
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -303,7 +303,7 @@ const checkProviderStatus = async (url: string | null, providerName: string): Pr
|
||||
};
|
||||
|
||||
export default function DebugTab() {
|
||||
const { providers, latestBranch } = useSettings();
|
||||
const { providers, isLatestBranch } = useSettings();
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderStatus[]>([]);
|
||||
const [updateMessage, setUpdateMessage] = useState<string>('');
|
||||
const [systemInfo] = useState<SystemInfo>(getSystemInfo());
|
||||
@ -363,7 +363,7 @@ export default function DebugTab() {
|
||||
setIsCheckingUpdate(true);
|
||||
setUpdateMessage('Checking for updates...');
|
||||
|
||||
const branchToCheck = latestBranch ? 'main' : 'stable';
|
||||
const branchToCheck = isLatestBranch ? 'main' : 'stable';
|
||||
console.log(`[Debug] Checking for updates against ${branchToCheck} branch`);
|
||||
|
||||
const localCommitResponse = await fetch(GITHUB_URLS.commitJson(branchToCheck));
|
||||
@ -391,7 +391,7 @@ export default function DebugTab() {
|
||||
} finally {
|
||||
setIsCheckingUpdate(false);
|
||||
}
|
||||
}, [isCheckingUpdate, latestBranch]);
|
||||
}, [isCheckingUpdate, isLatestBranch]);
|
||||
|
||||
const handleCopyToClipboard = useCallback(() => {
|
||||
const debugInfo = {
|
||||
@ -408,7 +408,7 @@ export default function DebugTab() {
|
||||
})),
|
||||
Version: {
|
||||
hash: versionHash.slice(0, 7),
|
||||
branch: latestBranch ? 'main' : 'stable',
|
||||
branch: isLatestBranch ? 'main' : 'stable',
|
||||
},
|
||||
Timestamp: new Date().toISOString(),
|
||||
};
|
||||
@ -416,7 +416,7 @@ export default function DebugTab() {
|
||||
navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)).then(() => {
|
||||
toast.success('Debug information copied to clipboard!');
|
||||
});
|
||||
}, [activeProviders, systemInfo, latestBranch]);
|
||||
}, [activeProviders, systemInfo, isLatestBranch]);
|
||||
|
||||
return (
|
||||
<div className="p-4 space-y-6">
|
||||
@ -523,7 +523,7 @@ export default function DebugTab() {
|
||||
<p className="text-sm font-medium text-bolt-elements-textPrimary font-mono">
|
||||
{versionHash.slice(0, 7)}
|
||||
<span className="ml-2 text-xs text-bolt-elements-textSecondary">
|
||||
(v{versionTag || '0.0.1'}) - {latestBranch ? 'nightly' : 'stable'}
|
||||
(v{versionTag || '0.0.1'}) - {isLatestBranch ? 'nightly' : 'stable'}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,10 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Switch } from '~/components/ui/Switch';
|
||||
import { PromptLibrary } from '~/lib/common/prompt-library';
|
||||
import { useSettings } from '~/lib/hooks/useSettings';
|
||||
|
||||
export default function FeaturesTab() {
|
||||
const { debug, enableDebugMode, isLocalModel, enableLocalModels, enableEventLogs, latestBranch, enableLatestBranch } =
|
||||
useSettings();
|
||||
const {
|
||||
debug,
|
||||
enableDebugMode,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
enableEventLogs,
|
||||
isLatestBranch,
|
||||
enableLatestBranch,
|
||||
promptId,
|
||||
setPromptId,
|
||||
} = useSettings();
|
||||
|
||||
const handleToggle = (enabled: boolean) => {
|
||||
enableDebugMode(enabled);
|
||||
@ -27,7 +37,7 @@ export default function FeaturesTab() {
|
||||
Check for updates against the main branch instead of stable
|
||||
</p>
|
||||
</div>
|
||||
<Switch className="ml-auto" checked={latestBranch} onCheckedChange={enableLatestBranch} />
|
||||
<Switch className="ml-auto" checked={isLatestBranch} onCheckedChange={enableLatestBranch} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,10 +47,30 @@ export default function FeaturesTab() {
|
||||
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
||||
Disclaimer: Experimental features may be unstable and are subject to change.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-bolt-elements-textPrimary">Experimental Providers</span>
|
||||
<Switch className="ml-auto" checked={isLocalModel} onCheckedChange={enableLocalModels} />
|
||||
</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 key={x.id} value={x.id}>
|
||||
{x.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { type ChatHistoryItem } from '~/lib/persistence';
|
||||
import WithTooltip from '~/components/ui/Tooltip';
|
||||
import { useEditChatDescription } from '~/lib/hooks';
|
||||
import { forwardRef, type ForwardedRef } from 'react';
|
||||
|
||||
interface HistoryItemProps {
|
||||
item: ChatHistoryItem;
|
||||
@ -103,25 +104,31 @@ export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: History
|
||||
);
|
||||
}
|
||||
|
||||
const ChatActionButton = ({
|
||||
toolTipContent,
|
||||
icon,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
toolTipContent: string;
|
||||
icon: string;
|
||||
className?: string;
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
btnTitle?: string;
|
||||
}) => {
|
||||
return (
|
||||
<WithTooltip tooltip={toolTipContent}>
|
||||
<button
|
||||
type="button"
|
||||
className={`scale-110 mr-2 hover:text-bolt-elements-item-contentAccent ${icon} ${className ? className : ''}`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</WithTooltip>
|
||||
);
|
||||
};
|
||||
const ChatActionButton = forwardRef(
|
||||
(
|
||||
{
|
||||
toolTipContent,
|
||||
icon,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
toolTipContent: string;
|
||||
icon: string;
|
||||
className?: string;
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
btnTitle?: string;
|
||||
},
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
return (
|
||||
<WithTooltip tooltip={toolTipContent}>
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={`scale-110 mr-2 hover:text-bolt-elements-item-contentAccent ${icon} ${className ? className : ''}`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</WithTooltip>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
14
app/components/ui/LoadingOverlay.tsx
Normal file
14
app/components/ui/LoadingOverlay.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
export const LoadingOverlay = ({ message = 'Loading...' }) => {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black/80 z-50 backdrop-blur-sm">
|
||||
{/* Loading content */}
|
||||
<div className="relative flex flex-col items-center gap-4 p-8 rounded-lg bg-bolt-elements-background-depth-2 shadow-lg">
|
||||
<div
|
||||
className={'i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress'}
|
||||
style={{ fontSize: '2rem' }}
|
||||
></div>
|
||||
<p className="text-lg text-bolt-elements-textTertiary">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,10 +1,20 @@
|
||||
import { convertToCoreMessages, streamText as _streamText } from 'ai';
|
||||
import { getModel } from '~/lib/.server/llm/model';
|
||||
import { MAX_TOKENS } from './constants';
|
||||
import { getSystemPrompt } from './prompts';
|
||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER, getModelList, MODEL_REGEX, PROVIDER_REGEX } from '~/utils/constants';
|
||||
import { getSystemPrompt } from '~/lib/common/prompts/prompts';
|
||||
import {
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
getModelList,
|
||||
MODEL_REGEX,
|
||||
MODIFICATIONS_TAG_NAME,
|
||||
PROVIDER_REGEX,
|
||||
WORK_DIR,
|
||||
} from '~/utils/constants';
|
||||
import ignore from 'ignore';
|
||||
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> {
|
||||
toolCallId: string;
|
||||
@ -139,8 +149,9 @@ export async function streamText(props: {
|
||||
apiKeys?: Record<string, string>;
|
||||
files?: FileMap;
|
||||
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 currentProvider = DEFAULT_PROVIDER.name;
|
||||
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;
|
||||
|
||||
let systemPrompt = getSystemPrompt();
|
||||
let systemPrompt =
|
||||
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
|
||||
cwd: WORK_DIR,
|
||||
allowedHtmlElements: allowedHTMLElements,
|
||||
modificationTagName: MODIFICATIONS_TAG_NAME,
|
||||
}) ?? getSystemPrompt();
|
||||
let codeContext = '';
|
||||
|
||||
if (files) {
|
||||
codeContext = createFilesContext(files);
|
||||
codeContext = '';
|
||||
systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
|
||||
}
|
||||
|
||||
|
49
app/lib/common/prompt-library.ts
Normal file
49
app/lib/common/prompt-library.ts
Normal 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);
|
||||
}
|
||||
}
|
199
app/lib/common/prompts/optimized.ts
Normal file
199
app/lib/common/prompts/optimized.ts
Normal 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.
|
||||
`;
|
||||
};
|
@ -4,6 +4,7 @@ import {
|
||||
isEventLogsEnabled,
|
||||
isLocalModelsEnabled,
|
||||
LOCAL_PROVIDERS,
|
||||
promptStore,
|
||||
providersStore,
|
||||
latestBranchStore,
|
||||
} from '~/lib/stores/settings';
|
||||
@ -24,8 +25,9 @@ export function useSettings() {
|
||||
const providers = useStore(providersStore);
|
||||
const debug = useStore(isDebugMode);
|
||||
const eventLogs = useStore(isEventLogsEnabled);
|
||||
const promptId = useStore(promptStore);
|
||||
const isLocalModel = useStore(isLocalModelsEnabled);
|
||||
const latestBranch = useStore(latestBranchStore);
|
||||
const isLatestBranch = useStore(latestBranchStore);
|
||||
const [activeProviders, setActiveProviders] = useState<ProviderInfo[]>([]);
|
||||
|
||||
// Function to check if we're on stable version
|
||||
@ -92,8 +94,14 @@ export function useSettings() {
|
||||
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
|
||||
const savedLatestBranch = Cookies.get('latestBranch');
|
||||
const savedLatestBranch = Cookies.get('isLatestBranch');
|
||||
let checkCommit = Cookies.get('commitHash');
|
||||
|
||||
if (checkCommit === undefined) {
|
||||
@ -105,7 +113,7 @@ export function useSettings() {
|
||||
checkIsStableVersion().then((isStable) => {
|
||||
const shouldUseLatest = !isStable;
|
||||
latestBranchStore.set(shouldUseLatest);
|
||||
Cookies.set('latestBranch', String(shouldUseLatest));
|
||||
Cookies.set('isLatestBranch', String(shouldUseLatest));
|
||||
Cookies.set('commitHash', String(commit.commit));
|
||||
});
|
||||
} else {
|
||||
@ -162,10 +170,14 @@ export function useSettings() {
|
||||
Cookies.set('isLocalModelsEnabled', String(enabled));
|
||||
}, []);
|
||||
|
||||
const setPromptId = useCallback((promptId: string) => {
|
||||
promptStore.set(promptId);
|
||||
Cookies.set('promptId', promptId);
|
||||
}, []);
|
||||
const enableLatestBranch = useCallback((enabled: boolean) => {
|
||||
latestBranchStore.set(enabled);
|
||||
logStore.logSystem(`Main branch updates ${enabled ? 'enabled' : 'disabled'}`);
|
||||
Cookies.set('latestBranch', String(enabled));
|
||||
Cookies.set('isLatestBranch', String(enabled));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
@ -178,7 +190,9 @@ export function useSettings() {
|
||||
enableEventLogs,
|
||||
isLocalModel,
|
||||
enableLocalModels,
|
||||
latestBranch,
|
||||
promptId,
|
||||
setPromptId,
|
||||
isLatestBranch,
|
||||
enableLatestBranch,
|
||||
};
|
||||
}
|
||||
|
@ -202,8 +202,9 @@ export class ActionRunner {
|
||||
}
|
||||
|
||||
const webcontainer = await this.#webcontainer;
|
||||
const relativePath = nodePath.relative(webcontainer.workdir, action.filePath);
|
||||
|
||||
let folder = nodePath.dirname(action.filePath);
|
||||
let folder = nodePath.dirname(relativePath);
|
||||
|
||||
// remove trailing slashes
|
||||
folder = folder.replace(/\/+$/g, '');
|
||||
@ -218,8 +219,8 @@ export class ActionRunner {
|
||||
}
|
||||
|
||||
try {
|
||||
await webcontainer.fs.writeFile(action.filePath, action.content);
|
||||
logger.debug(`File written ${action.filePath}`);
|
||||
await webcontainer.fs.writeFile(relativePath, action.content);
|
||||
logger.debug(`File written ${relativePath}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to write file\n\n', error);
|
||||
}
|
||||
|
@ -47,4 +47,6 @@ export const isEventLogsEnabled = atom(false);
|
||||
|
||||
export const isLocalModelsEnabled = atom(true);
|
||||
|
||||
export const promptStore = atom<string>('default');
|
||||
|
||||
export const latestBranchStore = atom(false);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { type ActionFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { createDataStream } from 'ai';
|
||||
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 SwitchableStream from '~/lib/.server/llm/switchable-stream';
|
||||
import type { IProviderSetting } from '~/types/model';
|
||||
@ -29,9 +29,10 @@ function parseCookies(cookieHeader: string): Record<string, string> {
|
||||
}
|
||||
|
||||
async function chatAction({ context, request }: ActionFunctionArgs) {
|
||||
const { messages, files } = await request.json<{
|
||||
const { messages, files, promptId } = await request.json<{
|
||||
messages: Messages;
|
||||
files: any;
|
||||
promptId?: string;
|
||||
}>();
|
||||
|
||||
const cookieHeader = request.headers.get('Cookie');
|
||||
@ -98,6 +99,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
||||
apiKeys,
|
||||
files,
|
||||
providerSettings,
|
||||
promptId,
|
||||
});
|
||||
|
||||
return stream.switchSource(result.toDataStream());
|
||||
@ -111,6 +113,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
||||
apiKeys,
|
||||
files,
|
||||
providerSettings,
|
||||
promptId,
|
||||
});
|
||||
|
||||
stream.switchSource(result.toDataStream());
|
||||
|
@ -4,6 +4,7 @@ import { ClientOnly } from 'remix-utils/client-only';
|
||||
import { BaseChat } from '~/components/chat/BaseChat';
|
||||
import { GitUrlImport } from '~/components/git/GitUrlImport.client';
|
||||
import { Header } from '~/components/header/Header';
|
||||
import BackgroundRays from '~/components/ui/BackgroundRays';
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
|
||||
@ -15,7 +16,8 @@ export async function loader(args: LoaderFunctionArgs) {
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<div className="flex flex-col h-full w-full bg-bolt-elements-background-depth-1">
|
||||
<BackgroundRays />
|
||||
<Header />
|
||||
<ClientOnly fallback={<BaseChat />}>{() => <GitUrlImport />}</ClientOnly>
|
||||
</div>
|
||||
|
1015
changelog.md
1015
changelog.md
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"scripts": {
|
||||
"deploy": "npm run build && wrangler pages deploy",
|
||||
"build": "remix vite:build",
|
||||
|
Loading…
Reference in New Issue
Block a user