mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-22 07:26:46 +00:00
feat(ai): add configurable max_tokens in shared model settings
This commit is contained in:
@@ -77,6 +77,7 @@ interface ConfigSchema {
|
|||||||
aiModelApiBaseUrl: string
|
aiModelApiBaseUrl: string
|
||||||
aiModelApiKey: string
|
aiModelApiKey: string
|
||||||
aiModelApiModel: string
|
aiModelApiModel: string
|
||||||
|
aiModelApiMaxTokens: number
|
||||||
aiInsightEnabled: boolean
|
aiInsightEnabled: boolean
|
||||||
aiInsightApiBaseUrl: string
|
aiInsightApiBaseUrl: string
|
||||||
aiInsightApiKey: string
|
aiInsightApiKey: string
|
||||||
@@ -194,6 +195,7 @@ export class ConfigService {
|
|||||||
aiModelApiBaseUrl: '',
|
aiModelApiBaseUrl: '',
|
||||||
aiModelApiKey: '',
|
aiModelApiKey: '',
|
||||||
aiModelApiModel: 'gpt-4o-mini',
|
aiModelApiModel: 'gpt-4o-mini',
|
||||||
|
aiModelApiMaxTokens: 200,
|
||||||
aiInsightEnabled: false,
|
aiInsightEnabled: false,
|
||||||
aiInsightApiBaseUrl: '',
|
aiInsightApiBaseUrl: '',
|
||||||
aiInsightApiKey: '',
|
aiInsightApiKey: '',
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ const SILENCE_SCAN_INITIAL_DELAY_MS = 3 * 60 * 1000
|
|||||||
|
|
||||||
/** 单次 API 请求超时(毫秒) */
|
/** 单次 API 请求超时(毫秒) */
|
||||||
const API_TIMEOUT_MS = 45_000
|
const API_TIMEOUT_MS = 45_000
|
||||||
const API_MAX_TOKENS = 200
|
const API_MAX_TOKENS_DEFAULT = 200
|
||||||
|
const API_MAX_TOKENS_MIN = 1
|
||||||
|
const API_MAX_TOKENS_MAX = 65_535
|
||||||
const API_TEMPERATURE = 0.7
|
const API_TEMPERATURE = 0.7
|
||||||
|
|
||||||
/** 沉默天数阈值默认值 */
|
/** 沉默天数阈值默认值 */
|
||||||
@@ -47,6 +49,7 @@ const INSIGHT_CONFIG_KEYS = new Set([
|
|||||||
'aiModelApiBaseUrl',
|
'aiModelApiBaseUrl',
|
||||||
'aiModelApiKey',
|
'aiModelApiKey',
|
||||||
'aiModelApiModel',
|
'aiModelApiModel',
|
||||||
|
'aiModelApiMaxTokens',
|
||||||
'aiInsightAllowSocialContext',
|
'aiInsightAllowSocialContext',
|
||||||
'aiInsightSocialContextCount',
|
'aiInsightSocialContextCount',
|
||||||
'aiInsightWeiboCookie',
|
'aiInsightWeiboCookie',
|
||||||
@@ -67,6 +70,7 @@ interface SharedAiModelConfig {
|
|||||||
apiBaseUrl: string
|
apiBaseUrl: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
model: string
|
model: string
|
||||||
|
maxTokens: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 日志 ─────────────────────────────────────────────────────────────────────
|
// ─── 日志 ─────────────────────────────────────────────────────────────────────
|
||||||
@@ -171,6 +175,12 @@ function formatTimestamp(ts: number): string {
|
|||||||
return new Date(ts).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
return new Date(ts).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeApiMaxTokens(value: unknown): number {
|
||||||
|
const numeric = Number(value)
|
||||||
|
if (!Number.isFinite(numeric)) return API_MAX_TOKENS_DEFAULT
|
||||||
|
return Math.min(API_MAX_TOKENS_MAX, Math.max(API_MAX_TOKENS_MIN, Math.floor(numeric)))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用 OpenAI 兼容 API(非流式),返回模型第一条消息内容。
|
* 调用 OpenAI 兼容 API(非流式),返回模型第一条消息内容。
|
||||||
* 使用 Node 原生 https/http 模块,无需任何第三方 SDK。
|
* 使用 Node 原生 https/http 模块,无需任何第三方 SDK。
|
||||||
@@ -180,7 +190,8 @@ function callApi(
|
|||||||
apiKey: string,
|
apiKey: string,
|
||||||
model: string,
|
model: string,
|
||||||
messages: Array<{ role: string; content: string }>,
|
messages: Array<{ role: string; content: string }>,
|
||||||
timeoutMs: number = API_TIMEOUT_MS
|
timeoutMs: number = API_TIMEOUT_MS,
|
||||||
|
maxTokens: number = API_MAX_TOKENS_DEFAULT
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
|
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
|
||||||
@@ -195,7 +206,7 @@ function callApi(
|
|||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
model,
|
model,
|
||||||
messages,
|
messages,
|
||||||
max_tokens: API_MAX_TOKENS,
|
max_tokens: normalizeApiMaxTokens(maxTokens),
|
||||||
temperature: API_TEMPERATURE,
|
temperature: API_TEMPERATURE,
|
||||||
stream: false
|
stream: false
|
||||||
})
|
})
|
||||||
@@ -402,7 +413,7 @@ class InsightService {
|
|||||||
* 供设置页"测试连接"按钮调用。
|
* 供设置页"测试连接"按钮调用。
|
||||||
*/
|
*/
|
||||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||||
const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig()
|
const { apiBaseUrl, apiKey, model, maxTokens } = this.getSharedAiModelConfig()
|
||||||
|
|
||||||
if (!apiBaseUrl || !apiKey) {
|
if (!apiBaseUrl || !apiKey) {
|
||||||
return { success: false, message: '请先填写 API 地址和 API Key' }
|
return { success: false, message: '请先填写 API 地址和 API Key' }
|
||||||
@@ -417,6 +428,7 @@ class InsightService {
|
|||||||
[
|
[
|
||||||
`Endpoint: ${endpoint}`,
|
`Endpoint: ${endpoint}`,
|
||||||
`Model: ${model}`,
|
`Model: ${model}`,
|
||||||
|
`Max Tokens: ${maxTokens}`,
|
||||||
'',
|
'',
|
||||||
'用户提示词:',
|
'用户提示词:',
|
||||||
requestMessages[0].content
|
requestMessages[0].content
|
||||||
@@ -428,7 +440,8 @@ class InsightService {
|
|||||||
apiKey,
|
apiKey,
|
||||||
model,
|
model,
|
||||||
requestMessages,
|
requestMessages,
|
||||||
15_000
|
15_000,
|
||||||
|
maxTokens
|
||||||
)
|
)
|
||||||
insightDebugSection('INFO', 'AI 测试连接输出原文', result)
|
insightDebugSection('INFO', 'AI 测试连接输出原文', result)
|
||||||
return { success: true, message: `连接成功,模型回复:${result.slice(0, 50)}` }
|
return { success: true, message: `连接成功,模型回复:${result.slice(0, 50)}` }
|
||||||
@@ -515,7 +528,7 @@ class InsightService {
|
|||||||
return { success: false, message: '请先在设置中开启「AI 足迹总结」' }
|
return { success: false, message: '请先在设置中开启「AI 足迹总结」' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig()
|
const { apiBaseUrl, apiKey, model, maxTokens } = this.getSharedAiModelConfig()
|
||||||
if (!apiBaseUrl || !apiKey) {
|
if (!apiBaseUrl || !apiKey) {
|
||||||
return { success: false, message: '请先填写通用 AI 模型配置(API 地址和 Key)' }
|
return { success: false, message: '请先填写通用 AI 模型配置(API 地址和 Key)' }
|
||||||
}
|
}
|
||||||
@@ -579,7 +592,8 @@ ${topMentionText}
|
|||||||
{ role: 'system', content: systemPrompt },
|
{ role: 'system', content: systemPrompt },
|
||||||
{ role: 'user', content: userPrompt }
|
{ role: 'user', content: userPrompt }
|
||||||
],
|
],
|
||||||
25_000
|
25_000,
|
||||||
|
maxTokens
|
||||||
)
|
)
|
||||||
const insight = result.trim().slice(0, 400)
|
const insight = result.trim().slice(0, 400)
|
||||||
if (!insight) return { success: false, message: '模型返回为空' }
|
if (!insight) return { success: false, message: '模型返回为空' }
|
||||||
@@ -611,8 +625,9 @@ ${topMentionText}
|
|||||||
|| this.config.get('aiInsightApiModel')
|
|| this.config.get('aiInsightApiModel')
|
||||||
|| 'gpt-4o-mini'
|
|| 'gpt-4o-mini'
|
||||||
).trim() || 'gpt-4o-mini'
|
).trim() || 'gpt-4o-mini'
|
||||||
|
const maxTokens = normalizeApiMaxTokens(this.config.get('aiModelApiMaxTokens'))
|
||||||
|
|
||||||
return { apiBaseUrl, apiKey, model }
|
return { apiBaseUrl, apiKey, model, maxTokens }
|
||||||
}
|
}
|
||||||
|
|
||||||
private looksLikeWxid(text: string): boolean {
|
private looksLikeWxid(text: string): boolean {
|
||||||
@@ -1050,7 +1065,7 @@ ${topMentionText}
|
|||||||
if (!sessionId) return
|
if (!sessionId) return
|
||||||
if (!this.isEnabled()) return
|
if (!this.isEnabled()) return
|
||||||
|
|
||||||
const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig()
|
const { apiBaseUrl, apiKey, model, maxTokens } = this.getSharedAiModelConfig()
|
||||||
const allowContext = this.config.get('aiInsightAllowContext') as boolean
|
const allowContext = this.config.get('aiInsightAllowContext') as boolean
|
||||||
const contextCount = (this.config.get('aiInsightContextCount') as number) || 40
|
const contextCount = (this.config.get('aiInsightContextCount') as number) || 40
|
||||||
const resolvedDisplayName = await this.resolveInsightSessionDisplayName(sessionId, displayName)
|
const resolvedDisplayName = await this.resolveInsightSessionDisplayName(sessionId, displayName)
|
||||||
@@ -1133,6 +1148,7 @@ ${topMentionText}
|
|||||||
[
|
[
|
||||||
`接口地址:${endpoint}`,
|
`接口地址:${endpoint}`,
|
||||||
`模型:${model}`,
|
`模型:${model}`,
|
||||||
|
`Max Tokens:${maxTokens}`,
|
||||||
`触发原因:${triggerReason}`,
|
`触发原因:${triggerReason}`,
|
||||||
`上下文开关:${allowContext ? '开启' : '关闭'}`,
|
`上下文开关:${allowContext ? '开启' : '关闭'}`,
|
||||||
`上下文条数:${contextCount}`,
|
`上下文条数:${contextCount}`,
|
||||||
@@ -1150,7 +1166,9 @@ ${topMentionText}
|
|||||||
apiBaseUrl,
|
apiBaseUrl,
|
||||||
apiKey,
|
apiKey,
|
||||||
model,
|
model,
|
||||||
requestMessages
|
requestMessages,
|
||||||
|
API_TIMEOUT_MS,
|
||||||
|
maxTokens
|
||||||
)
|
)
|
||||||
|
|
||||||
insightLog('INFO', `API 返回原文: ${result.slice(0, 150)}`)
|
insightLog('INFO', `API 返回原文: ${result.slice(0, 150)}`)
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const [aiModelApiBaseUrl, setAiModelApiBaseUrl] = useState('')
|
const [aiModelApiBaseUrl, setAiModelApiBaseUrl] = useState('')
|
||||||
const [aiModelApiKey, setAiModelApiKey] = useState('')
|
const [aiModelApiKey, setAiModelApiKey] = useState('')
|
||||||
const [aiModelApiModel, setAiModelApiModel] = useState('gpt-4o-mini')
|
const [aiModelApiModel, setAiModelApiModel] = useState('gpt-4o-mini')
|
||||||
|
const [aiModelApiMaxTokens, setAiModelApiMaxTokens] = useState(200)
|
||||||
const [aiInsightSilenceDays, setAiInsightSilenceDays] = useState(3)
|
const [aiInsightSilenceDays, setAiInsightSilenceDays] = useState(3)
|
||||||
const [aiInsightAllowContext, setAiInsightAllowContext] = useState(false)
|
const [aiInsightAllowContext, setAiInsightAllowContext] = useState(false)
|
||||||
const [isTestingInsight, setIsTestingInsight] = useState(false)
|
const [isTestingInsight, setIsTestingInsight] = useState(false)
|
||||||
@@ -527,6 +528,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const savedAiModelApiBaseUrl = await configService.getAiModelApiBaseUrl()
|
const savedAiModelApiBaseUrl = await configService.getAiModelApiBaseUrl()
|
||||||
const savedAiModelApiKey = await configService.getAiModelApiKey()
|
const savedAiModelApiKey = await configService.getAiModelApiKey()
|
||||||
const savedAiModelApiModel = await configService.getAiModelApiModel()
|
const savedAiModelApiModel = await configService.getAiModelApiModel()
|
||||||
|
const savedAiModelApiMaxTokens = await configService.getAiModelApiMaxTokens()
|
||||||
const savedAiInsightSilenceDays = await configService.getAiInsightSilenceDays()
|
const savedAiInsightSilenceDays = await configService.getAiInsightSilenceDays()
|
||||||
const savedAiInsightAllowContext = await configService.getAiInsightAllowContext()
|
const savedAiInsightAllowContext = await configService.getAiInsightAllowContext()
|
||||||
const savedAiInsightWhitelistEnabled = await configService.getAiInsightWhitelistEnabled()
|
const savedAiInsightWhitelistEnabled = await configService.getAiInsightWhitelistEnabled()
|
||||||
@@ -550,6 +552,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
setAiModelApiBaseUrl(savedAiModelApiBaseUrl)
|
setAiModelApiBaseUrl(savedAiModelApiBaseUrl)
|
||||||
setAiModelApiKey(savedAiModelApiKey)
|
setAiModelApiKey(savedAiModelApiKey)
|
||||||
setAiModelApiModel(savedAiModelApiModel)
|
setAiModelApiModel(savedAiModelApiModel)
|
||||||
|
setAiModelApiMaxTokens(savedAiModelApiMaxTokens)
|
||||||
setAiInsightSilenceDays(savedAiInsightSilenceDays)
|
setAiInsightSilenceDays(savedAiInsightSilenceDays)
|
||||||
setAiInsightAllowContext(savedAiInsightAllowContext)
|
setAiInsightAllowContext(savedAiInsightAllowContext)
|
||||||
setAiInsightWhitelistEnabled(savedAiInsightWhitelistEnabled)
|
setAiInsightWhitelistEnabled(savedAiInsightWhitelistEnabled)
|
||||||
@@ -2841,6 +2844,28 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>通用 Max Tokens</label>
|
||||||
|
<span className="form-hint">
|
||||||
|
设置单次请求的最大输出 token 数量,见解与足迹共享该值。默认 <code>200</code>。
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="field-input"
|
||||||
|
value={aiModelApiMaxTokens}
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
step={1}
|
||||||
|
onChange={(e) => {
|
||||||
|
const parsed = parseInt(e.target.value, 10)
|
||||||
|
const val = Math.min(65535, Math.max(1, Number.isFinite(parsed) ? parsed : 200))
|
||||||
|
setAiModelApiMaxTokens(val)
|
||||||
|
scheduleConfigSave('aiModelApiMaxTokens', () => configService.setAiModelApiMaxTokens(val))
|
||||||
|
}}
|
||||||
|
style={{ width: 260 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>连接测试</label>
|
<label>连接测试</label>
|
||||||
<span className="form-hint">
|
<span className="form-hint">
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const CONFIG_KEYS = {
|
|||||||
AI_MODEL_API_BASE_URL: 'aiModelApiBaseUrl',
|
AI_MODEL_API_BASE_URL: 'aiModelApiBaseUrl',
|
||||||
AI_MODEL_API_KEY: 'aiModelApiKey',
|
AI_MODEL_API_KEY: 'aiModelApiKey',
|
||||||
AI_MODEL_API_MODEL: 'aiModelApiModel',
|
AI_MODEL_API_MODEL: 'aiModelApiModel',
|
||||||
|
AI_MODEL_API_MAX_TOKENS: 'aiModelApiMaxTokens',
|
||||||
AI_INSIGHT_ENABLED: 'aiInsightEnabled',
|
AI_INSIGHT_ENABLED: 'aiInsightEnabled',
|
||||||
AI_INSIGHT_API_BASE_URL: 'aiInsightApiBaseUrl',
|
AI_INSIGHT_API_BASE_URL: 'aiInsightApiBaseUrl',
|
||||||
AI_INSIGHT_API_KEY: 'aiInsightApiKey',
|
AI_INSIGHT_API_KEY: 'aiInsightApiKey',
|
||||||
@@ -1845,6 +1846,21 @@ export async function setAiModelApiModel(model: string): Promise<void> {
|
|||||||
await config.set(CONFIG_KEYS.AI_MODEL_API_MODEL, model)
|
await config.set(CONFIG_KEYS.AI_MODEL_API_MODEL, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAiModelApiMaxTokens(): Promise<number> {
|
||||||
|
const value = await config.get(CONFIG_KEYS.AI_MODEL_API_MAX_TOKENS)
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
||||||
|
return Math.floor(value)
|
||||||
|
}
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAiModelApiMaxTokens(maxTokens: number): Promise<void> {
|
||||||
|
const normalized = Number.isFinite(maxTokens)
|
||||||
|
? Math.min(65535, Math.max(1, Math.floor(maxTokens)))
|
||||||
|
: 200
|
||||||
|
await config.set(CONFIG_KEYS.AI_MODEL_API_MAX_TOKENS, normalized)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAiInsightEnabled(): Promise<boolean> {
|
export async function getAiInsightEnabled(): Promise<boolean> {
|
||||||
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_ENABLED)
|
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_ENABLED)
|
||||||
return value === true
|
return value === true
|
||||||
|
|||||||
Reference in New Issue
Block a user