Skip to content

Commit ed5ccf4

Browse files
authored
feat(chat): refine status bar controls (#1365)
* feat(chat): refine status bar controls * fix(chat): harden model picker behavior * feat(app): update preview and model db * feat(config): use reasoning portraits * fix(renderer): refine preview and model picker
1 parent 5030bf4 commit ed5ccf4

File tree

43 files changed

+48715
-7557
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+48715
-7557
lines changed

resources/model-db/providers.json

Lines changed: 43883 additions & 6456 deletions
Large diffs are not rendered by default.

scripts/fetch-provider-db.mjs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,78 @@ async function ensureDir(dir) {
1919

2020
const PROVIDER_ID_REGEX = /^[a-z0-9][a-z0-9-_]*$/
2121
const MODEL_ID_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9\-_.:/]*$/
22+
const REASONING_EFFORT_VALUES = ['minimal', 'low', 'medium', 'high']
23+
const VERBOSITY_VALUES = ['low', 'medium', 'high']
24+
const REASONING_MODE_VALUES = ['budget', 'effort', 'level', 'fixed', 'mixed']
25+
const REASONING_VISIBILITY_VALUES = ['hidden', 'summary', 'full', 'mixed']
2226
const isValidLowercaseProviderId = (id) =>
2327
typeof id === 'string' && id === id.toLowerCase() && PROVIDER_ID_REGEX.test(id)
2428
const isValidModelId = (id) =>
2529
typeof id === 'string' && MODEL_ID_REGEX.test(id)
2630

31+
const sanitizeStringArray = (value) => {
32+
if (typeof value === 'string') {
33+
const normalized = value.trim()
34+
return normalized ? [normalized] : undefined
35+
}
36+
if (!Array.isArray(value)) return undefined
37+
const values = value.filter((item) => typeof item === 'string' && item.trim())
38+
return values.length ? values : undefined
39+
}
40+
41+
const sanitizeReasoningBudget = (value) => {
42+
if (!value || typeof value !== 'object') return undefined
43+
const budget = {}
44+
if (typeof value.default === 'number' && Number.isFinite(value.default)) budget.default = value.default
45+
if (typeof value.min === 'number' && Number.isFinite(value.min)) budget.min = value.min
46+
if (typeof value.max === 'number' && Number.isFinite(value.max)) budget.max = value.max
47+
if (typeof value.auto === 'number' && Number.isFinite(value.auto)) budget.auto = value.auto
48+
if (typeof value.off === 'number' && Number.isFinite(value.off)) budget.off = value.off
49+
if (typeof value.unit === 'string') budget.unit = value.unit
50+
return Object.keys(budget).length ? budget : undefined
51+
}
52+
53+
const sanitizeReasoningOptions = (value, allowedValues) => {
54+
if (!Array.isArray(value)) return undefined
55+
const values = value.filter((item) => typeof item === 'string' && allowedValues.includes(item))
56+
return values.length ? values : undefined
57+
}
58+
59+
const sanitizeExtraReasoning = (value) => {
60+
if (!value || typeof value !== 'object') return undefined
61+
const reasoning = {}
62+
if (typeof value.supported === 'boolean') reasoning.supported = value.supported
63+
if (typeof value.default_enabled === 'boolean') reasoning.default_enabled = value.default_enabled
64+
if (typeof value.mode === 'string' && REASONING_MODE_VALUES.includes(value.mode)) {
65+
reasoning.mode = value.mode
66+
}
67+
const budget = sanitizeReasoningBudget(value.budget)
68+
if (budget) reasoning.budget = budget
69+
if (typeof value.effort === 'string' && REASONING_EFFORT_VALUES.includes(value.effort)) {
70+
reasoning.effort = value.effort
71+
}
72+
const effortOptions = sanitizeReasoningOptions(value.effort_options, REASONING_EFFORT_VALUES)
73+
if (effortOptions) reasoning.effort_options = effortOptions
74+
if (typeof value.verbosity === 'string' && VERBOSITY_VALUES.includes(value.verbosity)) {
75+
reasoning.verbosity = value.verbosity
76+
}
77+
const verbosityOptions = sanitizeReasoningOptions(value.verbosity_options, VERBOSITY_VALUES)
78+
if (verbosityOptions) reasoning.verbosity_options = verbosityOptions
79+
if (typeof value.level === 'string') reasoning.level = value.level
80+
const levelOptions = sanitizeStringArray(value.level_options)
81+
if (levelOptions) reasoning.level_options = levelOptions
82+
if (typeof value.interleaved === 'boolean') reasoning.interleaved = value.interleaved
83+
if (typeof value.summaries === 'boolean') reasoning.summaries = value.summaries
84+
if (typeof value.visibility === 'string' && REASONING_VISIBILITY_VALUES.includes(value.visibility)) {
85+
reasoning.visibility = value.visibility
86+
}
87+
const continuation = sanitizeStringArray(value.continuation)
88+
if (continuation) reasoning.continuation = continuation
89+
const notes = sanitizeStringArray(value.notes)
90+
if (notes) reasoning.notes = notes
91+
return Object.keys(reasoning).length ? reasoning : undefined
92+
}
93+
2794
function sanitizeAggregateJson(json) {
2895
if (!json || typeof json !== 'object') return null
2996
const providers = json.providers
@@ -88,6 +155,12 @@ function sanitizeAggregateJson(json) {
88155
if (Object.keys(rs).length) reasoning = rs
89156
}
90157

158+
let extra_capabilities
159+
const extraReasoning = sanitizeExtraReasoning(m.extra_capabilities?.reasoning)
160+
if (extraReasoning) {
161+
extra_capabilities = { reasoning: extraReasoning }
162+
}
163+
91164
// search (new schema)
92165
let search
93166
const s = m.search
@@ -127,6 +200,7 @@ function sanitizeAggregateJson(json) {
127200
temperature: typeof m.temperature === 'boolean' ? m.temperature : undefined,
128201
tool_call: typeof m.tool_call === 'boolean' ? m.tool_call : undefined,
129202
reasoning,
203+
extra_capabilities,
130204
search,
131205
attachment: typeof m.attachment === 'boolean' ? m.attachment : undefined,
132206
open_weights: typeof m.open_weights === 'boolean' ? m.open_weights : undefined,

src/main/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { electronApp } from '@electron-toolkit/utils'
55
import log from 'electron-log'
66
import { eventBus, SendTarget } from './eventbus'
77
import { NOTIFICATION_EVENTS } from './events'
8+
import { registerWorkspacePreviewSchemes } from './presenter/workspacePresenter/workspacePreviewProtocol'
9+
10+
registerWorkspacePreviewSchemes()
811

912
// Handle unhandled exceptions to prevent app crash or error dialogs
1013
process.on('uncaughtException', (error) => {

src/main/presenter/configPresenter/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { defaultShortcutKey, ShortcutKeySetting } from './shortcutKeySettings'
4040
import { ModelConfigHelper } from './modelConfig'
4141
import { KnowledgeConfHelper } from './knowledgeConfHelper'
4242
import { providerDbLoader } from './providerDbLoader'
43-
import { ProviderAggregate } from '@shared/types/model-db'
43+
import { ProviderAggregate, ReasoningPortrait } from '@shared/types/model-db'
4444
import { modelCapabilities } from './modelCapabilities'
4545
import { ProviderHelper } from './providerHelper'
4646
import { ModelStatusHelper } from './modelStatusHelper'
@@ -297,6 +297,10 @@ export class ConfigPresenter implements IConfigPresenter {
297297
return modelCapabilities.supportsReasoning(providerId, modelId)
298298
}
299299

300+
getReasoningPortrait(providerId: string, modelId: string): ReasoningPortrait | null {
301+
return modelCapabilities.getReasoningPortrait(providerId, modelId)
302+
}
303+
300304
getThinkingBudgetRange(
301305
providerId: string,
302306
modelId: string

0 commit comments

Comments
 (0)