feat: expose streaming timeout setting

Change-Id: Ide69c0f0fe208a557024f9f77d83c9d1215391cf
This commit is contained in:
wujunchen 2026-04-26 18:14:43 +08:00
parent 9098de6884
commit 174fa7295d
7 changed files with 67 additions and 27 deletions

54
main.js

File diff suppressed because one or more lines are too long

View file

@ -50,6 +50,7 @@ import {
getApiBaseUrl,
modelForApi,
normalizeSettings,
normalizeStreamingTimeoutMs,
pruneCacheEntries,
} from './src/settings';
import { ParallelReaderSettingTab } from './src/settings-tab';
@ -788,6 +789,8 @@ export const __test = {
modelForApi,
normalizeBatchFolderInput,
normalizeCardsPayload,
normalizeSettings,
normalizeStreamingTimeoutMs,
nextCardIndex,
pruneCacheEntries,
recordBatchProcessed,

View file

@ -146,6 +146,8 @@ export const STRINGS: Record<string, Record<string, string>> = {
clearAllCacheButton: '清除所有缓存',
settingStreamingName: '流式输出',
settingStreamingDesc: '启用后生成时实时显示 LLM 输出进度(仅 OpenAI Chat 和 Anthropic 格式支持)',
settingStreamingTimeoutName: '流式超时 (ms)',
settingStreamingTimeoutDesc: '单次流式请求的超时时间;低于 1000ms 或非法值会恢复默认值',
cmdBatchGenerate: '批量生成对照笔记(当前文件夹)',
batchSelectFolder: '请输入要批量处理的文件夹路径(留空为 Vault 根目录)',
batchFolderPrompt: '文件夹路径',
@ -308,6 +310,9 @@ export const STRINGS: Record<string, Record<string, string>> = {
settingStreamingName: 'Streaming output',
settingStreamingDesc:
'Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)',
settingStreamingTimeoutName: 'Streaming timeout (ms)',
settingStreamingTimeoutDesc:
'Timeout for one streaming request; invalid values or values below 1000ms use the default',
cmdBatchGenerate: 'Batch generate parallel notes (current folder)',
batchSelectFolder: 'Enter the folder path to batch-process (leave blank for Vault root)',
batchFolderPrompt: 'Folder path',

View file

@ -12,6 +12,7 @@ import {
DEFAULT_SETTINGS,
getApiFormat,
getApiPreset,
normalizeStreamingTimeoutMs,
PROMPT_LANGUAGES,
UI_LANGUAGES,
} from './settings';
@ -218,6 +219,19 @@ export class ParallelReaderSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName(tr('settingStreamingTimeoutName'))
.setDesc(tr('settingStreamingTimeoutDesc'))
.addText((t) =>
t
.setPlaceholder(String(DEFAULT_SETTINGS.streamingTimeoutMs))
.setValue(String(this.plugin.settings.streamingTimeoutMs || DEFAULT_SETTINGS.streamingTimeoutMs))
.onChange((v) => {
this.plugin.settings.streamingTimeoutMs = normalizeStreamingTimeoutMs(v);
this.plugin.saveSettingsDebounced();
}),
);
}
new Setting(containerEl)

View file

@ -8,6 +8,7 @@ export const MAX_DOC_CHARS = 100000;
export const PROMPT_VERSION = 2;
export const CACHE_SCHEMA_VERSION = 2;
export const DEFAULT_MAX_CACHE_ENTRIES = 100;
export const MIN_STREAMING_TIMEOUT_MS = 1000;
export const PROMPT_LANGUAGES = {
zh: '中文',
en: 'English',
@ -352,6 +353,7 @@ export function normalizeSettings(settings: PluginSettings): PluginSettings {
settings.minCards = normalizeCardCount(settings.minCards, DEFAULT_SETTINGS.minCards);
settings.maxCards = normalizeCardCount(settings.maxCards, DEFAULT_SETTINGS.maxCards);
if (settings.maxCards < settings.minCards) settings.maxCards = settings.minCards;
settings.streamingTimeoutMs = normalizeStreamingTimeoutMs(settings.streamingTimeoutMs);
if (typeof settings.customSystemPrompt !== 'string') settings.customSystemPrompt = '';
return settings;
}
@ -368,6 +370,12 @@ export function normalizeMaxCacheEntries(value: unknown): number {
return n;
}
export function normalizeStreamingTimeoutMs(value: unknown): number {
const n = Math.floor(Number(value));
if (!Number.isFinite(n) || n < MIN_STREAMING_TIMEOUT_MS) return DEFAULT_SETTINGS.streamingTimeoutMs;
return n;
}
function cacheEntryTime(entry: CacheEntry): number {
const value = entry && (entry.lastAccessedAt || entry.generatedAt || entry.updatedAt);
const timestamp = Date.parse(value || '');

View file

@ -92,6 +92,8 @@ assert.strictEqual(typeof t.shouldConfirmRegenerate, 'function');
assert.strictEqual(typeof t.translate, 'function');
assert.strictEqual(typeof t.updateCardAt, 'function');
assert.strictEqual(typeof t.validateBatchFolderInput, 'function');
assert.strictEqual(typeof t.normalizeSettings, 'function');
assert.strictEqual(typeof t.normalizeStreamingTimeoutMs, 'function');
const baseSettings = {
backend: 'api',

View file

@ -351,6 +351,14 @@ const fp2 = t.generationFingerprint({ ...baseSettings });
assert.strictEqual(fp1, fp2, 'same settings = same fingerprint');
assert.notStrictEqual(fp1, t.generationFingerprint({ ...baseSettings, model: 'other' }), 'different model = different fp');
assert.notStrictEqual(fp1, t.generationFingerprint({ ...baseSettings, apiMaxTokens: 8192 }), 'different tokens = different fp');
assert.strictEqual(t.normalizeStreamingTimeoutMs('30000'), 30000, 'streaming timeout accepts numeric strings');
assert.strictEqual(t.normalizeStreamingTimeoutMs(999), 120000, 'streaming timeout below minimum falls back');
assert.strictEqual(t.normalizeStreamingTimeoutMs('bad'), 120000, 'streaming timeout rejects non-numeric values');
assert.strictEqual(
t.normalizeSettings({ ...baseSettings, streamingTimeoutMs: 500 }).streamingTimeoutMs,
120000,
'normalizeSettings protects invalid streaming timeout values',
);
// cacheEntryMatches
const crypto = require('crypto');