added embedding model selection

This commit is contained in:
2026-03-20 12:00:44 +01:00
parent afaaa1cfb2
commit 6be438519c
18 changed files with 513 additions and 100 deletions

View File

@@ -127,6 +127,7 @@ const COLOR_SCHEME_KEY = 'colorScheme';
const WEBSEARCH_URL_KEY = 'websearch.searxUrl';
const WEBSEARCH_ENGINES_KEY = 'websearch.engines';
const CHAT_LIBRARY_MAP_KEY = 'chat.libraryBySession';
const DEFAULT_SEARX_URL = 'http://127.0.0.1:8888';
// Initial API value will be set by useEffect after settings are loaded
let API = import.meta.env.VITE_API_URL ?? 'http://127.0.0.1:8000';
@@ -137,6 +138,13 @@ function resolveBackendApiUrl(settings) {
return settings.backendApiUrl || settings.ollamaApiUrl || API;
}
function migrateLegacySearxUrl(value) {
const trimmed = typeof value === 'string' ? value.trim() : '';
if (!trimmed) return DEFAULT_SEARX_URL;
if (trimmed === 'http://localhost:8888') return DEFAULT_SEARX_URL;
return trimmed;
}
export default function App() {
const [chatSessions, setChatSessions] = useState([])
const [activeSessionId, setActiveSessionId] = useState(null)
@@ -169,7 +177,8 @@ export default function App() {
const [backendApiUrl, setBackendApiUrl] = useState(API); // State for Heimgeist backend URL
const [colorScheme, setColorScheme] = useState('Default'); // State for color scheme
const [streamOutput, setStreamOutput] = useState(false);
const [searxUrl, setSearxUrl] = useState(localStorage.getItem(WEBSEARCH_URL_KEY) || 'http://localhost:8888');
const [startupTaskMessage, setStartupTaskMessage] = useState('');
const [searxUrl, setSearxUrl] = useState(() => migrateLegacySearxUrl(localStorage.getItem(WEBSEARCH_URL_KEY)));
const [searxEngines, setSearxEngines] = useState(() => {
try {
const raw = localStorage.getItem(WEBSEARCH_ENGINES_KEY);
@@ -191,6 +200,8 @@ export default function App() {
const [loading, setLoading] = useState(true); // Loading state for initial session fetch
const [unreadSessions, setUnreadSessions] = useState([]); // Track unread messages
const [scrollPositions, setScrollPositions] = useState({}); // Store scroll positions for each session
const [settingsLoaded, setSettingsLoaded] = useState(false);
const startupOllamaCheckRanRef = useRef(false);
// Editing state for user messages
const [editingMessageIndex, setEditingMessageIndex] = useState(null);
const [editText, setEditText] = useState('');
@@ -253,6 +264,20 @@ export default function App() {
return String(error)
}
async function expectBackendJson(response) {
const data = await response.json().catch(() => null)
if (response.ok) return data
const detail = typeof data?.detail === 'string'
? data.detail
: (typeof data?.message === 'string' ? data.message : '')
throw new Error(detail || `HTTP ${response.status}`)
}
async function fetchStartupOllamaStatus() {
const response = await fetch(`${backendApiUrl}/ollama/startup-status`)
return expectBackendJson(response)
}
async function fetchLocalLibraryContext(slug, prompt, signal) {
if (!slug) return { contextBlock: null, sources: [] }
@@ -705,6 +730,8 @@ async function regenerateFromIndex(index, overrideUserText = null) {
setStreamOutput(settings.streamOutput || false);
setScrollPositions(settings.scrollPositions || {}); // Load scroll positions
applyColorScheme(settings.colorScheme || 'Default'); // Apply initial scheme
}).finally(() => {
setSettingsLoaded(true);
});
const handleFocus = () => {
@@ -724,6 +751,68 @@ async function regenerateFromIndex(index, overrideUserText = null) {
};
}, [activeSidebarMode]);
useEffect(() => {
if (!settingsLoaded || !backendApiUrl || startupOllamaCheckRanRef.current) return
startupOllamaCheckRanRef.current = true
let cancelled = false
;(async () => {
let actionStarted = false
try {
let status = await fetchStartupOllamaStatus()
if (cancelled) return
if (!status?.ollama_running && status?.can_manage_locally) {
const confirmed = window.confirm(
`Ollama is not running at ${status.ollama_url}. Start it in the background now with "ollama serve"?`
)
if (cancelled) return
if (confirmed) {
actionStarted = true
setStartupTaskMessage('Starting Ollama in the background...')
const response = await fetch(`${backendApiUrl}/ollama/start`, { method: 'POST' })
status = await expectBackendJson(response)
if (cancelled) return
}
}
if (status?.ollama_running && !status?.embedding_model_available && status?.can_manage_locally) {
const confirmed = window.confirm(
`The selected embedding model "${status.selected_embed_model}" is not installed in Ollama. Pull it now?`
)
if (cancelled) return
if (confirmed) {
actionStarted = true
setStartupTaskMessage(`Pulling ${status.selected_embed_model} in Ollama...`)
const response = await fetch(`${backendApiUrl}/ollama/pull`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: status.selected_embed_model })
})
await expectBackendJson(response)
if (cancelled) return
}
}
} catch (error) {
if (!cancelled) {
console.warn('startup Ollama check failed', error)
if (actionStarted) {
window.alert(`Startup action failed: ${getErrorText(error)}`)
}
}
} finally {
if (!cancelled) {
setStartupTaskMessage('')
}
}
})()
return () => {
cancelled = true
}
}, [backendApiUrl, settingsLoaded]);
// Apply color scheme whenever it changes
useEffect(() => {
applyColorScheme(colorScheme);
@@ -1785,6 +1874,11 @@ async function createNewChat() {
<div className="resizer" onMouseDown={startResizing}></div>
</div>
<div className="main-content">
{startupTaskMessage && (
<div className="startup-task-banner" role="status" aria-live="polite">
{startupTaskMessage}
</div>
)}
{activeSidebarMode === 'chats' && (
<>
<div className="header">