Skip to content

Commit 366aa40

Browse files
committed
implement topic retrival
1 parent 9d43508 commit 366aa40

File tree

8 files changed

+759
-40
lines changed

8 files changed

+759
-40
lines changed

electron/index.html

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ <h1 class="splash-title">LoFS</h1>
257257
<div id="chat-page-header">
258258
<div class="chat-page-header-info">
259259
<div class="chat-model-selector">
260-
<button id="chat-model-button" class="chat-model-button" type="button" aria-haspopup="listbox" aria-expanded="false">
260+
<button id="chat-model-button" class="chat-model-button" type="button" aria-haspopup="listbox"
261+
aria-expanded="false">
261262
<span id="chat-model-button-text" class="chat-model-button-text">暂无可用模型</span>
262263
<span class="chat-model-button-icon"></span>
263264
</button>
@@ -280,7 +281,8 @@ <h1 class="splash-title">LoFS</h1>
280281
<textarea id="chat-input" rows="1" placeholder="询问任何问题" autocomplete="off" spellcheck="false"></textarea>
281282
<button id="chat-send-btn" type="button" aria-label="发送消息">
282283
<svg class="icon-arrow" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
283-
<path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
284+
<path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
285+
stroke-linejoin="round" fill="none" />
284286
</svg>
285287
</button>
286288
</div>
@@ -321,30 +323,41 @@ <h2>外观</h2>
321323

322324
<section class="settings-section" id="model-summary-settings">
323325
<div class="settings-section-header">
324-
<h2>文档处理</h2>
326+
<h2>检索</h2>
325327
<span id="model-summary-status" class="settings-status-text" aria-live="polite"></span>
326328
</div>
327329
<div class="settings-divider"></div>
328330
<div class="setting-item setting-item--stack">
329331
<div class="setting-item-text">
330-
<span class="setting-item-title">启用模型主题总结</span>
331-
<p class="setting-item-desc">挂载文档时,使用所选模型生成文档主题概述</p>
332+
<span class="setting-item-title">文档总结</span>
333+
<p class="setting-item-desc">启用后,挂载文档时会使用所选模型自动生成并保存文档主题概述</p>
332334
</div>
333335
<label class="toggle-switch">
334336
<input type="checkbox" id="enable-model-summary-toggle">
335337
<span class="slider"></span>
336338
</label>
337339
</div>
340+
<div class="setting-item setting-item--stack">
341+
<div class="setting-item-text">
342+
<span class="setting-item-title">主题检索</span>
343+
<p class="setting-item-desc">开启后,聊天会优先根据文档主题概述筛选相关文档,若无法匹配则回退至混合检索。</p>
344+
</div>
345+
<label class="toggle-switch">
346+
<input type="checkbox" id="enable-summary-search-toggle">
347+
<span class="slider"></span>
348+
</label>
349+
</div>
338350
<div id="model-summary-hint" class="settings-hint-message"></div>
339-
<div class="setting-item setting-item--column" id="model-summary-selector">
351+
<div class="setting-item setting-item--select-row" id="model-summary-selector">
340352
<label for="model-summary-select" class="settings-input-label">选择模型</label>
341-
<select id="model-summary-select" class="settings-select" aria-describedby="model-summary-hint"></select>
353+
<select id="model-summary-select" class="settings-select settings-select--compact"
354+
aria-describedby="model-summary-hint"></select>
342355
</div>
343356
</section>
344357

