Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,16 @@ export class OllamaProvider extends BaseLLMProvider {
}
}

// 处理<think>标签
// 处理 thinking 字段
const content = response.message?.content || ''
if (content.includes('<think>')) {
const thinking = response.message?.thinking || ''

if (thinking) {
resultResp.reasoning_content = thinking
resultResp.content = content
}
// 处理<think>标签(其他模型)
else if (content.includes('<think>')) {
const thinkStart = content.indexOf('<think>')
const thinkEnd = content.indexOf('</think>')

Expand All @@ -194,7 +201,7 @@ export class OllamaProvider extends BaseLLMProvider {
resultResp.content = content
}
} else {
// 没有think标签,所有内容作为普通内容
// 没有特殊格式,所有内容作为普通内容
resultResp.content = content
}

Expand Down Expand Up @@ -492,10 +499,10 @@ export class OllamaProvider extends BaseLLMProvider {
messages: processedMessages,
options: {
temperature: temperature || 0.7,
num_predict: maxTokens,
...(modelConfig?.reasoningEffort && { reasoning_effort: modelConfig.reasoningEffort })
num_predict: maxTokens
},
stream: true as const,
...(modelConfig?.reasoningEffort && { reasoning_effort: modelConfig.reasoningEffort }),
...(supportsFunctionCall && ollamaTools && ollamaTools.length > 0
? { tools: ollamaTools }
: {})
Expand Down Expand Up @@ -600,6 +607,12 @@ export class OllamaProvider extends BaseLLMProvider {
continue
}

// 处理 thinking 字段
const currentThinking = chunk.message?.thinking || ''
if (currentThinking) {
yield { type: 'reasoning', reasoning_content: currentThinking }
}

Comment on lines +610 to +615
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential duplication: streaming message.thinking is likely cumulative — emit only the delta

Many providers expose thinking as the full-so-far content. Appending currentThinking verbatim each chunk will duplicate previously emitted text, causing runaway growth in the renderer (which concatenates reasoning_content). Track and emit only the delta.

Apply this diff to track and emit incremental deltas:

@@
-      // --- 流处理循环 ---
+      // --- 流处理循环 ---
+      // Track last emitted reasoning for streaming `thinking`
+      let lastThinking = ''
       for await (const chunk of stream) {
@@
-        // 处理 thinking 字段
-        const currentThinking = chunk.message?.thinking || ''
-        if (currentThinking) {
-          yield { type: 'reasoning', reasoning_content: currentThinking }
-        }
+        // 处理 thinking 字段(只输出增量)
+        const currentThinking = chunk.message?.thinking ?? ''
+        if (currentThinking && currentThinking !== lastThinking) {
+          const delta = currentThinking.startsWith(lastThinking)
+            ? currentThinking.slice(lastThinking.length)
+            : currentThinking
+          if (delta) {
+            yield { type: 'reasoning', reasoning_content: delta }
+          }
+          lastThinking = currentThinking
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 处理 thinking 字段
const currentThinking = chunk.message?.thinking || ''
if (currentThinking) {
yield { type: 'reasoning', reasoning_content: currentThinking }
}
// --- 流处理循环 ---
// Track last emitted reasoning for streaming `thinking`
let lastThinking = ''
for await (const chunk of stream) {
// 处理 thinking 字段(只输出增量)
const currentThinking = chunk.message?.thinking ?? ''
if (currentThinking && currentThinking !== lastThinking) {
const delta = currentThinking.startsWith(lastThinking)
? currentThinking.slice(lastThinking.length)
: currentThinking
if (delta) {
yield { type: 'reasoning', reasoning_content: delta }
}
lastThinking = currentThinking
}
// ... (other chunk handling)
}
🤖 Prompt for AI Agents
In src/main/presenter/llmProviderPresenter/providers/ollamaProvider.ts around
lines 610 to 615, the code emits chunk.message.thinking verbatim which
duplicates previously streamed content; fix by keeping a local previousThinking
string, compute the incremental delta between chunk.message.thinking and
previousThinking (e.g., if current startsWith previous, delta =
current.slice(previous.length)), emit only the delta when non-empty as the
reasoning chunk, and then update previousThinking to the full currentThinking
(handle null/undefined as empty string and fall back to emitting full current if
not a simple prefix).

// 获取当前内容
const currentContent = chunk.message?.content || ''
if (!currentContent) continue
Expand Down
51 changes: 43 additions & 8 deletions src/main/presenter/sqlitePresenter/tables/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type ConversationRow = {
is_pinned: number
enabled_mcp_tools: string | null
thinking_budget: number | null
reasoning_effort: string | null
verbosity: string | null
}

// 解析 JSON 字段
Expand Down Expand Up @@ -95,12 +97,21 @@ export class ConversationsTable extends BaseTable {
UPDATE conversations SET enabled_mcp_tools = NULL WHERE enabled_mcp_tools = '[]';
`
}
if (version === 6) {
return `
-- 添加 reasoning_effort 字段
ALTER TABLE conversations ADD COLUMN reasoning_effort TEXT DEFAULT NULL;

-- 添加 verbosity 字段
ALTER TABLE conversations ADD COLUMN verbosity TEXT DEFAULT NULL;
`
}

return null
}

getLatestVersion(): number {
return 5
return 6
}

async create(title: string, settings: Partial<CONVERSATION_SETTINGS> = {}): Promise<string> {
Expand All @@ -120,9 +131,11 @@ export class ConversationsTable extends BaseTable {
artifacts,
is_pinned,
enabled_mcp_tools,
thinking_budget
thinking_budget,
reasoning_effort,
verbosity
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
const conv_id = nanoid()
const now = Date.now()
Expand All @@ -141,7 +154,9 @@ export class ConversationsTable extends BaseTable {
settings.artifacts || 0,
0, // Default is_pinned to 0
settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : 'NULL',
settings.thinkingBudget !== undefined ? settings.thinkingBudget : null
settings.thinkingBudget !== undefined ? settings.thinkingBudget : null,
settings.reasoningEffort !== undefined ? settings.reasoningEffort : null,
settings.verbosity !== undefined ? settings.verbosity : null
)
return conv_id
}
Expand All @@ -165,7 +180,9 @@ export class ConversationsTable extends BaseTable {
artifacts,
is_pinned,
enabled_mcp_tools,
thinking_budget
thinking_budget,
reasoning_effort,
verbosity
FROM conversations
WHERE conv_id = ?
`
Expand All @@ -192,7 +209,11 @@ export class ConversationsTable extends BaseTable {
modelId: result.modelId,
artifacts: result.artifacts as 0 | 1,
enabledMcpTools: getJsonField(result.enabled_mcp_tools, undefined),
thinkingBudget: result.thinking_budget !== null ? result.thinking_budget : undefined
thinkingBudget: result.thinking_budget !== null ? result.thinking_budget : undefined,
reasoningEffort: result.reasoning_effort
? (result.reasoning_effort as 'minimal' | 'low' | 'medium' | 'high')
: undefined,
verbosity: result.verbosity ? (result.verbosity as 'low' | 'medium' | 'high') : undefined
}
}
}
Expand Down Expand Up @@ -253,6 +274,14 @@ export class ConversationsTable extends BaseTable {
updates.push('thinking_budget = ?')
params.push(data.settings.thinkingBudget)
}
if (data.settings.reasoningEffort !== undefined) {
updates.push('reasoning_effort = ?')
params.push(data.settings.reasoningEffort)
}
if (data.settings.verbosity !== undefined) {
updates.push('verbosity = ?')
params.push(data.settings.verbosity)
}
}
if (updates.length > 0 || data.updatedAt) {
updates.push('updated_at = ?')
Expand Down Expand Up @@ -298,7 +327,9 @@ export class ConversationsTable extends BaseTable {
artifacts,
is_pinned,
enabled_mcp_tools,
thinking_budget
thinking_budget,
reasoning_effort,
verbosity
FROM conversations
ORDER BY updated_at DESC
LIMIT ? OFFSET ?
Expand All @@ -324,7 +355,11 @@ export class ConversationsTable extends BaseTable {
modelId: row.modelId,
artifacts: row.artifacts as 0 | 1,
enabledMcpTools: getJsonField(row.enabled_mcp_tools, undefined),
thinkingBudget: row.thinking_budget !== null ? row.thinking_budget : undefined
thinkingBudget: row.thinking_budget !== null ? row.thinking_budget : undefined,
reasoningEffort: row.reasoning_effort
? (row.reasoning_effort as 'minimal' | 'low' | 'medium' | 'high')
: undefined,
verbosity: row.verbosity ? (row.verbosity as 'low' | 'medium' | 'high') : undefined
}
}))
}
Expand Down
17 changes: 6 additions & 11 deletions src/renderer/src/components/TitleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ const maxTokens = ref(chatStore.chatConfig.maxTokens)
const systemPrompt = ref(chatStore.chatConfig.systemPrompt)
const artifacts = ref(chatStore.chatConfig.artifacts)
const thinkingBudget = ref(chatStore.chatConfig.thinkingBudget)
const reasoningEffort = ref((chatStore.chatConfig as any).reasoningEffort)
const verbosity = ref((chatStore.chatConfig as any).verbosity)
const reasoningEffort = ref(chatStore.chatConfig.reasoningEffort)
const verbosity = ref(chatStore.chatConfig.verbosity)

