Compare commits
19 Commits
revert-172
...
slate-rebr
| Author | SHA1 | Date | |
|---|---|---|---|
| 713a3db8ce | |||
| d9eea2adaf | |||
| 6fd45ff34c | |||
|
|
5d3fb1d7de | ||
|
|
c1474d85e5 | ||
|
|
3779000ec5 | ||
|
|
9c0e898d17 | ||
|
|
dee7fb0545 | ||
|
|
0e31267078 | ||
|
|
332edd3d98 | ||
|
|
7045646b1a | ||
|
|
eb4aa68581 | ||
|
|
73710da501 | ||
|
|
a87eae2119 | ||
|
|
ecf3cad189 | ||
|
|
be7a754a74 | ||
|
|
2a585ab5ea | ||
|
|
eac1933094 | ||
|
|
c36ab43dcc |
@@ -9,7 +9,7 @@ COPY package.json pnpm-lock.yaml ./
|
|||||||
#RUN npm install -g corepack@latest
|
#RUN npm install -g corepack@latest
|
||||||
|
|
||||||
#RUN corepack enable pnpm && pnpm install
|
#RUN corepack enable pnpm && pnpm install
|
||||||
RUN npm install -g pnpm && pnpm install
|
RUN npm install -g pnpm && pnpm install --prod=false
|
||||||
|
|
||||||
# Copy the rest of your app's source code
|
# Copy the rest of your app's source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ const UpdateTab = () => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Updates are fetched from: <span className="font-mono">stackblitz-labs/bolt.diy</span> (
|
Updates are fetched from: <span className="font-mono">stackblitz-labs/bolt.diy</span> (Slate upstream) (
|
||||||
{isLatestBranch ? 'main' : 'stable'} branch)
|
{isLatestBranch ? 'main' : 'stable'} branch)
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1">
|
<p className="mt-1">
|
||||||
@@ -553,7 +553,7 @@ const UpdateTab = () => {
|
|||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
<p className="text-sm text-bolt-elements-textSecondary mb-4">
|
||||||
A new version is available from <span className="font-mono">stackblitz-labs/bolt.diy</span> (
|
A new version is available from <span className="font-mono">stackblitz-labs/bolt.diy</span> (Slate upstream) (
|
||||||
{isLatestBranch ? 'main' : 'stable'} branch)
|
{isLatestBranch ? 'main' : 'stable'} branch)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
minHeight: TEXTAREA_MIN_HEIGHT,
|
minHeight: TEXTAREA_MIN_HEIGHT,
|
||||||
maxHeight: TEXTAREA_MAX_HEIGHT,
|
maxHeight: TEXTAREA_MAX_HEIGHT,
|
||||||
}}
|
}}
|
||||||
placeholder="How can Bolt help you today?"
|
placeholder="How can Slate help you today?"
|
||||||
translate="no"
|
translate="no"
|
||||||
/>
|
/>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const EXAMPLE_PROMPTS = [
|
const EXAMPLE_PROMPTS = [
|
||||||
{ text: 'Create a mobile app about bolt.diy' },
|
{ text: 'Create a mobile app idea generator' },
|
||||||
{ text: 'Build a todo app in React using Tailwind' },
|
{ text: 'Build a todo app in React using Tailwind' },
|
||||||
{ text: 'Build a simple blog using Astro' },
|
{ text: 'Build a simple blog using Astro' },
|
||||||
{ text: 'Create a cookie consent form using Material UI' },
|
{ text: 'Create a cookie consent form using Material UI' },
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { chatStore } from '~/lib/stores/chat';
|
|||||||
import { classNames } from '~/utils/classNames';
|
import { classNames } from '~/utils/classNames';
|
||||||
import { HeaderActionButtons } from './HeaderActionButtons.client';
|
import { HeaderActionButtons } from './HeaderActionButtons.client';
|
||||||
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
|
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
|
||||||
|
import { SlateWordmark } from './SlateWordmark';
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const chat = useStore(chatStore);
|
const chat = useStore(chatStore);
|
||||||
@@ -17,10 +18,8 @@ export function Header() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 z-logo text-bolt-elements-textPrimary cursor-pointer">
|
<div className="flex items-center gap-2 z-logo text-bolt-elements-textPrimary cursor-pointer">
|
||||||
<div className="i-ph:sidebar-simple-duotone text-xl" />
|
<div className="i-ph:sidebar-simple-duotone text-xl" />
|
||||||
<a href="/" className="text-2xl font-semibold text-accent flex items-center">
|
<a href="/" className="text-2xl font-semibold flex items-center text-bolt-elements-textPrimary">
|
||||||
{/* <span className="i-bolt:logo-text?mask w-[46px] inline-block" /> */}
|
<SlateWordmark className="h-8 w-auto" />
|
||||||
<img src="/logo-light-styled.png" alt="logo" className="w-[90px] inline-block dark:hidden" />
|
|
||||||
<img src="/logo-dark-styled.png" alt="logo" className="w-[90px] inline-block hidden dark:block" />
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
|
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
|
|||||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||||
className="px-4 hover:bg-bolt-elements-item-backgroundActive flex items-center gap-2"
|
className="px-4 hover:bg-bolt-elements-item-backgroundActive flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{isDeploying ? `Deploying to ${deployingTo}...` : 'Deploy'}
|
{isDeploying ? `Launching to ${deployingTo}...` : 'Launch'}
|
||||||
<div
|
<div
|
||||||
className={classNames('i-ph:caret-down w-4 h-4 transition-transform', isDropdownOpen ? 'rotate-180' : '')}
|
className={classNames('i-ph:caret-down w-4 h-4 transition-transform', isDropdownOpen ? 'rotate-180' : '')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
20
app/components/header/SlateWordmark.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export function SlateWordmark({ className = '' }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 132 28"
|
||||||
|
aria-label="Slate"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="slate-mark-grad" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stopColor="#FF1F8F" />
|
||||||
|
<stop offset="100%" stopColor="#00D4FF" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="0" y="2" width="24" height="24" rx="6" fill="url(#slate-mark-grad)" />
|
||||||
|
<path d="M7.5 18.2c0 1.6 1.4 2.6 3.4 2.6 1.9 0 3.3-.9 3.3-2.4 0-1.4-1-2-2.7-2.4l-1.3-.3c-2-.4-3.5-1.5-3.5-3.6 0-2.4 2-4.1 4.9-4.1 2.8 0 4.7 1.5 4.8 3.9h-2.2c-.1-1.3-1.1-2-2.6-2-1.5 0-2.5.7-2.5 1.9 0 1.1.9 1.7 2.5 2l1.3.3c2.3.5 3.7 1.6 3.7 3.8 0 2.6-2 4.3-5.2 4.3-3 0-5.1-1.5-5.2-4z" fill="#FFFFFF"/>
|
||||||
|
<text x="32" y="22" font-family="Inter, system-ui, -apple-system, Segoe UI, sans-serif" font-weight="700" font-size="22" fill="currentColor" letter-spacing="-0.5">Slate</text>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -542,20 +542,19 @@ const FileInfo = memo(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create and manage a single highlighter instance at the module level
|
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => {
|
||||||
let highlighterInstance: any = null;
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
let highlighterPromise: Promise<any> | null = null;
|
const [highlighter, setHighlighter] = useState<any>(null);
|
||||||
|
const theme = useStore(themeStore);
|
||||||
|
|
||||||
const getSharedHighlighter = async () => {
|
const toggleFullscreen = useCallback(() => {
|
||||||
if (highlighterInstance) {
|
setIsFullscreen((prev) => !prev);
|
||||||
return highlighterInstance;
|
}, []);
|
||||||
}
|
|
||||||
|
|
||||||
if (highlighterPromise) {
|
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
|
||||||
return highlighterPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
highlighterPromise = getHighlighter({
|
useEffect(() => {
|
||||||
|
getHighlighter({
|
||||||
themes: ['github-dark', 'github-light'],
|
themes: ['github-dark', 'github-light'],
|
||||||
langs: [
|
langs: [
|
||||||
'typescript',
|
'typescript',
|
||||||
@@ -576,55 +575,13 @@ const getSharedHighlighter = async () => {
|
|||||||
'rust',
|
'rust',
|
||||||
'plaintext',
|
'plaintext',
|
||||||
],
|
],
|
||||||
});
|
}).then(setHighlighter);
|
||||||
|
|
||||||
highlighterInstance = await highlighterPromise;
|
|
||||||
highlighterPromise = null;
|
|
||||||
|
|
||||||
// Clear the promise once resolved
|
|
||||||
return highlighterInstance;
|
|
||||||
};
|
|
||||||
|
|
||||||
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => {
|
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
||||||
|
|
||||||
// Use state to hold the shared highlighter instance
|
|
||||||
const [highlighter, setHighlighter] = useState<any>(null);
|
|
||||||
const theme = useStore(themeStore);
|
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(() => {
|
|
||||||
setIsFullscreen((prev) => !prev);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Fetch the shared highlighter instance
|
|
||||||
getSharedHighlighter().then(setHighlighter);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* No cleanup needed here for the highlighter instance itself,
|
|
||||||
* as it's managed globally. Shiki instances don't typically
|
|
||||||
* need disposal unless you are dynamically loading/unloading themes/languages.
|
|
||||||
* If you were dynamically loading, you might need a more complex
|
|
||||||
* shared instance manager with reference counting or similar.
|
|
||||||
* For static themes/langs, a single instance is sufficient.
|
|
||||||
*/
|
|
||||||
}, []); // Empty dependency array ensures this runs only once on mount
|
|
||||||
|
|
||||||
if (isBinary || error) {
|
if (isBinary || error) {
|
||||||
return renderContentWarning(isBinary ? 'binary' : 'error');
|
return renderContentWarning(isBinary ? 'binary' : 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render a loading state or null while highlighter is not ready
|
|
||||||
if (!highlighter) {
|
|
||||||
return (
|
|
||||||
<div className="h-full flex items-center justify-center">
|
|
||||||
<div className="text-bolt-elements-textTertiary">Loading diff...</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullscreenOverlay isFullscreen={isFullscreen}>
|
<FullscreenOverlay isFullscreen={isFullscreen}>
|
||||||
<div className="w-full h-full flex flex-col">
|
<div className="w-full h-full flex flex-col">
|
||||||
@@ -645,7 +602,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
|
|||||||
lineNumber={block.lineNumber}
|
lineNumber={block.lineNumber}
|
||||||
content={block.content}
|
content={block.content}
|
||||||
type={block.type}
|
type={block.type}
|
||||||
highlighter={highlighter} // Pass the shared instance
|
highlighter={highlighter}
|
||||||
language={language}
|
language={language}
|
||||||
block={block}
|
block={block}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|||||||
@@ -108,9 +108,6 @@ export async function streamText(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
|
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
|
||||||
logger.info(
|
|
||||||
`Max tokens for model ${modelDetails.name} is ${dynamicMaxTokens} based on ${modelDetails.maxTokenAllowed} or ${MAX_TOKENS}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let systemPrompt =
|
let systemPrompt =
|
||||||
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
|
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ The year is 2025.
|
|||||||
</technology_preferences>
|
</technology_preferences>
|
||||||
|
|
||||||
<running_shell_commands_info>
|
<running_shell_commands_info>
|
||||||
|
With each user request, you are provided with information about the shell command that is currently running.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<bolt_running_commands>
|
||||||
|
npm run dev
|
||||||
|
</bolt_running_commands>
|
||||||
|
|
||||||
CRITICAL:
|
CRITICAL:
|
||||||
- NEVER mention or reference the XML tags or structure of this process list in your responses
|
- NEVER mention or reference the XML tags or structure of this process list in your responses
|
||||||
- DO NOT repeat or directly quote any part of the command information provided
|
- DO NOT repeat or directly quote any part of the command information provided
|
||||||
@@ -278,7 +286,6 @@ The year is 2025.
|
|||||||
|
|
||||||
- Follow the guidelines for shell commands.
|
- Follow the guidelines for shell commands.
|
||||||
- Use the start action type over the shell type ONLY when the command is intended to start the project.
|
- Use the start action type over the shell type ONLY when the command is intended to start the project.
|
||||||
- IMPORTANT: Always execute the start command after executing a shell command.
|
|
||||||
|
|
||||||
- file: For creating new files or updating existing files. Add \`filePath\` and \`contentType\` attributes:
|
- file: For creating new files or updating existing files. Add \`filePath\` and \`contentType\` attributes:
|
||||||
|
|
||||||
@@ -310,7 +317,7 @@ The year is 2025.
|
|||||||
11. Prioritize installing required dependencies by updating \`package.json\` first.
|
11. Prioritize installing required dependencies by updating \`package.json\` first.
|
||||||
|
|
||||||
- If a \`package.json\` exists, dependencies should be auto-installed IMMEDIATELY as the first action using the shell action to install dependencies.
|
- If a \`package.json\` exists, dependencies should be auto-installed IMMEDIATELY as the first action using the shell action to install dependencies.
|
||||||
- If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed this should ALWAYS be done inside the artifact.
|
- If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed.
|
||||||
- \`npm install\` will not automatically run every time \`package.json\` is updated, so you need to include a shell action to install dependencies.
|
- \`npm install\` will not automatically run every time \`package.json\` is updated, so you need to include a shell action to install dependencies.
|
||||||
- Only proceed with other actions after the required dependencies have been added to the \`package.json\`.
|
- Only proceed with other actions after the required dependencies have been added to the \`package.json\`.
|
||||||
|
|
||||||
@@ -318,7 +325,7 @@ The year is 2025.
|
|||||||
|
|
||||||
12. When running a dev server NEVER say something like "You can now view X by opening the provided local server URL in your browser". The preview will be opened automatically or by the user manually!
|
12. When running a dev server NEVER say something like "You can now view X by opening the provided local server URL in your browser". The preview will be opened automatically or by the user manually!
|
||||||
|
|
||||||
13. Always include a start command in the artifact, The start command should be the LAST action in the artifact.
|
13. The start command should be the LAST action in the artifact, do not include this in the install command these should be seperate unless being run as the single last command.
|
||||||
</artifact_instructions>
|
</artifact_instructions>
|
||||||
|
|
||||||
<design_instructions>
|
<design_instructions>
|
||||||
@@ -640,8 +647,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default App;</boltAction>
|
export default App;</boltAction>
|
||||||
|
<boltAction type="file" filePath="src/App.css" contentType="content">.container {
|
||||||
<boltAction type="file" filePath="src/App.css" contentType="content"> {
|
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -692,6 +698,25 @@ export default App;</boltAction>
|
|||||||
npm run dev
|
npm run dev
|
||||||
</boltAction>
|
</boltAction>
|
||||||
</boltArtifact>
|
</boltArtifact>
|
||||||
|
|
||||||
|
I've created a demonstration of three different ways to center a div:
|
||||||
|
|
||||||
|
1. **Using Flexbox** - This is the most recommended modern approach:
|
||||||
|
- Set the parent container to \`display: flex\`
|
||||||
|
- Use \`justify-content: center\` for horizontal centering
|
||||||
|
- Use \`align-items: center\` for vertical centering
|
||||||
|
|
||||||
|
2. **Using CSS Grid** - Even simpler than flexbox in some cases:
|
||||||
|
- Set the parent container to \`display: grid\`
|
||||||
|
- Use \`place-items: center\` to center in both directions at once
|
||||||
|
|
||||||
|
3. **Using Position Absolute** - The traditional method:
|
||||||
|
- Set the parent to \`position: relative\`
|
||||||
|
- Set the child to \`position: absolute\`
|
||||||
|
- Use \`top: 50%; left: 50%\` to position at the center
|
||||||
|
- Use \`transform: translate(-50%, -50%)\` to adjust for the element's size
|
||||||
|
|
||||||
|
The flexbox method is generally the most versatile and recommended approach for most centering needs in modern web development.</assistant_response>
|
||||||
</example>
|
</example>
|
||||||
</examples>`;
|
</examples>`;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
name: 'claude-3-7-sonnet-20250219',
|
name: 'claude-3-7-sonnet-20250219',
|
||||||
label: 'Claude 3.7 Sonnet',
|
label: 'Claude 3.7 Sonnet',
|
||||||
provider: 'Anthropic',
|
provider: 'Anthropic',
|
||||||
maxTokenAllowed: 128000,
|
maxTokenAllowed: 8000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'claude-3-5-sonnet-latest',
|
name: 'claude-3-5-sonnet-latest',
|
||||||
@@ -95,7 +95,6 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
});
|
});
|
||||||
const anthropic = createAnthropic({
|
const anthropic = createAnthropic({
|
||||||
apiKey,
|
apiKey,
|
||||||
headers: { 'anthropic-beta': 'output-128k-2025-02-19' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return anthropic(model);
|
return anthropic(model);
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ ${value.content}
|
|||||||
|
|
||||||
const takeSnapshot = useCallback(
|
const takeSnapshot = useCallback(
|
||||||
async (chatIdx: string, files: FileMap, _chatId?: string | undefined, chatSummary?: string) => {
|
async (chatIdx: string, files: FileMap, _chatId?: string | undefined, chatSummary?: string) => {
|
||||||
const id = chatId.get();
|
const id = _chatId || chatId.get();
|
||||||
|
|
||||||
if (!id || !db) {
|
if (!id || !db) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -692,9 +692,27 @@ export class FilesStore {
|
|||||||
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
|
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
|
||||||
const watchEvents = events.flat(2);
|
const watchEvents = events.flat(2);
|
||||||
|
|
||||||
for (const { type, path, buffer } of watchEvents) {
|
for (const { type, path: eventPath, buffer } of watchEvents) {
|
||||||
// remove any trailing slashes
|
// remove any trailing slashes
|
||||||
const sanitizedPath = path.replace(/\/+$/g, '');
|
const sanitizedPath = eventPath.replace(/\/+$/g, '');
|
||||||
|
|
||||||
|
// Skip processing if this file/folder was explicitly deleted
|
||||||
|
if (this.#deletedPaths.has(sanitizedPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isInDeletedFolder = false;
|
||||||
|
|
||||||
|
for (const deletedPath of this.#deletedPaths) {
|
||||||
|
if (sanitizedPath.startsWith(deletedPath + '/')) {
|
||||||
|
isInDeletedFolder = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInDeletedFolder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'add_dir': {
|
case 'add_dir': {
|
||||||
@@ -720,21 +738,38 @@ export class FilesStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* @note This check is purely for the editor. The way we detect this is not
|
|
||||||
* bullet-proof and it's a best guess so there might be false-positives.
|
|
||||||
* The reason we do this is because we don't want to display binary files
|
|
||||||
* in the editor nor allow to edit them.
|
|
||||||
*/
|
|
||||||
const isBinary = isBinaryFile(buffer);
|
const isBinary = isBinaryFile(buffer);
|
||||||
|
|
||||||
if (!isBinary) {
|
if (isBinary && buffer) {
|
||||||
|
// For binary files, we need to preserve the content as base64
|
||||||
|
content = Buffer.from(buffer).toString('base64');
|
||||||
|
} else if (!isBinary) {
|
||||||
content = this.#decodeFileContent(buffer);
|
content = this.#decodeFileContent(buffer);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the content is a single space and this is from our empty file workaround,
|
||||||
|
* convert it back to an actual empty string
|
||||||
|
*/
|
||||||
|
if (content === ' ' && type === 'add_file') {
|
||||||
|
content = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.files.setKey(sanitizedPath, { type: 'file', content, isBinary });
|
const existingFile = this.files.get()[sanitizedPath];
|
||||||
|
|
||||||
|
if (existingFile?.type === 'file' && existingFile.isBinary && existingFile.content && !content) {
|
||||||
|
content = existingFile.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve lock state if the file already exists
|
||||||
|
const isLocked = existingFile?.type === 'file' ? existingFile.isLocked : false;
|
||||||
|
|
||||||
|
this.files.setKey(sanitizedPath, {
|
||||||
|
type: 'file',
|
||||||
|
content,
|
||||||
|
isBinary,
|
||||||
|
isLocked,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove_file': {
|
case 'remove_file': {
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import { Header } from '~/components/header/Header';
|
|||||||
import BackgroundRays from '~/components/ui/BackgroundRays';
|
import BackgroundRays from '~/components/ui/BackgroundRays';
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
|
return [{ title: 'Slate — AI App Builder' }, { name: 'description', content: 'Slate — build and launch apps with AI.' }];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loader = () => json({});
|
export const loader = () => json({});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Landing page component for Bolt
|
* Landing page component for Slate
|
||||||
* Note: Settings functionality should ONLY be accessed through the sidebar menu.
|
* Note: Settings functionality should ONLY be accessed through the sidebar menu.
|
||||||
* Do not add settings button/panel to this landing page as it was intentionally removed
|
* Do not add settings button/panel to this landing page as it was intentionally removed
|
||||||
* to keep the UI clean and consistent with the design system.
|
* to keep the UI clean and consistent with the design system.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { type ActionFunctionArgs, json } from '@remix-run/cloudflare';
|
import { type ActionFunctionArgs, json } from '@remix-run/cloudflare';
|
||||||
import crypto from 'crypto';
|
|
||||||
import type { NetlifySiteInfo } from '~/types/netlify';
|
import type { NetlifySiteInfo } from '~/types/netlify';
|
||||||
|
|
||||||
interface DeployRequestBody {
|
interface DeployRequestBody {
|
||||||
@@ -8,6 +7,15 @@ interface DeployRequestBody {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sha1(message: string) {
|
||||||
|
const msgBuffer = new TextEncoder().encode(message);
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
export async function action({ request }: ActionFunctionArgs) {
|
export async function action({ request }: ActionFunctionArgs) {
|
||||||
try {
|
try {
|
||||||
const { siteId, files, token, chatId } = (await request.json()) as DeployRequestBody & { token: string };
|
const { siteId, files, token, chatId } = (await request.json()) as DeployRequestBody & { token: string };
|
||||||
@@ -104,7 +112,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
for (const [filePath, content] of Object.entries(files)) {
|
for (const [filePath, content] of Object.entries(files)) {
|
||||||
// Ensure file path starts with a forward slash
|
// Ensure file path starts with a forward slash
|
||||||
const normalizedPath = filePath.startsWith('/') ? filePath : '/' + filePath;
|
const normalizedPath = filePath.startsWith('/') ? filePath : '/' + filePath;
|
||||||
const hash = crypto.createHash('sha1').update(content).digest('hex');
|
const hash = await sha1(content);
|
||||||
fileDigests[normalizedPath] = hash;
|
fileDigests[normalizedPath] = hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Header } from '~/components/header/Header';
|
|||||||
import BackgroundRays from '~/components/ui/BackgroundRays';
|
import BackgroundRays from '~/components/ui/BackgroundRays';
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
|
return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Slate, an AI assistant from StackBlitz' }];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function loader(args: LoaderFunctionArgs) {
|
export async function loader(args: LoaderFunctionArgs) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 51 21.9"><path d="M24.1 19.3c-4.7 0-7-2.7-7-6.1s3.2-7.7 7.9-7.7 7 2.7 7 6.1-3.2 7.7-7.9 7.7Zm.2-4.3c1.6 0 2.7-1.5 2.7-3.1s-.8-2-2.2-2-2.7 1.5-2.7 3.1.8 2 2.2 2ZM37 19h-4.9l4-18.2H41l-4 18.1Z"/><path d="M9.6 19.3c-1.5 0-3-.5-3.8-1.7L5.5 19 0 21.9.6 19 4.6.8h4.9L8.1 7.2c1.1-1.2 2.2-1.7 3.6-1.7 3 0 4.9 1.9 4.9 5.5s-2.3 8.3-7 8.3Zm1.9-7.3c0 1.7-1.2 3-2.8 3s-1.7-.3-2.2-.9l.8-3.3c.6-.6 1.2-.9 2-.9 1.2 0 2.2.9 2.2 2.2Z" style="fill-rule:evenodd"/><path d="M46.1 19.3c-2.8 0-4.9-1-4.9-3.3s0-.7.1-1l1.1-4.9h-2.2l1-4.2h2.2l.8-3.6L49.7 0l-.6 2.3-.8 3.6H51l-1 4.2h-2.7l-.7 3.2v.6c0 .6.4 1.1 1.2 1.1s.6 0 .7-.1v3.9c-.5.4-1.4.5-2.3.5Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 22"><text x="0" y="18" font-family="Inter, system-ui, -apple-system, Segoe UI, sans-serif" font-weight="700" font-size="20" fill="currentColor" letter-spacing="-0.5">Slate</text></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 241 B |
@@ -28,7 +28,7 @@
|
|||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"typegen": "wrangler types",
|
"typegen": "wrangler types",
|
||||||
"preview": "pnpm run build && pnpm run start",
|
"preview": "pnpm run build && pnpm run start",
|
||||||
"prepare": "husky",
|
"prepare": "husky || true",
|
||||||
"clean": "node scripts/clean.js",
|
"clean": "node scripts/clean.js",
|
||||||
"electron:build:deps": "concurrently \"pnpm electron:build:main\" \"pnpm electron:build:preload\" --kill-others-on-fail",
|
"electron:build:deps": "concurrently \"pnpm electron:build:main\" \"pnpm electron:build:preload\" --kill-others-on-fail",
|
||||||
"electron:build:main": "vite build --config ./electron/main/vite.config.ts",
|
"electron:build:main": "vite build --config ./electron/main/vite.config.ts",
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
<rect width="16" height="16" rx="2" fill="#8A5FFF" />
|
<defs>
|
||||||
<path d="M7.398 9.091h-3.58L10.364 2 8.602 6.909h3.58L5.636 14l1.762-4.909Z" fill="#fff" />
|
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#FF1F8F"/>
|
||||||
|
<stop offset="100%" stop-color="#00D4FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="32" height="32" rx="7" fill="url(#g)"/>
|
||||||
|
<path d="M9.5 21.5c0 2.1 1.9 3.5 4.7 3.5 2.6 0 4.5-1.3 4.5-3.3 0-2-1.4-2.8-3.7-3.3l-1.7-.4c-2.7-.6-4.7-2-4.7-4.9 0-3.2 2.7-5.4 6.6-5.4 3.8 0 6.4 2 6.5 5.2h-3c-.1-1.7-1.5-2.7-3.5-2.7-2 0-3.4 1-3.4 2.5 0 1.5 1.2 2.3 3.4 2.7l1.7.4c3.1.7 5 2.2 5 5.1 0 3.6-2.7 5.8-7 5.8-4.1 0-6.9-2-7-5.4z" fill="#FFFFFF"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 846 B |
@@ -1,15 +1,11 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="83" fill="none"><g filter="url(#a)"><path fill="url(#b)" d="M66.657 0H28.343a7.948 7.948 0 0 0-6.887 3.979L2.288 37.235a7.948 7.948 0 0 0 0 7.938L21.456 78.43a7.948 7.948 0 0 0 6.887 3.979h38.314a7.948 7.948 0 0 0 6.886-3.98l19.17-33.256a7.948 7.948 0 0 0 0-7.938L73.542 3.98A7.948 7.948 0 0 0 66.657 0Z"/></g><g filter="url(#c)"><path fill="#fff" fill-rule="evenodd" d="M50.642 59.608c-3.468 0-6.873-1.261-8.827-3.973l-.69 3.198-12.729 6.762 1.374-6.762 9.27-42.04h11.35l-3.279 14.818c2.649-2.9 5.108-3.973 8.26-3.973 6.81 0 11.35 4.477 11.35 12.675 0 8.45-5.233 19.295-16.079 19.295Zm4.351-16.9c0 3.91-2.774 6.874-6.368 6.874-2.018 0-3.847-.757-5.045-2.08l1.766-7.757c1.324-1.324 2.837-2.08 4.603-2.08 2.711 0 5.044 2.017 5.044 5.044Z" clip-rule="evenodd"/></g><defs><filter id="a" width="92.549" height="82.409" x="1.226" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset/><feGaussianBlur stdDeviation="1.717"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.65 0"/><feBlend in2="shape" result="effect1_innerShadow_615_13380"/></filter><filter id="c" width="38.326" height="48.802" x="28.396" y="16.793" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset/><feGaussianBlur stdDeviation=".072"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.95 0"/><feBlend in2="shape" result="effect1_innerShadow_615_13380"/></filter>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 132 32" width="132" height="32">
|
||||||
<linearGradient id="b" x1="47.5" x2="47.5" y1="0" y2="82.409" gradientUnits="userSpaceOnUse">
|
<defs>
|
||||||
<stop offset="0%" stop-color="#F8F5FF" />
|
<linearGradient id="slate-grad" x1="0" y1="0" x2="1" y2="1">
|
||||||
<stop offset="10%" stop-color="#F0EBFF" />
|
<stop offset="0%" stop-color="#FF1F8F"/>
|
||||||
<stop offset="20%" stop-color="#E1D6FF" />
|
<stop offset="100%" stop-color="#00D4FF"/>
|
||||||
<stop offset="30%" stop-color="#CEBEFF" />
|
</linearGradient>
|
||||||
<stop offset="40%" stop-color="#B69EFF" />
|
</defs>
|
||||||
<stop offset="50%" stop-color="#9C7DFF" />
|
<rect x="0" y="4" width="24" height="24" rx="6" fill="url(#slate-grad)"/>
|
||||||
<stop offset="60%" stop-color="#8A5FFF" />
|
<path d="M7.5 21.2c0 1.6 1.4 2.6 3.4 2.6 1.9 0 3.3-.9 3.3-2.4 0-1.4-1-2-2.7-2.4l-1.3-.3c-2-.4-3.5-1.5-3.5-3.6 0-2.4 2-4.1 4.9-4.1 2.8 0 4.7 1.5 4.8 3.9h-2.2c-.1-1.3-1.1-2-2.6-2-1.5 0-2.5.7-2.5 1.9 0 1.1.9 1.7 2.5 2l1.3.3c2.3.5 3.7 1.6 3.7 3.8 0 2.6-2 4.3-5.2 4.3-3 0-5.1-1.5-5.2-4z" fill="#FFFFFF"/>
|
||||||
<stop offset="70%" stop-color="#7645E8" />
|
<text x="32" y="24" font-family="Inter, system-ui, -apple-system, Segoe UI, sans-serif" font-weight="700" font-size="22" fill="#FFFFFF" letter-spacing="-0.5">Slate</text>
|
||||||
<stop offset="80%" stop-color="#6234BB" />
|
</svg>
|
||||||
<stop offset="90%" stop-color="#502D93" />
|
|
||||||
<stop offset="100%" stop-color="#2D1959" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs></svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 846 B |
@@ -35,17 +35,29 @@ const BASE_COLORS = {
|
|||||||
950: '#0A0A0A',
|
950: '#0A0A0A',
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
50: '#F8F5FF',
|
50: '#FFF1F8',
|
||||||
100: '#F0EBFF',
|
100: '#FFD7EC',
|
||||||
200: '#E1D6FF',
|
200: '#FFB1D9',
|
||||||
300: '#CEBEFF',
|
300: '#FF85C2',
|
||||||
400: '#B69EFF',
|
400: '#FF52A8',
|
||||||
500: '#9C7DFF',
|
500: '#FF1F8F',
|
||||||
600: '#8A5FFF',
|
600: '#E60077',
|
||||||
700: '#7645E8',
|
700: '#B80060',
|
||||||
800: '#6234BB',
|
800: '#8A004A',
|
||||||
900: '#502D93',
|
900: '#5C0033',
|
||||||
950: '#2D1959',
|
950: '#33001D',
|
||||||
|
},
|
||||||
|
cyan2: {
|
||||||
|
50: '#E6FBFF',
|
||||||
|
100: '#BFF4FF',
|
||||||
|
200: '#80E9FF',
|
||||||
|
300: '#40DCFF',
|
||||||
|
400: '#1FD7FF',
|
||||||
|
500: '#00D4FF',
|
||||||
|
600: '#00A6CC',
|
||||||
|
700: '#007F99',
|
||||||
|
800: '#005566',
|
||||||
|
900: '#002B33',
|
||||||
},
|
},
|
||||||
green: {
|
green: {
|
||||||
50: '#F0FDF4',
|
50: '#F0FDF4',
|
||||||
|
|||||||
@@ -93,6 +93,26 @@ export default defineConfig((config) => {
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
define: {
|
||||||
|
global: 'globalThis',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
buffer: 'vite-plugin-node-polyfills/polyfills/buffer',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
nodePolyfills({
|
nodePolyfills({
|
||||||
|
|||||||