Skip to content

Commit 3373a32

Browse files
authored
Merge branch 'stackblitz-labs:main' into main
2 parents 68feae0 + b7ef224 commit 3373a32

26 files changed

+1417
-376
lines changed

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
# Get your API key from: https://console.anthropic.com/
1313
ANTHROPIC_API_KEY=your_anthropic_api_key_here
1414

15+
# Cerebras (High-performance inference)
16+
# Get your API key from: https://cloud.cerebras.ai/settings
17+
CEREBRAS_API_KEY=your_cerebras_api_key_here
18+
19+
# Fireworks AI (Fast inference with FireAttention engine)
20+
# Get your API key from: https://fireworks.ai/api-keys
21+
FIREWORKS_API_KEY=your_fireworks_api_key_here
22+
1523
# OpenAI GPT models
1624
# Get your API key from: https://platform.openai.com/api-keys
1725
OPENAI_API_KEY=your_openai_api_key_here
@@ -59,6 +67,10 @@ XAI_API_KEY=your_xai_api_key_here
5967
# Get your API key from: https://platform.moonshot.ai/console/api-keys
6068
MOONSHOT_API_KEY=your_moonshot_api_key_here
6169

70+
# Z.AI (GLM models with JWT authentication)
71+
# Get your API key from: https://open.bigmodel.cn/usercenter/apikeys
72+
ZAI_API_KEY=your_zai_api_key_here
73+
6274
# Hugging Face
6375
# Get your API key from: https://huggingface.co/settings/tokens
6476
HuggingFace_API_KEY=your_huggingface_api_key_here

app/components/chat/ModelSelector.tsx

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
33
import type { KeyboardEvent } from 'react';
44
import type { ModelInfo } from '~/lib/modules/llm/types';
55
import { classNames } from '~/utils/classNames';
6+
import { LOCAL_PROVIDERS } from '~/lib/stores/settings';
67