// 获取模型配置来初始化默认值
const loadModelConfig = async () => {
Expand Down Expand Up @@ -258,8 +258,8 @@ watch(
newSystemPrompt !== chatStore.chatConfig.systemPrompt ||
newArtifacts !== chatStore.chatConfig.artifacts ||
newThinkingBudget !== chatStore.chatConfig.thinkingBudget ||
newReasoningEffort !== (chatStore.chatConfig as any).reasoningEffort ||
newVerbosity !== (chatStore.chatConfig as any).verbosity
newReasoningEffort !== chatStore.chatConfig.reasoningEffort ||
newVerbosity !== chatStore.chatConfig.verbosity
) {
chatStore.updateChatConfig({
temperature: newTemp,
Expand All @@ -285,13 +285,8 @@ watch(
systemPrompt.value = newConfig.systemPrompt
artifacts.value = newConfig.artifacts
thinkingBudget.value = newConfig.thinkingBudget

if ((newConfig as any).reasoningEffort !== undefined) {
reasoningEffort.value = (newConfig as any).reasoningEffort
}
if ((newConfig as any).verbosity !== undefined) {
verbosity.value = (newConfig as any).verbosity
}
reasoningEffort.value = newConfig.reasoningEffort
verbosity.value = newConfig.verbosity
if (
oldConfig &&
(newConfig.modelId !== oldConfig.modelId || newConfig.providerId !== oldConfig.providerId)
Expand Down
5 changes: 4 additions & 1 deletion src/renderer/src/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export const useChatStore = defineStore('chat', () => {
providerId: '',
modelId: '',
artifacts: 0,
enabledMcpTools: []
enabledMcpTools: [],
thinkingBudget: undefined,
reasoningEffort: undefined,
verbosity: undefined
})

// Deeplink 消息缓存
Expand Down
2 changes: 2 additions & 0 deletions src/shared/presenter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ export type CONVERSATION_SETTINGS = {
artifacts: 0 | 1
enabledMcpTools?: string[]
thinkingBudget?: number
reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high'
verbosity?: 'low' | 'medium' | 'high'
}

export type CONVERSATION = {
Expand Down
Loading