diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index d10db74f8..403df51cf 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -456,7 +456,7 @@ export const executeToolServer = async ( ...(token && { authorization: `Bearer ${token}` }) }; - let requestOptions: RequestInit = { + const requestOptions: RequestInit = { method: httpMethod.toUpperCase(), headers }; @@ -1005,7 +1005,7 @@ export const getPipelinesList = async (token: string = '') => { throw error; } - let pipelines = res?.data ?? []; + const pipelines = res?.data ?? []; return pipelines; }; @@ -1148,7 +1148,7 @@ export const getPipelines = async (token: string, urlIdx?: string) => { throw error; } - let pipelines = res?.data ?? []; + const pipelines = res?.data ?? []; return pipelines; }; diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 489055c1b..1e0ce5850 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -331,7 +331,7 @@ export const generateTextCompletion = async (token: string = '', model: string, }; export const generateChatCompletion = async (token: string = '', body: object) => { - let controller = new AbortController(); + const controller = new AbortController(); let error = null; const res = await fetch(`${OLLAMA_API_BASE_URL}/api/chat`, { diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index f2449ff3e..1d0f66919 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -126,7 +126,7 @@ export const getUsers = async ( let error = null; let res = null; - let searchParams = new URLSearchParams(); + const searchParams = new URLSearchParams(); searchParams.set('page', `${page}`); diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index 20c0948d0..35f2c3fea 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -336,7 +336,7 @@
- {!!model?.meta?.description + {model?.meta?.description ? model?.meta?.description : model?.ollama?.digest ? `${model.id} (${model?.ollama?.digest})` diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 54fbd4a41..be76a68d5 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -125,6 +125,7 @@ let imageGenerationEnabled = false; let webSearchEnabled = false; let codeInterpreterEnabled = false; + let thinkingEnabled = false; let chat = null; let tags = []; @@ -152,6 +153,8 @@ selectedFilterIds = []; webSearchEnabled = false; imageGenerationEnabled = false; + codeInterpreterEnabled = false; + thinkingEnabled = false; if (sessionStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)) { try { @@ -167,6 +170,7 @@ webSearchEnabled = input.webSearchEnabled; imageGenerationEnabled = input.imageGenerationEnabled; codeInterpreterEnabled = input.codeInterpreterEnabled; + thinkingEnabled = input.thinkingEnabled; } } catch (e) {} } @@ -215,6 +219,7 @@ webSearchEnabled = false; imageGenerationEnabled = false; codeInterpreterEnabled = false; + thinkingEnabled = false; }; const setToolIds = async () => { @@ -454,6 +459,7 @@ webSearchEnabled = false; imageGenerationEnabled = false; codeInterpreterEnabled = false; + thinkingEnabled = false; try { const input = JSON.parse( @@ -468,6 +474,7 @@ webSearchEnabled = input.webSearchEnabled; imageGenerationEnabled = input.imageGenerationEnabled; codeInterpreterEnabled = input.codeInterpreterEnabled; + thinkingEnabled = input.thinkingEnabled; } } catch (e) {} } @@ -1659,6 +1666,19 @@ : undefined }, + // Add thinking mode option for compatible models + ...(thinkingEnabled && (model.owned_by === 'ollama' || model?.info?.meta?.owned_by === 'ollama') && + (() => { + const modelName = model?.name?.toLowerCase() || model.id.toLowerCase(); + return modelName.includes('deepseek-r1') || modelName.includes('qwen3') || modelName.includes('magistral'); + })() + ? { + options: { + thinking: true + } + } + : {}), + files: (files?.length ?? 0) > 0 ? files : undefined, filter_ids: selectedFilterIds.length > 0 ? selectedFilterIds : undefined, @@ -2112,6 +2132,7 @@ bind:imageGenerationEnabled bind:codeInterpreterEnabled bind:webSearchEnabled + bind:thinkingEnabled bind:atSelectedModel toolServers={$toolServers} transparentBackground={$settings?.backgroundImageUrl ?? false} @@ -2171,6 +2192,7 @@ bind:imageGenerationEnabled bind:codeInterpreterEnabled bind:webSearchEnabled + bind:thinkingEnabled bind:atSelectedModel transparentBackground={$settings?.backgroundImageUrl ?? false} toolServers={$toolServers} diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index f384c5b8f..533559e3b 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -89,6 +89,9 @@ export let webSearchEnabled = false; export let codeInterpreterEnabled = false; + // Add thinking mode state + export let thinkingEnabled = false; + $: onChange({ prompt, files: files @@ -104,7 +107,8 @@ selectedFilterIds, imageGenerationEnabled, webSearchEnabled, - codeInterpreterEnabled + codeInterpreterEnabled, + thinkingEnabled }); let showTools = false; @@ -159,6 +163,15 @@ $models.find((m) => m.id === model)?.info?.meta?.capabilities?.code_interpreter ?? true ); + // Add thinking capable models check + let thinkingCapableModels = []; + $: thinkingCapableModels = (atSelectedModel?.id ? [atSelectedModel.id] : selectedModels).filter( + (model) => { + const modelName = $models.find((m) => m.id === model)?.name?.toLowerCase() || model.toLowerCase(); + return modelName.includes('deepseek-r1') || modelName.includes('qwen3') || modelName.includes('magistral'); + } + ); + let toggleFilters = []; $: toggleFilters = (atSelectedModel?.id ? [atSelectedModel.id] : selectedModels) .map((id) => ($models.find((model) => model.id === id) || {})?.filters ?? []) @@ -188,6 +201,12 @@ $config?.features?.enable_code_interpreter && ($_user.role === 'admin' || $_user?.permissions?.features?.code_interpreter); + let showThinkingButton = false; + $: showThinkingButton = + (atSelectedModel?.id ? [atSelectedModel.id] : selectedModels).length === + thinkingCapableModels.length && + thinkingCapableModels.length > 0; // Only show if at least one model supports thinking + const scrollToBottom = () => { const element = document.getElementById('messages-container'); element.scrollTo({ @@ -744,6 +763,28 @@
{/if} + {#if thinkingEnabled && showThinkingButton} +
+
+ + + + + + {$i18n.t('Thinking mode enabled - Model may take longer but respond better')} +
+
+ {/if} +
{#if $settings?.richTextInput ?? true}
- {#if $_user && (showToolsButton || (toggleFilters && toggleFilters.length > 0) || showWebSearchButton || showImageGenerationButton || showCodeInterpreterButton)} + {#if $_user && (showToolsButton || (toggleFilters && toggleFilters.length > 0) || showWebSearchButton || showImageGenerationButton || showCodeInterpreterButton || showThinkingButton)}
@@ -1394,6 +1435,38 @@ {/if} + + {#if showThinkingButton} + + + + {/if}
{/if}
diff --git a/src/lib/components/chat/MessageInput/CallOverlay.svelte b/src/lib/components/chat/MessageInput/CallOverlay.svelte index ba7f5958c..78c001b03 100644 --- a/src/lib/components/chat/MessageInput/CallOverlay.svelte +++ b/src/lib/components/chat/MessageInput/CallOverlay.svelte @@ -50,7 +50,7 @@ const devices = await navigator.mediaDevices.enumerateDevices(); videoInputDevices = devices.filter((device) => device.kind === 'videoinput'); - if (!!navigator.mediaDevices.getDisplayMedia) { + if (navigator.mediaDevices.getDisplayMedia) { videoInputDevices = [ ...videoInputDevices, { diff --git a/src/lib/components/chat/Placeholder.svelte b/src/lib/components/chat/Placeholder.svelte index ce524db42..f6842eec7 100644 --- a/src/lib/components/chat/Placeholder.svelte +++ b/src/lib/components/chat/Placeholder.svelte @@ -39,6 +39,7 @@ export let imageGenerationEnabled = false; export let codeInterpreterEnabled = false; export let webSearchEnabled = false; + export let thinkingEnabled = false; export let toolServers = []; @@ -213,6 +214,7 @@ bind:imageGenerationEnabled bind:codeInterpreterEnabled bind:webSearchEnabled + bind:thinkingEnabled bind:atSelectedModel {toolServers} {transparentBackground} diff --git a/src/lib/components/chat/Settings/Audio.svelte b/src/lib/components/chat/Settings/Audio.svelte index 8031f1677..75aea2d29 100644 --- a/src/lib/components/chat/Settings/Audio.svelte +++ b/src/lib/components/chat/Settings/Audio.svelte @@ -130,7 +130,7 @@ TTSModel = await KokoroTTS.from_pretrained(model_id, { dtype: TTSEngineConfig.dtype, // Options: "fp32", "fp16", "q8", "q4", "q4f16" - device: !!navigator?.gpu ? 'webgpu' : 'wasm', // Detect WebGPU + device: navigator?.gpu ? 'webgpu' : 'wasm', // Detect WebGPU progress_callback: (e) => { TTSModelProgress = e; console.log(e); diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts index a5a83d093..27c58383c 100644 --- a/src/lib/i18n/index.ts +++ b/src/lib/i18n/index.ts @@ -38,10 +38,10 @@ const createIsLoadingStore = (i18n: i18nType) => { }; export const initI18n = (defaultLocale?: string | undefined) => { - let detectionOrder = defaultLocale + const detectionOrder = defaultLocale ? ['querystring', 'localStorage'] : ['querystring', 'localStorage', 'navigator']; - let fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US']; + const fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US']; const loadResource = (language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`); diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 363ddf6f2..5d75acb3f 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -221,6 +221,9 @@ "Code Interpreter": "", "Code Interpreter Engine": "", "Code Interpreter Prompt Template": "", + "Thinking": "Thinking", + "Enable thinking mode - Model may take longer but respond better": "Enable thinking mode - Model may take longer but respond better", + "Thinking mode enabled - Model may take longer but respond better": "Thinking mode enabled - Model may take longer but respond better", "Collapse": "", "Collection": "", "Color": "", diff --git a/src/lib/utils/characters/index.ts b/src/lib/utils/characters/index.ts index af3436693..dff08e66f 100644 --- a/src/lib/utils/characters/index.ts +++ b/src/lib/utils/characters/index.ts @@ -74,15 +74,15 @@ const readPngChunks = (data) => { if (!isValidPng) throw new Error('Invalid PNG file'); - let chunks = []; + const chunks = []; let offset = 8; // Skip PNG signature while (offset < data.length) { - let length = + const length = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; - let type = String.fromCharCode.apply(null, data.slice(offset + 4, offset + 8)); - let chunkData = data.slice(offset + 8, offset + 8 + length); - let crc = + const type = String.fromCharCode.apply(null, data.slice(offset + 4, offset + 8)); + const chunkData = data.slice(offset + 8, offset + 8 + length); + const crc = (data[offset + 8 + length] << 24) | (data[offset + 8 + length + 1] << 16) | (data[offset + 8 + length + 2] << 8) | diff --git a/src/lib/utils/marked/katex-extension.ts b/src/lib/utils/marked/katex-extension.ts index 9e864ca18..8d7d0dc87 100644 --- a/src/lib/utils/marked/katex-extension.ts +++ b/src/lib/utils/marked/katex-extension.ts @@ -23,8 +23,8 @@ const ALLOWED_SURROUNDING_CHARS = // const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/; // const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/; -let inlinePatterns = []; -let blockPatterns = []; +const inlinePatterns = []; +const blockPatterns = []; function escapeRegex(string) { return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); @@ -69,7 +69,7 @@ export default function (options = {}) { } function katexStart(src, displayMode: boolean) { - let ruleReg = displayMode ? blockRule : inlineRule; + const ruleReg = displayMode ? blockRule : inlineRule; let indexSrc = src; @@ -78,7 +78,7 @@ function katexStart(src, displayMode: boolean) { let startIndex = -1; let startDelimiter = ''; let endDelimiter = ''; - for (let delimiter of DELIMITER_LIST) { + for (const delimiter of DELIMITER_LIST) { if (delimiter.display !== displayMode) { continue; } @@ -115,8 +115,8 @@ function katexStart(src, displayMode: boolean) { } function katexTokenizer(src, tokens, displayMode: boolean) { - let ruleReg = displayMode ? blockRule : inlineRule; - let type = displayMode ? 'blockKatex' : 'inlineKatex'; + const ruleReg = displayMode ? blockRule : inlineRule; + const type = displayMode ? 'blockKatex' : 'inlineKatex'; const match = src.match(ruleReg); diff --git a/src/lib/workers/kokoro.worker.ts b/src/lib/workers/kokoro.worker.ts index ce047a655..29175339b 100644 --- a/src/lib/workers/kokoro.worker.ts +++ b/src/lib/workers/kokoro.worker.ts @@ -20,7 +20,7 @@ self.onmessage = async (event) => { try { tts = await KokoroTTS.from_pretrained(model_id, { dtype, - device: !!navigator?.gpu ? 'webgpu' : 'wasm' // Detect WebGPU + device: navigator?.gpu ? 'webgpu' : 'wasm' // Detect WebGPU }); isInitialized = true; // Mark as initialized after successful loading self.postMessage({ status: 'init:complete' }); diff --git a/src/lib/workers/pyodide.worker.ts b/src/lib/workers/pyodide.worker.ts index 09fb0e1f3..221effca5 100644 --- a/src/lib/workers/pyodide.worker.ts +++ b/src/lib/workers/pyodide.worker.ts @@ -40,7 +40,7 @@ async function loadPyodideAndPackages(packages: string[] = []) { packages: ['micropip'] }); - let mountDir = '/mnt'; + const mountDir = '/mnt'; self.pyodide.FS.mkdirTree(mountDir); // self.pyodide.FS.mount(self.pyodide.FS.filesystems.IDBFS, {}, mountDir);