78
// Fuzzy search utilities
89
const levenshteinDistance = (str1: string, str2: string): number => {
@@ -130,6 +131,32 @@ export const ModelSelector = ({
130131
const providerDropdownRef = useRef<HTMLDivElement>(null);
131132
const [showFreeModelsOnly, setShowFreeModelsOnly] = useState(false);
132133

134+
type ConnectionStatus = 'unknown' | 'connected' | 'disconnected';
135+
136+
const [localProviderStatus, setLocalProviderStatus] = useState<Record<string, ConnectionStatus>>({});
137+
138+
// Check connectivity of local providers when provider list changes
139+
useEffect(() => {
140+
const checkLocalProviders = async () => {
141+
const statuses: Record<string, 'connected' | 'disconnected'> = {};
142+
143+
for (const p of providerList) {
144+
if (!LOCAL_PROVIDERS.includes(p.name)) {
145+
continue;
146+
}
147+
148+
// If the provider has models loaded, it's connected
149+
const hasModels = modelList.some((m) => m.provider === p.name);
150+
151+
statuses[p.name] = hasModels ? 'connected' : 'disconnected';
152+
}
153+
154+
setLocalProviderStatus(statuses);
155+
};
156+
157+
checkLocalProviders();
158+
}, [providerList, modelList]);
159+
133160
// Debounce search queries
134161
useEffect(() => {
135162
const timer = setTimeout(() => {
@@ -440,7 +467,28 @@ export const ModelSelector = ({
440467
tabIndex={0}
441468
>
442469
<div className="flex items-center justify-between">
443-
<div className="truncate">{provider?.name || 'Select provider'}</div>
470+
<div className="flex items-center gap-2 truncate">
471+
{provider?.name && LOCAL_PROVIDERS.includes(provider.name) && (
472+
<span
473+
className={classNames(
474+
'inline-block w-2 h-2 rounded-full flex-shrink-0',
475+
localProviderStatus[provider.name] === 'connected'
476+
? 'bg-green-500'
477+
: localProviderStatus[provider.name] === 'disconnected'
478+
? 'bg-red-400'
479+
: 'bg-bolt-elements-textTertiary',
480+
)}
481+
title={
482+
localProviderStatus[provider.name] === 'connected'
483+
? `${provider.name} is running`
484+
: localProviderStatus[provider.name] === 'disconnected'
485+
? `${provider.name} is not reachable`
486+
: 'Checking...'
487+
}
488+
/>
489+
)}
490+
{provider?.name || 'Select provider'}
491+
</div>
444492
<div
445493
className={classNames(
446494
'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75',
@@ -559,11 +607,25 @@ export const ModelSelector = ({
559607
}}
560608
tabIndex={focusedProviderIndex === index ? 0 : -1}
561609
>
562-
<div
563-
dangerouslySetInnerHTML={{
564-
__html: (providerOption as any).highlightedName || providerOption.name,
565-
}}
566-
/>
610+
<div className="flex items-center gap-2">
611+
{LOCAL_PROVIDERS.includes(providerOption.name) && (
612+
<span
613+
className={classNames(
614+
'inline-block w-2 h-2 rounded-full flex-shrink-0',
615+
localProviderStatus[providerOption.name] === 'connected'
616+
? 'bg-green-500'
617+
: localProviderStatus[providerOption.name] === 'disconnected'
618+
? 'bg-red-400'
619+
: 'bg-bolt-elements-textTertiary',
620+
)}
621+
/>
622+
)}
623+
<span
624+
dangerouslySetInnerHTML={{
625+
__html: (providerOption as any).highlightedName || providerOption.name,
626+
}}
627+
/>
628+
</div>
567629
</div>
568630
))
569631
)}
@@ -717,8 +779,17 @@ export const ModelSelector = ({
717779
? `No models match "${debouncedModelSearchQuery}"${showFreeModelsOnly ? ' (free only)' : ''}`
718780
: showFreeModelsOnly
719781
? 'No free models available'
720-
: 'No models available'}
782+
: provider?.name && LOCAL_PROVIDERS.includes(provider.name)
783+
? `No models found — is ${provider.name} running?`
784+
: 'No models available'}
721785
</div>
786+
{!debouncedModelSearchQuery && provider?.name && LOCAL_PROVIDERS.includes(provider.name) && (
787+
<div className="text-xs text-bolt-elements-textTertiary mt-1">
788+
Make sure {provider.name} is running and has at least one model loaded.
789+
{provider.name === 'Ollama' && ' Try: ollama pull llama3.2'}
790+
{provider.name === 'LMStudio' && ' Load a model in LM Studio first.'}
791+
</div>
792+
)}
722793
{debouncedModelSearchQuery && (
723794
<div className="text-xs text-bolt-elements-textTertiary">
724795
Try searching for model names, context sizes (e.g., "128k", "1M"), or capabilities

app/components/deploy/GitHubDeploy.client.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useState } from 'react';
77
import type { ActionCallbackData } from '~/lib/runtime/message-parser';
88
import { chatId } from '~/lib/persistence/useChatHistory';
99
import { getLocalStorage } from '~/lib/persistence/localStorage';
10+
import { formatBuildFailureOutput } from './deployUtils';
1011

1112
export function useGitHubDeploy() {
1213
const [isDeploying, setIsDeploying] = useState(false);
@@ -65,10 +66,12 @@ export function useGitHubDeploy() {
6566
// Then run it
6667
await artifact.runner.runAction(actionData);
6768

68-
if (!artifact.runner.buildOutput) {
69+
const buildOutput = artifact.runner.buildOutput;
70+
71+
if (!buildOutput || buildOutput.exitCode !== 0) {
6972
// Notify that build failed
7073
deployArtifact.runner.handleDeployAction('building', 'failed', {
71-
error: 'Build failed. Check the terminal for details.',
74+
error: formatBuildFailureOutput(buildOutput?.output),
7275
source: 'github',
7376
});
7477
throw new Error('Build failed');

app/components/deploy/GitLabDeploy.client.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useState } from 'react';
77
import type { ActionCallbackData } from '~/lib/runtime/message-parser';
88
import { chatId } from '~/lib/persistence/useChatHistory';
99
import { getLocalStorage } from '~/lib/persistence/localStorage';
10+
import { formatBuildFailureOutput } from './deployUtils';
1011

1112
export function useGitLabDeploy() {
1213
const [isDeploying, setIsDeploying] = useState(false);
@@ -65,10 +66,12 @@ export function useGitLabDeploy() {
6566
// Then run it
6667
await artifact.runner.runAction(actionData);
6768

68-
if (!artifact.runner.buildOutput) {
69+
const buildOutput = artifact.runner.buildOutput;
70+
71+
if (!buildOutput || buildOutput.exitCode !== 0) {
6972
// Notify that build failed
7073
deployArtifact.runner.handleDeployAction('building', 'failed', {
71-
error: 'Build failed. Check the terminal for details.',
74+
error: formatBuildFailureOutput(buildOutput?.output),
7275
source: 'gitlab',
7376
});
7477
throw new Error('Build failed');

app/components/deploy/NetlifyDeploy.client.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { path } from '~/utils/path';
77
import { useState } from 'react';
88
import type { ActionCallbackData } from '~/lib/runtime/message-parser';
99
import { chatId } from '~/lib/persistence/useChatHistory';
10+
import { formatBuildFailureOutput } from './deployUtils';
1011

1112
export function useNetlifyDeploy() {
1213
const [isDeploying, setIsDeploying] = useState(false);
@@ -65,10 +66,12 @@ export function useNetlifyDeploy() {
6566
// Then run it
6667
await artifact.runner.runAction(actionData);
6768

68-
if (!artifact.runner.buildOutput) {
69+
const buildOutput = artifact.runner.buildOutput;
70+
71+
if (!buildOutput || buildOutput.exitCode !== 0) {
6972
// Notify that build failed
7073
deployArtifact.runner.handleDeployAction('building', 'failed', {
71-
error: 'Build failed. Check the terminal for details.',
74+
error: formatBuildFailureOutput(buildOutput?.output),
7275
source: 'netlify',
7376
});
7477
throw new Error('Build failed');
@@ -81,7 +84,7 @@ export function useNetlifyDeploy() {
8184
const container = await webcontainer;
8285

8386
// Remove /home/project from buildPath if it exists
84-
const buildPath = artifact.runner.buildOutput.path.replace('/home/project', '');
87+
const buildPath = buildOutput.path.replace('/home/project', '');
8588

8689
console.log('Original buildPath', buildPath);
8790

app/components/deploy/VercelDeploy.client.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { path } from '~/utils/path';
77
import { useState } from 'react';
88
import type { ActionCallbackData } from '~/lib/runtime/message-parser';
99
import { chatId } from '~/lib/persistence/useChatHistory';
10+
import { formatBuildFailureOutput } from './deployUtils';
1011

1112
export function useVercelDeploy() {
1213
const [isDeploying, setIsDeploying] = useState(false);
@@ -64,10 +65,12 @@ export function useVercelDeploy() {
6465
// Then run it
6566
await artifact.runner.runAction(actionData);
6667

67-
if (!artifact.runner.buildOutput) {
68+
const buildOutput = artifact.runner.buildOutput;
69+
70+
if (!buildOutput || buildOutput.exitCode !== 0) {
6871
// Notify that build failed
6972
deployArtifact.runner.handleDeployAction('building', 'failed', {
70-
error: 'Build failed. Check the terminal for details.',
73+
error: formatBuildFailureOutput(buildOutput?.output),
7174
source: 'vercel',
7275
});
7376
throw new Error('Build failed');
@@ -80,7 +83,7 @@ export function useVercelDeploy() {
8083
const container = await webcontainer;
8184

8285
// Remove /home/project from buildPath if it exists
83-
const buildPath = artifact.runner.buildOutput.path.replace('/home/project', '');
86+
const buildPath = buildOutput.path.replace('/home/project', '');
8487

8588
// Check if the build path exists
8689
let finalBuildPath = buildPath;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const MAX_BUILD_OUTPUT_CHARS = 4000;
2+
3+
export function formatBuildFailureOutput(output?: string) {
4+
const trimmed = output?.trim();
5+
6+
if (!trimmed) {
7+
return 'Build failed with no output captured.';
8+
}
9+
10+
if (trimmed.length <= MAX_BUILD_OUTPUT_CHARS) {
11+
return trimmed;
12+
}
13+
14+
return `Build output (truncated):\n${trimmed.slice(-MAX_BUILD_OUTPUT_CHARS)}`;
15+
}

app/components/ui/Badge.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use client';
2-
31
import * as React from 'react';
42
import { cva, type VariantProps } from 'class-variance-authority';
53
import { classNames } from '~/utils/classNames';

app/components/ui/Collapsible.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use client';
2-
31
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
42

53
const Collapsible = CollapsiblePrimitive.Root;

app/components/ui/ScrollArea.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use client';
2-
31
import * as React from 'react';
42
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
53
import { classNames } from '~/utils/classNames';

0 commit comments

Comments
 (0)