345358
<section class="settings-section">
346359
<div class="settings-section-header">
347-
<h2>检索</h2>
360+
<h2>文档处理</h2>
348361
<div class="retrieval-actions">
349362
<span id="retrieval-settings-status" class="settings-status-text" aria-live="polite"></span>
350363
<div class="retrieval-actions-buttons">
@@ -596,7 +609,8 @@ <h2>API-Key</h2>
596609
<div id="github-page" style="display: none;">
597610
<div class="github-section">
598611
<div class="github-content">
599-
<webview id="github-webview" src="https://github.com/Oli51467/local_fs" style="width:100%; height:100%; border:0;"></webview>
612+
<webview id="github-webview" src="https://github.com/Oli51467/local_fs"
613+
style="width:100%; height:100%; border:0;"></webview>
600614
</div>
601615
</div>
602616
</div>
@@ -701,13 +715,13 @@ <h3>图片文件</h3>
701715
(async () => {
702716
const resolveAsset = window.fsAPI && typeof window.fsAPI.getAssetPathSync === 'function'
703717
? (relative) => {
704-
try {
705-
return window.fsAPI.getAssetPathSync(relative) || '';
706-
} catch (error) {
707-
console.warn('解析资源路径失败:', relative, error);
708-
return '';
709-
}
718+
try {
719+
return window.fsAPI.getAssetPathSync(relative) || '';
720+
} catch (error) {
721+
console.warn('解析资源路径失败:', relative, error);
722+
return '';
710723
}
724+
}
711725
: null;
712726

713727
const toFsPath = (maybeFileUrl) => {

electron/src/modules/chat.js

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,19 @@ class ChatModule {
544544
return normalized;
545545
}
546546

547+
shouldUseSummarySearch() {
548+
const settingsModule = window.settingsModule;
549+
if (!settingsModule || typeof settingsModule.isSummarySearchEnabled !== 'function') {
550+
return false;
551+
}
552+
try {
553+
return Boolean(settingsModule.isSummarySearchEnabled());
554+
} catch (error) {
555+
console.warn('读取主题检索配置失败:', error);
556+
return false;
557+
}
558+
}
559+
547560
normalizeModelList(models) {
548561
if (!Array.isArray(models) || models.length === 0) {
549562
return [];
@@ -1940,7 +1953,9 @@ class ChatModule {
19401953
}
19411954

19421955
renderReferenceSection(references, metadata) {
1943-
if (!Array.isArray(references) || !references.length) {
1956+
const summaryCard = this.createSummaryReferenceCard(metadata);
1957+
const referenceList = Array.isArray(references) ? references : [];
1958+
if (!summaryCard && !referenceList.length) {
19441959
return null;
19451960
}
19461961
const section = document.createElement('div');
@@ -1954,7 +1969,11 @@ class ChatModule {
19541969
const list = document.createElement('div');
19551970
list.className = 'chat-reference-list';
19561971

1957-
references.forEach((reference) => {
1972+
if (summaryCard) {
1973+
list.appendChild(summaryCard);
1974+
}
1975+
1976+
referenceList.forEach((reference) => {
19581977
const item = this.createReferenceItem(reference, metadata);
19591978
if (item) {
19601979
list.appendChild(item);
@@ -1969,6 +1988,106 @@ class ChatModule {
19691988
return section;
19701989
}
19711990

1991+
createSummaryReferenceCard(metadata) {
1992+
if (!metadata || typeof metadata !== 'object') {
1993+
return null;
1994+
}
1995+
const useSummarySearch = Boolean(metadata.use_summary_search);
1996+
const retrievalContext = metadata.retrieval_context || {};
1997+
if (!useSummarySearch || retrievalContext.mode !== 'summary' || !retrievalContext.summary_search_applied) {
1998+
return null;
1999+
}
2000+
const rawMatches = Array.isArray(retrievalContext.summary_matches) ? retrievalContext.summary_matches : [];
2001+
if (!rawMatches.length) {
2002+
return null;
2003+
}
2004+
2005+
const matches = rawMatches.map((match, index) => {
2006+
if (!match || typeof match !== 'object') {
2007+
return null;
2008+
}
2009+
const summaryText = typeof match.summary_text === 'string' ? match.summary_text : (match.summary_preview || '');
2010+
return {
2011+
name: (match.filename || '').trim() || `文档-${match.rank || index + 1}`,
2012+
summary: summaryText,
2013+
score: Number.isFinite(match.score) ? Number(match.score) : null,
2014+
vectorScore: Number.isFinite(match.vector_score) ? Number(match.vector_score) : null,
2015+
lexicalScore: Number.isFinite(match.lexical_score) ? Number(match.lexical_score) : null,
2016+
modelName: (match.summary_model_name || '').trim(),
2017+
};
2018+
}).filter(Boolean);
2019+
2020+
if (!matches.length) {
2021+
return null;
2022+
}
2023+
2024+
const card = document.createElement('div');
2025+
card.className = 'chat-reference-item is-summary';
2026+
2027+
const header = document.createElement('div');
2028+
header.className = 'chat-reference-summary-header';
2029+
const title = document.createElement('span');
2030+
title.className = 'chat-reference-summary-title';
2031+
title.textContent = '参考文档主题';
2032+
header.appendChild(title);
2033+
2034+
const summaryMeta = document.createElement('span');
2035+
summaryMeta.className = 'chat-reference-summary-meta';
2036+
const thresholdValue = Number(retrievalContext.summary_threshold);
2037+
const threshold = Number.isFinite(thresholdValue) ? thresholdValue.toFixed(2) : '0.70';
2038+
summaryMeta.textContent = `命中 ${matches.length} 篇 · 阈值 ≥ ${threshold}`;
2039+
header.appendChild(summaryMeta);
2040+
2041+
card.appendChild(header);
2042+
2043+
const list = document.createElement('div');
2044+
list.className = 'chat-reference-summary-list';
2045+
2046+
matches.forEach((match) => {
2047+
const entry = document.createElement('div');
2048+
entry.className = 'chat-reference-summary-item';
2049+
2050+
const nameEl = document.createElement('div');
2051+
nameEl.className = 'chat-reference-summary-name';
2052+
nameEl.textContent = match.name;
2053+
entry.appendChild(nameEl);
2054+
2055+
const metaParts = [];
2056+
if (Number.isFinite(match.score)) {
2057+
metaParts.push(`综合 ${match.score.toFixed(2)}`);
2058+
}
2059+
if (Number.isFinite(match.vectorScore)) {
2060+
metaParts.push(`语义 ${match.vectorScore.toFixed(2)}`);
2061+
}
2062+
if (Number.isFinite(match.lexicalScore)) {
2063+
metaParts.push(`词法 ${match.lexicalScore.toFixed(2)}`);
2064+
}
2065+
if (match.modelName) {
2066+
metaParts.push(`模型 ${match.modelName}`);
2067+
}
2068+
2069+
if (metaParts.length) {
2070+
const metaLine = document.createElement('div');
2071+
metaLine.className = 'chat-reference-summary-submeta';
2072+
metaLine.textContent = metaParts.join(' · ');
2073+
entry.appendChild(metaLine);
2074+
}
2075+
2076+
const textEl = document.createElement('div');
2077+
textEl.className = 'chat-reference-summary-text';
2078+
const summaryContent = match.summary && match.summary.trim()
2079+
? this.buildReferenceSnippet(match.summary.trim())
2080+
: '暂无主题概述内容。';
2081+
textEl.textContent = summaryContent;
2082+
entry.appendChild(textEl);
2083+
2084+
list.appendChild(entry);
2085+
});
2086+
2087+
card.appendChild(list);
2088+
return card;
2089+
}
2090+
19722091
createReferenceItem(reference, metadata) {
19732092
if (!reference || typeof reference !== 'object') {
19742093
return null;
@@ -3016,6 +3135,7 @@ class ChatModule {
30163135
top_k: 5,
30173136
stream: true,
30183137
client_request_id: streamingMessage.id,
3138+
use_summary_search: this.shouldUseSummarySearch(),
30193139
model: {
30203140
source_id: this.selectedModel.sourceId,
30213141
model_id: this.selectedModel.modelId,

electron/src/modules/config-manager.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class ConfigManager extends EventEmitter {
2323
siliconflwApiKey: '',
2424
customModels: [],
2525
enableModelSummary: false,
26-
modelSummarySelection: null
26+
modelSummarySelection: null,
27+
enableSummarySearch: false
2728
};
2829

2930
this.ensureSettingsDirExists();

electron/src/modules/settings-backend.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class SettingsBackendModule {
1616
siliconflwApiKey: '',
1717
customModels: [],
1818
enableModelSummary: false,
19-
modelSummarySelection: null
19+
modelSummarySelection: null,
20+
enableSummarySearch: false
2021
};
2122

2223
// 初始化配置管理器

0 commit comments

Comments
 (0)