mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-31 03:12:16 +00:00
feat: model switching shows loading spinner, disables input until ready
This commit is contained in:
@@ -23,6 +23,7 @@ export function InputArea() {
|
||||
const updateLastAssistant = useAppStore((s) => s.updateLastAssistant);
|
||||
const setStreamState = useAppStore((s) => s.setStreamState);
|
||||
const resetStream = useAppStore((s) => s.resetStream);
|
||||
const modelLoading = useAppStore((s) => s.modelLoading);
|
||||
|
||||
const { state: speechState, available: speechAvailable, startRecording, stopRecording } = useSpeech();
|
||||
|
||||
@@ -272,7 +273,7 @@ export function InputArea() {
|
||||
rows={1}
|
||||
className="flex-1 bg-transparent outline-none resize-none text-sm leading-relaxed"
|
||||
style={{ color: 'var(--color-text)', maxHeight: '200px' }}
|
||||
disabled={streamState.isStreaming}
|
||||
disabled={streamState.isStreaming || modelLoading}
|
||||
/>
|
||||
{streamState.isStreaming ? (
|
||||
<button
|
||||
@@ -293,7 +294,7 @@ export function InputArea() {
|
||||
/>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
disabled={!input.trim()}
|
||||
disabled={!input.trim() || modelLoading}
|
||||
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)',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Search, Cpu, X, Download, Loader2, Trash2, Check } from 'lucide-react';
|
||||
import { useAppStore } from '../lib/store';
|
||||
import { pullModel, deleteModel, fetchModels } from '../lib/api';
|
||||
import { pullModel, deleteModel, fetchModels, preloadModel } from '../lib/api';
|
||||
|
||||
/** Popular models that users can download from the catalogue. */
|
||||
const CATALOGUE_MODELS = [
|
||||
@@ -65,9 +65,25 @@ export function CommandPalette() {
|
||||
}
|
||||
}, [pullSuccess]);
|
||||
|
||||
const handleSelect = (modelId: string) => {
|
||||
const handleSelect = async (modelId: string) => {
|
||||
const previousModel = selectedModel;
|
||||
setSelectedModel(modelId);
|
||||
setCommandPaletteOpen(false);
|
||||
|
||||
// Preload the model if switching to a different one
|
||||
if (modelId !== previousModel) {
|
||||
const { setModelLoading, addLogEntry } = useAppStore.getState();
|
||||
setModelLoading(true);
|
||||
addLogEntry({ timestamp: Date.now(), level: 'info', category: 'model', message: `Switching to ${modelId}...` });
|
||||
try {
|
||||
await preloadModel(modelId);
|
||||
addLogEntry({ timestamp: Date.now(), level: 'info', category: 'model', message: `${modelId} loaded` });
|
||||
} catch (e: any) {
|
||||
addLogEntry({ timestamp: Date.now(), level: 'error', category: 'model', message: `Failed to load ${modelId}: ${e.message}` });
|
||||
} finally {
|
||||
setModelLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refreshModels = async () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Sun,
|
||||
Moon,
|
||||
Monitor,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { ConversationList } from './ConversationList';
|
||||
import { useAppStore } from '../../lib/store';
|
||||
@@ -29,6 +30,7 @@ export function Sidebar() {
|
||||
const selectedModel = useAppStore((s) => s.selectedModel);
|
||||
const serverInfo = useAppStore((s) => s.serverInfo);
|
||||
const setCommandPaletteOpen = useAppStore((s) => s.setCommandPaletteOpen);
|
||||
const modelLoading = useAppStore((s) => s.modelLoading);
|
||||
|
||||
const settings = useAppStore((s) => s.settings);
|
||||
const updateSettings = useAppStore((s) => s.updateSettings);
|
||||
@@ -120,16 +122,29 @@ export function Sidebar() {
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-bg-tertiary)')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'var(--color-bg-secondary)')}
|
||||
>
|
||||
<Cpu size={14} />
|
||||
<span className="truncate flex-1 text-left" style={{ color: 'var(--color-text)' }}>
|
||||
{selectedModel || serverInfo?.model || 'Select model'}
|
||||
</span>
|
||||
<kbd
|
||||
className="text-[10px] px-1.5 py-0.5 rounded font-mono"
|
||||
style={{ background: 'var(--color-bg-tertiary)', color: 'var(--color-text-tertiary)' }}
|
||||
>
|
||||
⌘K
|
||||
</kbd>
|
||||
{modelLoading ? (
|
||||
<Loader2 size={14} className="animate-spin" style={{ color: 'var(--color-accent)' }} />
|
||||
) : (
|
||||
<Cpu size={14} />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="truncate block text-left" style={{ color: 'var(--color-text)' }}>
|
||||
{selectedModel || serverInfo?.model || 'Select model'}
|
||||
</span>
|
||||
{modelLoading && (
|
||||
<span className="text-[10px] block" style={{ color: 'var(--color-accent)' }}>
|
||||
Loading model...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!modelLoading && (
|
||||
<kbd
|
||||
className="text-[10px] px-1.5 py-0.5 rounded font-mono"
|
||||
style={{ background: 'var(--color-bg-tertiary)', color: 'var(--color-text-tertiary)' }}
|
||||
>
|
||||
⌘K
|
||||
</kbd>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Search */}
|
||||
|
||||
Reference in New Issue
Block a user