上傳檔案到「modules/story-summary」
This commit is contained in:
@@ -21,6 +21,10 @@
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.confirm-modal-box {
|
||||||
|
max-width: 440px;
|
||||||
|
}
|
||||||
|
|
||||||
.fact-group {
|
.fact-group {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,8 +358,8 @@
|
|||||||
postMsg('ANCHOR_GENERATE');
|
postMsg('ANCHOR_GENERATE');
|
||||||
};
|
};
|
||||||
|
|
||||||
$('btn-anchor-clear').onclick = () => {
|
$('btn-anchor-clear').onclick = async () => {
|
||||||
if (confirm('清空所有记忆锚点?(L0 向量也会一并清除)')) {
|
if (await showConfirm('清空锚点', '清空所有记忆锚点?(L0 向量也会一并清除)')) {
|
||||||
postMsg('ANCHOR_CLEAR');
|
postMsg('ANCHOR_CLEAR');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -375,6 +375,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$('btn-test-vector-api').onclick = () => {
|
$('btn-test-vector-api').onclick = () => {
|
||||||
|
saveConfig(); // 先保存新 Key 到 localStorage
|
||||||
postMsg('VECTOR_TEST_ONLINE', {
|
postMsg('VECTOR_TEST_ONLINE', {
|
||||||
provider: 'siliconflow',
|
provider: 'siliconflow',
|
||||||
config: {
|
config: {
|
||||||
@@ -391,8 +392,10 @@
|
|||||||
postMsg('VECTOR_GENERATE', { config: getVectorConfig() });
|
postMsg('VECTOR_GENERATE', { config: getVectorConfig() });
|
||||||
};
|
};
|
||||||
|
|
||||||
$('btn-clear-vectors').onclick = () => {
|
$('btn-clear-vectors').onclick = async () => {
|
||||||
if (confirm('确定清空所有向量数据?')) postMsg('VECTOR_CLEAR');
|
if (await showConfirm('清空向量', '确定清空所有向量数据?')) {
|
||||||
|
postMsg('VECTOR_CLEAR');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$('btn-cancel-vectors').onclick = () => postMsg('VECTOR_CANCEL_GENERATE');
|
$('btn-cancel-vectors').onclick = () => postMsg('VECTOR_CANCEL_GENERATE');
|
||||||
@@ -955,6 +958,43 @@
|
|||||||
postMsg('FULLSCREEN_CLOSED');
|
postMsg('FULLSCREEN_CLOSED');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示通用确认弹窗
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
function showConfirm(title, message, okText = '执行', cancelText = '取消') {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const modal = $('confirm-modal');
|
||||||
|
const titleEl = $('confirm-title');
|
||||||
|
const msgEl = $('confirm-message');
|
||||||
|
const okBtn = $('confirm-ok');
|
||||||
|
const cancelBtn = $('confirm-cancel');
|
||||||
|
const closeBtn = $('confirm-close');
|
||||||
|
const backdrop = $('confirm-backdrop');
|
||||||
|
|
||||||
|
titleEl.textContent = title;
|
||||||
|
msgEl.textContent = message;
|
||||||
|
okBtn.textContent = okText;
|
||||||
|
cancelBtn.textContent = cancelText;
|
||||||
|
|
||||||
|
const close = (result) => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
okBtn.onclick = null;
|
||||||
|
cancelBtn.onclick = null;
|
||||||
|
closeBtn.onclick = null;
|
||||||
|
backdrop.onclick = null;
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
okBtn.onclick = () => close(true);
|
||||||
|
cancelBtn.onclick = () => close(false);
|
||||||
|
closeBtn.onclick = () => close(false);
|
||||||
|
backdrop.onclick = () => close(false);
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderArcsEditor(arcs) {
|
function renderArcsEditor(arcs) {
|
||||||
const list = arcs?.length ? arcs : [{ name: '', trajectory: '', progress: 0, moments: [] }];
|
const list = arcs?.length ? arcs : [{ name: '', trajectory: '', progress: 0, moments: [] }];
|
||||||
const es = $('editor-struct');
|
const es = $('editor-struct');
|
||||||
@@ -1526,7 +1566,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Main actions
|
// Main actions
|
||||||
$('btn-clear').onclick = () => postMsg('REQUEST_CLEAR');
|
$('btn-clear').onclick = async () => {
|
||||||
|
if (await showConfirm('清空数据', '确定要清空本聊天的所有总结、关键词及人物关系数据吗?此操作不可撤销。')) {
|
||||||
|
postMsg('REQUEST_CLEAR');
|
||||||
|
}
|
||||||
|
};
|
||||||
$('btn-generate').onclick = () => {
|
$('btn-generate').onclick = () => {
|
||||||
const btn = $('btn-generate');
|
const btn = $('btn-generate');
|
||||||
if (!localGenerating) {
|
if (!localGenerating) {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.confirm-modal-box {
|
||||||
|
max-width: 440px;
|
||||||
|
}
|
||||||
|
|
||||||
.fact-group {
|
.fact-group {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -833,6 +833,28 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
||||||
<script src="story-summary-ui.js"></script>
|
<script src="story-summary-ui.js"></script>
|
||||||
|
<!-- Confirm Modal -->
|
||||||
|
<div class="modal" id="confirm-modal">
|
||||||
|
<div class="modal-bg" id="confirm-backdrop"></div>
|
||||||
|
<div class="modal-box confirm-modal-box">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h2 id="confirm-title">确认操作</h2>
|
||||||
|
<button class="modal-close" id="confirm-close">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" />
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="confirm-message" style="margin: 10px 0; line-height: 1.6; color: var(--fg);">内容</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button class="btn" id="confirm-cancel">取消</button>
|
||||||
|
<button class="btn btn-del" id="confirm-ok">执行</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -449,6 +449,34 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
await clearStateVectors(chatId);
|
await clearStateVectors(chatId);
|
||||||
await updateMeta(chatId, { lastChunkFloor: -1, fingerprint });
|
await updateMeta(chatId, { lastChunkFloor: -1, fingerprint });
|
||||||
|
|
||||||
|
// Helper to embed with retry
|
||||||
|
const embedWithRetry = async (texts, phase, currentBatchIdx, totalItems) => {
|
||||||
|
while (true) {
|
||||||
|
if (vectorCancelled) return null;
|
||||||
|
try {
|
||||||
|
return await embed(texts, vectorCfg, { signal: vectorAbortController.signal });
|
||||||
|
} catch (e) {
|
||||||
|
if (e?.name === "AbortError" || vectorCancelled) return null;
|
||||||
|
xbLog.error(MODULE_ID, `${phase} 向量化单次失败`, e);
|
||||||
|
|
||||||
|
// 等待 60 秒重试
|
||||||
|
const waitSec = 60;
|
||||||
|
for (let s = waitSec; s > 0; s--) {
|
||||||
|
if (vectorCancelled) return null;
|
||||||
|
postToFrame({
|
||||||
|
type: "VECTOR_GEN_PROGRESS",
|
||||||
|
phase,
|
||||||
|
current: currentBatchIdx,
|
||||||
|
total: totalItems,
|
||||||
|
message: `触发限流,${s}s 后重试...`
|
||||||
|
});
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
}
|
||||||
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase, current: currentBatchIdx, total: totalItems, message: "正在重试..." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const atoms = getStateAtoms();
|
const atoms = getStateAtoms();
|
||||||
if (!atoms.length) {
|
if (!atoms.length) {
|
||||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: 0, total: 0, message: "L0 为空,跳过" });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: 0, total: 0, message: "L0 为空,跳过" });
|
||||||
@@ -462,11 +490,14 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
const batch = atoms.slice(i, i + batchSize);
|
const batch = atoms.slice(i, i + batchSize);
|
||||||
const semTexts = batch.map(a => a.semantic);
|
const semTexts = batch.map(a => a.semantic);
|
||||||
const rTexts = batch.map(a => buildRAggregateText(a));
|
const rTexts = batch.map(a => buildRAggregateText(a));
|
||||||
try {
|
|
||||||
const vectors = await embed(semTexts.concat(rTexts), vectorCfg, { signal: vectorAbortController.signal });
|
const vectors = await embedWithRetry(semTexts.concat(rTexts), "L0", l0Completed, atoms.length);
|
||||||
|
if (!vectors) break; // cancelled
|
||||||
|
|
||||||
const split = semTexts.length;
|
const split = semTexts.length;
|
||||||
if (!Array.isArray(vectors) || vectors.length < split * 2) {
|
if (!Array.isArray(vectors) || vectors.length < split * 2) {
|
||||||
throw new Error(`embed length mismatch: expect>=${split * 2}, got=${vectors?.length || 0}`);
|
xbLog.error(MODULE_ID, `embed长度不匹配: expect>=${split * 2}, got=${vectors?.length || 0}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
const semVectors = vectors.slice(0, split);
|
const semVectors = vectors.slice(0, split);
|
||||||
const rVectors = vectors.slice(split, split + split);
|
const rVectors = vectors.slice(split, split + split);
|
||||||
@@ -479,12 +510,6 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
await saveStateVectors(chatId, items, fingerprint);
|
await saveStateVectors(chatId, items, fingerprint);
|
||||||
l0Completed += batch.length;
|
l0Completed += batch.length;
|
||||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: l0Completed, total: atoms.length });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: l0Completed, total: atoms.length });
|
||||||
} catch (e) {
|
|
||||||
if (e?.name === "AbortError") break;
|
|
||||||
xbLog.error(MODULE_ID, "L0 向量化失败", e);
|
|
||||||
vectorCancelled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,8 +541,10 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
const batch = allChunks.slice(i, i + batchSize);
|
const batch = allChunks.slice(i, i + batchSize);
|
||||||
const texts = batch.map(c => c.text);
|
const texts = batch.map(c => c.text);
|
||||||
try {
|
|
||||||
const vectors = await embed(texts, vectorCfg, { signal: vectorAbortController.signal });
|
const vectors = await embedWithRetry(texts, "L1", l1Completed, allChunks.length);
|
||||||
|
if (!vectors) break; // cancelled
|
||||||
|
|
||||||
const items = batch.map((c, j) => ({
|
const items = batch.map((c, j) => ({
|
||||||
chunkId: c.chunkId,
|
chunkId: c.chunkId,
|
||||||
vector: vectors[j],
|
vector: vectors[j],
|
||||||
@@ -526,12 +553,6 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
l1Vectors = l1Vectors.concat(items);
|
l1Vectors = l1Vectors.concat(items);
|
||||||
l1Completed += batch.length;
|
l1Completed += batch.length;
|
||||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: l1Completed, total: allChunks.length });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: l1Completed, total: allChunks.length });
|
||||||
} catch (e) {
|
|
||||||
if (e?.name === "AbortError") break;
|
|
||||||
xbLog.error(MODULE_ID, "L1 向量化失败", e);
|
|
||||||
vectorCancelled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,8 +576,10 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
const batch = l2Pairs.slice(i, i + batchSize);
|
const batch = l2Pairs.slice(i, i + batchSize);
|
||||||
const texts = batch.map(p => p.text);
|
const texts = batch.map(p => p.text);
|
||||||
try {
|
|
||||||
const vectors = await embed(texts, vectorCfg, { signal: vectorAbortController.signal });
|
const vectors = await embedWithRetry(texts, "L2", l2Completed, l2Pairs.length);
|
||||||
|
if (!vectors) break; // cancelled
|
||||||
|
|
||||||
const items = batch.map((p, idx) => ({
|
const items = batch.map((p, idx) => ({
|
||||||
eventId: p.id,
|
eventId: p.id,
|
||||||
vector: vectors[idx],
|
vector: vectors[idx],
|
||||||
@@ -564,12 +587,6 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
await saveEventVectorsToDb(chatId, items, fingerprint);
|
await saveEventVectorsToDb(chatId, items, fingerprint);
|
||||||
l2Completed += batch.length;
|
l2Completed += batch.length;
|
||||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: l2Completed, total: l2Pairs.length });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: l2Completed, total: l2Pairs.length });
|
||||||
} catch (e) {
|
|
||||||
if (e?.name === "AbortError") break;
|
|
||||||
xbLog.error(MODULE_ID, "L2 向量化失败", e);
|
|
||||||
vectorCancelled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user