Add support for vision and transcription models in GeneralSettings.jsx
This commit is contained in:
@@ -12,14 +12,17 @@ import {
|
|||||||
const BACKEND_API_URL_KEY = 'backendApiUrl';
|
const BACKEND_API_URL_KEY = 'backendApiUrl';
|
||||||
const OLLAMA_API_URL_KEY = 'ollamaApiUrl';
|
const OLLAMA_API_URL_KEY = 'ollamaApiUrl';
|
||||||
const EMBED_MODEL_KEY = 'embedModel';
|
const EMBED_MODEL_KEY = 'embedModel';
|
||||||
|
const RERANK_MODEL_KEY = 'rerankModel';
|
||||||
const MODEL_KEY = 'chatModel';
|
const MODEL_KEY = 'chatModel';
|
||||||
|
const VISION_MODEL_KEY = 'visionModel';
|
||||||
|
const TRANSCRIPTION_MODEL_KEY = 'transcriptionModel';
|
||||||
const STREAM_KEY = 'streamOutput';
|
const STREAM_KEY = 'streamOutput';
|
||||||
const DEFAULT_AUDIO_INPUT_DEVICE_ID = '';
|
const DEFAULT_AUDIO_INPUT_DEVICE_ID = '';
|
||||||
const DEFAULT_AUDIO_INPUT_LANGUAGE = '';
|
const DEFAULT_AUDIO_INPUT_LANGUAGE = '';
|
||||||
const DEFAULT_BACKEND_API_URL = 'http://127.0.0.1:8000';
|
const DEFAULT_BACKEND_API_URL = 'http://127.0.0.1:8000';
|
||||||
const DEFAULT_OLLAMA_API_URL = 'http://127.0.0.1:11434';
|
const DEFAULT_OLLAMA_API_URL = 'http://127.0.0.1:11434';
|
||||||
const DEFAULT_EMBED_MODEL = 'nomic-embed-text:latest';
|
const DEFAULT_EMBED_MODEL = 'nomic-embed-text:latest';
|
||||||
const BGE_EMBED_MODEL = 'bge-m3:latest';
|
const DEFAULT_TRANSCRIPTION_MODEL = 'base';
|
||||||
const DEFAULT_UPDATE_STATUS = {
|
const DEFAULT_UPDATE_STATUS = {
|
||||||
state: 'idle',
|
state: 'idle',
|
||||||
message: '',
|
message: '',
|
||||||
@@ -32,6 +35,18 @@ function resolveBackendApiUrl(settings) {
|
|||||||
return settings.backendApiUrl || settings.ollamaApiUrl || DEFAULT_BACKEND_API_URL;
|
return settings.backendApiUrl || settings.ollamaApiUrl || DEFAULT_BACKEND_API_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSelectOptions(values, currentValue, missingLabel) {
|
||||||
|
const uniqueValues = [...new Set((Array.isArray(values) ? values : []).filter(Boolean))];
|
||||||
|
const options = uniqueValues.map(value => ({ value, label: value }));
|
||||||
|
if (currentValue && !uniqueValues.includes(currentValue)) {
|
||||||
|
options.unshift({
|
||||||
|
value: currentValue,
|
||||||
|
label: `${currentValue} (${missingLabel})`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
function shortCommit(commit) {
|
function shortCommit(commit) {
|
||||||
return typeof commit === 'string' && commit.length > 7 ? commit.slice(0, 7) : commit || '—';
|
return typeof commit === 'string' && commit.length > 7 ? commit.slice(0, 7) : commit || '—';
|
||||||
}
|
}
|
||||||
@@ -45,6 +60,8 @@ function getStatusTone(state) {
|
|||||||
|
|
||||||
export default function GeneralSettings({
|
export default function GeneralSettings({
|
||||||
onModelChange,
|
onModelChange,
|
||||||
|
onVisionModelChange,
|
||||||
|
onTranscriptionModelChange,
|
||||||
onStreamOutputChange,
|
onStreamOutputChange,
|
||||||
onLibrariesPurged,
|
onLibrariesPurged,
|
||||||
onBackendApiUrlChange,
|
onBackendApiUrlChange,
|
||||||
@@ -55,8 +72,15 @@ export default function GeneralSettings({
|
|||||||
const [backendApiUrl, setBackendApiUrl] = useState('');
|
const [backendApiUrl, setBackendApiUrl] = useState('');
|
||||||
const [ollamaApiUrl, setOllamaApiUrl] = useState('');
|
const [ollamaApiUrl, setOllamaApiUrl] = useState('');
|
||||||
const [embedModel, setEmbedModel] = useState(DEFAULT_EMBED_MODEL);
|
const [embedModel, setEmbedModel] = useState(DEFAULT_EMBED_MODEL);
|
||||||
const [models, setModels] = useState([]);
|
const [rerankModel, setRerankModel] = useState(DEFAULT_EMBED_MODEL);
|
||||||
|
const [chatModels, setChatModels] = useState([]);
|
||||||
|
const [embeddingModels, setEmbeddingModels] = useState([]);
|
||||||
|
const [visionModels, setVisionModels] = useState([]);
|
||||||
|
const [rerankingModels, setRerankingModels] = useState([]);
|
||||||
|
const [whisperModels, setWhisperModels] = useState([]);
|
||||||
const [selectedModel, setSelectedModel] = useState('');
|
const [selectedModel, setSelectedModel] = useState('');
|
||||||
|
const [visionModel, setVisionModel] = useState('');
|
||||||
|
const [transcriptionModel, setTranscriptionModel] = useState(DEFAULT_TRANSCRIPTION_MODEL);
|
||||||
const [streamOutput, setStreamOutput] = useState(false);
|
const [streamOutput, setStreamOutput] = useState(false);
|
||||||
const [audioInputEnabled, setAudioInputEnabled] = useState(false);
|
const [audioInputEnabled, setAudioInputEnabled] = useState(false);
|
||||||
const [audioInputDeviceId, setAudioInputDeviceId] = useState(DEFAULT_AUDIO_INPUT_DEVICE_ID);
|
const [audioInputDeviceId, setAudioInputDeviceId] = useState(DEFAULT_AUDIO_INPUT_DEVICE_ID);
|
||||||
@@ -84,7 +108,10 @@ export default function GeneralSettings({
|
|||||||
setBackendApiUrl(resolveBackendApiUrl(settings));
|
setBackendApiUrl(resolveBackendApiUrl(settings));
|
||||||
setOllamaApiUrl(settings.ollamaApiUrl || DEFAULT_OLLAMA_API_URL);
|
setOllamaApiUrl(settings.ollamaApiUrl || DEFAULT_OLLAMA_API_URL);
|
||||||
setEmbedModel(settings.embedModel || DEFAULT_EMBED_MODEL);
|
setEmbedModel(settings.embedModel || DEFAULT_EMBED_MODEL);
|
||||||
|
setRerankModel(settings.rerankModel || settings.embedModel || DEFAULT_EMBED_MODEL);
|
||||||
setSelectedModel(settings.chatModel || '');
|
setSelectedModel(settings.chatModel || '');
|
||||||
|
setVisionModel(settings.visionModel || settings.chatModel || '');
|
||||||
|
setTranscriptionModel(settings.transcriptionModel || DEFAULT_TRANSCRIPTION_MODEL);
|
||||||
setStreamOutput(settings.streamOutput || false);
|
setStreamOutput(settings.streamOutput || false);
|
||||||
setAudioInputEnabled(settings.audioInputEnabled === true);
|
setAudioInputEnabled(settings.audioInputEnabled === true);
|
||||||
setAudioInputDeviceId(
|
setAudioInputDeviceId(
|
||||||
@@ -110,20 +137,82 @@ export default function GeneralSettings({
|
|||||||
fetch(backendApiUrl + '/models')
|
fetch(backendApiUrl + '/models')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const names = data.models?.map(m => m.name) || [];
|
setChatModels(Array.isArray(data.chat_models) ? data.chat_models : []);
|
||||||
setModels(names);
|
setEmbeddingModels(Array.isArray(data.embedding_models) ? data.embedding_models : []);
|
||||||
if (!selectedModel || !names.includes(selectedModel)) {
|
setVisionModels(Array.isArray(data.vision_models) ? data.vision_models : []);
|
||||||
const defaultModel = names[0] || '';
|
setRerankingModels(Array.isArray(data.reranking_models) ? data.reranking_models : []);
|
||||||
setSelectedModel(defaultModel);
|
setWhisperModels(Array.isArray(data.whisper_models) ? data.whisper_models.map(model => model.name).filter(Boolean) : []);
|
||||||
window.electronAPI.setSetting(MODEL_KEY, defaultModel);
|
|
||||||
if (onModelChange) {
|
|
||||||
onModelChange(defaultModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(err => console.error('Failed to load models', err));
|
.catch(err => console.error('Failed to load models', err));
|
||||||
}
|
}
|
||||||
}, [backendApiUrl, ollamaApiUrl, selectedModel, onModelChange]);
|
}, [backendApiUrl, ollamaApiUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatModels.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextModel = chatModels.includes(selectedModel) ? selectedModel : chatModels[0];
|
||||||
|
if (nextModel === selectedModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedModel(nextModel);
|
||||||
|
window.electronAPI.setSetting(MODEL_KEY, nextModel);
|
||||||
|
if (onModelChange) {
|
||||||
|
onModelChange(nextModel);
|
||||||
|
}
|
||||||
|
}, [chatModels, selectedModel, onModelChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visionModels.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextModel = visionModels.includes(visionModel) ? visionModel : visionModels[0];
|
||||||
|
if (nextModel === visionModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisionModel(nextModel);
|
||||||
|
window.electronAPI.setSetting(VISION_MODEL_KEY, nextModel);
|
||||||
|
if (onVisionModelChange) {
|
||||||
|
onVisionModelChange(nextModel);
|
||||||
|
}
|
||||||
|
}, [visionModels, visionModel, onVisionModelChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (embedModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextModel = embeddingModels[0] || DEFAULT_EMBED_MODEL;
|
||||||
|
setEmbedModel(nextModel);
|
||||||
|
window.electronAPI.setSetting(EMBED_MODEL_KEY, nextModel);
|
||||||
|
}, [embeddingModels, embedModel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rerankModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextModel = embedModel || rerankingModels[0] || DEFAULT_EMBED_MODEL;
|
||||||
|
setRerankModel(nextModel);
|
||||||
|
window.electronAPI.setSetting(RERANK_MODEL_KEY, nextModel);
|
||||||
|
}, [rerankingModels, rerankModel, embedModel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (transcriptionModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextModel = whisperModels[0] || DEFAULT_TRANSCRIPTION_MODEL;
|
||||||
|
setTranscriptionModel(nextModel);
|
||||||
|
window.electronAPI.setSetting(TRANSCRIPTION_MODEL_KEY, nextModel);
|
||||||
|
if (onTranscriptionModelChange) {
|
||||||
|
onTranscriptionModelChange(nextModel);
|
||||||
|
}
|
||||||
|
}, [whisperModels, transcriptionModel, onTranscriptionModelChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!audioInputSupported) {
|
if (!audioInputSupported) {
|
||||||
|
|||||||
Reference in New Issue
Block a user