mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 10:52:15 +00:00
fix(desktop): preselect model in onboarding so first chat doesn't 400 (#427)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import { Send, Square, Paperclip, Search } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { useAppStore, generateId } from '../../lib/store';
|
||||
import { streamChat, streamResearch } from '../../lib/sse';
|
||||
import { fetchSavings, getBase } from '../../lib/api';
|
||||
@@ -155,6 +156,10 @@ export function InputArea() {
|
||||
const sendMessage = useCallback(async () => {
|
||||
const content = input.trim();
|
||||
if (!content || streamState.isStreaming) return;
|
||||
if (!selectedModel) {
|
||||
toast.error('Pick a model first (⌘K)');
|
||||
return;
|
||||
}
|
||||
|
||||
setInput('');
|
||||
|
||||
@@ -555,7 +560,7 @@ export function InputArea() {
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Message OpenJarvis..."
|
||||
placeholder={selectedModel ? 'Message OpenJarvis...' : 'Pick a model first (⌘K)...'}
|
||||
rows={1}
|
||||
className="flex-1 bg-transparent outline-none resize-none text-sm leading-relaxed"
|
||||
style={{ color: 'var(--color-text)', maxHeight: '200px' }}
|
||||
@@ -580,13 +585,13 @@ export function InputArea() {
|
||||
/>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
disabled={!input.trim() || modelLoading}
|
||||
disabled={!input.trim() || modelLoading || !selectedModel}
|
||||
title={selectedModel ? 'Send message' : 'Pick a model first (⌘K)'}
|
||||
className="p-2 rounded-xl transition-colors shrink-0 cursor-pointer disabled:opacity-30 disabled:cursor-default"
|
||||
style={{
|
||||
background: input.trim() ? 'var(--color-accent)' : 'var(--color-bg-tertiary)',
|
||||
color: input.trim() ? 'white' : 'var(--color-text-tertiary)',
|
||||
}}
|
||||
title="Send message"
|
||||
>
|
||||
<Send size={16} />
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Loader2, CheckCircle2, XCircle, Cpu, Server, Database } from 'lucide-react';
|
||||
import { getSetupStatus, type SetupStatus } from '../lib/api';
|
||||
import {
|
||||
getSetupStatus,
|
||||
fetchModels,
|
||||
fetchRecommendedModel,
|
||||
type SetupStatus,
|
||||
} from '../lib/api';
|
||||
import { useAppStore } from '../lib/store';
|
||||
|
||||
const STEPS = [
|
||||
{ key: 'ollama_ready', label: 'Inference Engine', icon: Cpu, detail: 'Starting Ollama...' },
|
||||
@@ -70,10 +76,33 @@ function StepRow({
|
||||
|
||||
export function SetupScreen({ onReady }: { onReady: () => void }) {
|
||||
const [status, setStatus] = useState<SetupStatus | null>(null);
|
||||
const handedOffRef = useRef(false);
|
||||
const poll = useCallback(async () => {
|
||||
const s = await getSetupStatus();
|
||||
if (s) setStatus(s);
|
||||
if (s?.phase === 'ready') {
|
||||
if (s?.phase === 'ready' && !handedOffRef.current) {
|
||||
handedOffRef.current = true;
|
||||
// Pre-select a model BEFORE handing off so the chat is usable on
|
||||
// first send. Without this, the main app's post-mount fetch can
|
||||
// lose a race to a fast first message and Ollama 400s.
|
||||
try {
|
||||
const [models, rec] = await Promise.all([
|
||||
fetchModels().catch(() => []),
|
||||
fetchRecommendedModel().catch(() => ({ model: '', reason: '' })),
|
||||
]);
|
||||
const store = useAppStore.getState();
|
||||
store.setModels(models);
|
||||
store.setModelsLoading(false);
|
||||
const recommended = rec.model && models.some((m) => m.id === rec.model)
|
||||
? rec.model
|
||||
: models[0]?.id || '';
|
||||
if (recommended && !store.selectedModel) {
|
||||
store.setSelectedModel(recommended);
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: store.setModels auto-selects on later fetch, and
|
||||
// the InputArea guards the empty-model case with a toast.
|
||||
}
|
||||
setTimeout(() => onReady(), 600);
|
||||
}
|
||||
}, [onReady]);
|
||||
|
||||
@@ -438,7 +438,12 @@ export const useAppStore = create<AppState>((set, get) => {
|
||||
|
||||
// ── Models & server ────────────────────────────────────────────
|
||||
|
||||
setModels: (models: ModelInfo[]) => set({ models }),
|
||||
setModels: (models: ModelInfo[]) =>
|
||||
set((state) =>
|
||||
!state.selectedModel && models.length > 0
|
||||
? { models, selectedModel: models[0].id }
|
||||
: { models },
|
||||
),
|
||||
setModelsLoading: (loading: boolean) => set({ modelsLoading: loading }),
|
||||
setSelectedModel: (model: string) => set({ selectedModel: model }),
|
||||
setServerInfo: (info: ServerInfo | null) => set({ serverInfo: info }),
|
||||
|
||||
Reference in New Issue
Block a user