mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-10-17 05:14:19 +02:00
* feat(AI): per-model thinking toggle with global default (off)
Stop forcing thinking on for every capable model. Adds a global default
(ai.autoThinking, ships OFF) and a per-model override in the chat window,
shown only for thinking-capable models and remembered client-side.
The /v1 (OpenAI-compat) endpoint ignores `think`; reasoning_effort is the
real lever. The controller resolves per-request preference -> global default
-> OFF (gated on capability), and the service maps that to reasoning_effort
('none' to suppress on a capable model, 'medium' for gpt-oss, unset to let a
capable model default thinking on). A new thinkingCapable flag keeps
non-Ollama backends from ever receiving reasoning_effort.
installed-models is enriched with a `thinking` flag (checkModelHasThinking,
now memoized) so the picker knows which models get the toggle.
Stacks on #1078 (reasoning-field read + client-disconnect abort).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(AI): thinking toggle as NOMAD Switch + info tooltip
Address review feedback on the per-model thinking control:
- Use the shared Switch component (matches AI Assistant settings) instead
of a raw checkbox.
- Label "Thinking:" with a colon to match the adjacent "Model:" label.
- Add an InfoTooltip explaining what thinking does and where the default
lives. Extend InfoTooltip with optional `position` ('top'|'bottom') and
`align` ('center'|'right') so it opens downward and expands leftward from
the right-edge header slot instead of being clipped/crushed against the
viewport edge. Defaults preserve existing (benchmark page) behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
5.1 KiB
TypeScript
149 lines
5.1 KiB
TypeScript
import KVStore from '#models/kv_store'
|
|
import { BenchmarkService } from '#services/benchmark_service'
|
|
import { MapService } from '#services/map_service'
|
|
import { OllamaService } from '#services/ollama_service'
|
|
import { SystemService } from '#services/system_service'
|
|
import { getSettingSchema, updateSettingSchema, validateSettingValue } from '#validators/settings'
|
|
import { inject } from '@adonisjs/core'
|
|
import type { HttpContext } from '@adonisjs/core/http'
|
|
import env from '#start/env'
|
|
|
|
@inject()
|
|
export default class SettingsController {
|
|
constructor(
|
|
private systemService: SystemService,
|
|
private mapService: MapService,
|
|
private benchmarkService: BenchmarkService,
|
|
private ollamaService: OllamaService
|
|
) {}
|
|
|
|
async system({ inertia }: HttpContext) {
|
|
const systemInfo = await this.systemService.getSystemInfo()
|
|
return inertia.render('settings/system', {
|
|
system: {
|
|
info: systemInfo,
|
|
},
|
|
})
|
|
}
|
|
|
|
async apps({ inertia }: HttpContext) {
|
|
const services = await this.systemService.getServices({ installedOnly: false })
|
|
return inertia.render('settings/apps', {
|
|
system: {
|
|
services,
|
|
},
|
|
})
|
|
}
|
|
|
|
async legal({ inertia }: HttpContext) {
|
|
return inertia.render('settings/legal')
|
|
}
|
|
|
|
async support({ inertia }: HttpContext) {
|
|
return inertia.render('settings/support')
|
|
}
|
|
|
|
async maps({ inertia }: HttpContext) {
|
|
const baseAssetsCheck = await this.mapService.ensureBaseAssets()
|
|
const regionFiles = await this.mapService.listRegions()
|
|
return inertia.render('settings/maps', {
|
|
maps: {
|
|
baseAssetsExist: baseAssetsCheck,
|
|
regionFiles: regionFiles.files,
|
|
},
|
|
})
|
|
}
|
|
|
|
async models({ inertia }: HttpContext) {
|
|
const availableModels = await this.ollamaService.getAvailableModels({
|
|
sort: 'pulls',
|
|
recommendedOnly: false,
|
|
query: null,
|
|
limit: 15,
|
|
})
|
|
const installedModels = await this.ollamaService.getModels().catch(() => [])
|
|
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
|
|
const aiAssistantCustomName = await KVStore.getValue('ai.assistantCustomName')
|
|
const remoteOllamaUrl = await KVStore.getValue('ai.remoteOllamaUrl')
|
|
const ollamaFlashAttention = await KVStore.getValue('ai.ollamaFlashAttention')
|
|
const autoThinking = await KVStore.getValue('ai.autoThinking')
|
|
return inertia.render('settings/models', {
|
|
models: {
|
|
availableModels: availableModels?.models || [],
|
|
installedModels: installedModels || [],
|
|
settings: {
|
|
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false,
|
|
aiAssistantCustomName: aiAssistantCustomName ?? '',
|
|
remoteOllamaUrl: remoteOllamaUrl ?? '',
|
|
ollamaFlashAttention: ollamaFlashAttention ?? true,
|
|
autoThinking: autoThinking ?? false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
async update({ inertia }: HttpContext) {
|
|
const updateInfo = await this.systemService.checkLatestVersion()
|
|
return inertia.render('settings/update', {
|
|
system: {
|
|
updateAvailable: updateInfo.updateAvailable,
|
|
latestVersion: updateInfo.latestVersion,
|
|
currentVersion: updateInfo.currentVersion,
|
|
},
|
|
})
|
|
}
|
|
|
|
async zim({ inertia }: HttpContext) {
|
|
return inertia.render('settings/zim/index')
|
|
}
|
|
|
|
async zimRemote({ inertia }: HttpContext) {
|
|
return inertia.render('settings/zim/remote-explorer')
|
|
}
|
|
|
|
async creatorPacks({ inertia }: HttpContext) {
|
|
return inertia.render('settings/creator-packs')
|
|
}
|
|
|
|
async benchmark({ inertia }: HttpContext) {
|
|
const latestResult = await this.benchmarkService.getLatestResult()
|
|
const status = this.benchmarkService.getStatus()
|
|
return inertia.render('settings/benchmark', {
|
|
benchmark: {
|
|
latestResult,
|
|
status: status.status,
|
|
currentBenchmarkId: status.benchmarkId,
|
|
},
|
|
})
|
|
}
|
|
|
|
async advanced({ inertia }: HttpContext) {
|
|
// When the env var is set it always takes precedence over the stored value,
|
|
// so surface that to the UI to disable the field and explain the override.
|
|
const envOverride = Boolean(env.get('INTERNET_STATUS_TEST_URL')?.trim())
|
|
const internetStatusTestUrl = await KVStore.getValue('system.internetStatusTestUrl')
|
|
return inertia.render('settings/advanced', {
|
|
advanced: {
|
|
internetStatusTestUrl: internetStatusTestUrl ?? '',
|
|
internetStatusTestUrlEnvOverride: envOverride,
|
|
},
|
|
})
|
|
}
|
|
|
|
async getSetting({ request, response }: HttpContext) {
|
|
const { key } = await getSettingSchema.validate({ key: request.qs().key });
|
|
const value = await KVStore.getValue(key);
|
|
return response.status(200).send({ key, value });
|
|
}
|
|
|
|
async updateSetting({ request, response }: HttpContext) {
|
|
const reqData = await request.validateUsing(updateSettingSchema)
|
|
const valueError = validateSettingValue(reqData.key, reqData.value)
|
|
if (valueError) {
|
|
return response.status(422).send({ success: false, message: valueError })
|
|
}
|
|
await this.systemService.updateSetting(reqData.key, reqData.value)
|
|
return response.status(200).send({ success: true, message: 'Setting updated successfully' })
|
|
}
|
|
}
|