feat: enhance session sync and add voice input feature
- 增强会话同步:确保所有会话完全同步到新账号 - 强制更新 model_provider 字段 - 添加 session_index.jsonl 重建功能 - 确保数据库 UPSERT 逻辑正确处理所有情况 - 新增语音输入功能(voice_cdp.js, voice_inject.js) - 测试脚本和增强模块 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -234,8 +234,12 @@ function buildThreadUpsertSql(records, target, cols) {
|
||||
const updateSet = [];
|
||||
for (const c of desired) {
|
||||
if (c === "id") continue;
|
||||
if (c === "has_user_event") updateSet.push(`${c}=CASE WHEN excluded.${c}=1 THEN 1 ELSE threads.${c} END`);
|
||||
else if (["cwd", "first_user_message", "preview", "thread_source"].includes(c)) {
|
||||
// 【增强】model_provider 强制更新为目标 provider(切账号核心逻辑)
|
||||
if (c === "model_provider") {
|
||||
updateSet.push(`${c}=${sql(target)}`);
|
||||
} else if (c === "has_user_event") {
|
||||
updateSet.push(`${c}=CASE WHEN excluded.${c}=1 THEN 1 ELSE threads.${c} END`);
|
||||
} else if (["cwd", "first_user_message", "preview", "thread_source"].includes(c)) {
|
||||
updateSet.push(`${c}=CASE WHEN COALESCE(excluded.${c},'')<>'' THEN excluded.${c} ELSE threads.${c} END`);
|
||||
} else if (c === "title") {
|
||||
updateSet.push(`${c}=CASE WHEN COALESCE(threads.${c},'')='' OR threads.${c}='(无标题对话)' THEN excluded.${c} ELSE threads.${c} END`);
|
||||
@@ -252,6 +256,7 @@ function buildThreadUpsertSql(records, target, cols) {
|
||||
`ON CONFLICT(id) DO UPDATE SET ${updateSet.join(",")};`
|
||||
);
|
||||
}
|
||||
// 【增强】确保数据库中所有会话的 provider 都更新为目标(兜底逻辑)
|
||||
lines.push(`UPDATE threads SET model_provider=${sql(target)} WHERE COALESCE(model_provider,'')<>${sql(target)};`);
|
||||
lines.push("COMMIT;");
|
||||
return lines.join("\n") + "\n";
|
||||
@@ -288,6 +293,59 @@ function ts() {
|
||||
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
// 【增强】重建 session_index.jsonl:确保所有会话都在索引中(切账号后必要)
|
||||
function rebuildSessionIndex(records, log = () => {}) {
|
||||
if (!records || !records.length) {
|
||||
log("索引重建:无会话记录,跳过");
|
||||
return { rebuilt: 0 };
|
||||
}
|
||||
// 备份旧索引
|
||||
if (fs.existsSync(SESSION_INDEX)) {
|
||||
const backup = SESSION_INDEX + ".bak-" + ts();
|
||||
try {
|
||||
fs.copyFileSync(SESSION_INDEX, backup);
|
||||
log(`已备份旧索引 → ${path.basename(backup)}`);
|
||||
} catch (e) {
|
||||
log(`索引备份失败:${e.message || e}`);
|
||||
}
|
||||
}
|
||||
// 读取现有索引,保留用户重命名的标题
|
||||
const existingTitles = {};
|
||||
try {
|
||||
const lines = fs.readFileSync(SESSION_INDEX, "utf-8").split("\n");
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
const d = JSON.parse(line);
|
||||
if (d.id && d.thread_name) existingTitles[d.id] = d.thread_name;
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
// 重建索引:每个会话一行 JSON
|
||||
const lines = [];
|
||||
for (const r of records) {
|
||||
if (!r.id) continue;
|
||||
const indexEntry = {
|
||||
id: r.id,
|
||||
thread_name: existingTitles[r.id] || r.title || "(无标题对话)",
|
||||
project_path: r.cwd || null,
|
||||
created_at: new Date(r.created_at_ms).toISOString(),
|
||||
source: r.source || "vscode",
|
||||
archived: r.archived ? true : false,
|
||||
};
|
||||
lines.push(JSON.stringify(indexEntry));
|
||||
}
|
||||
// 写入新索引
|
||||
try {
|
||||
fs.writeFileSync(SESSION_INDEX, lines.join("\n") + "\n", "utf-8");
|
||||
log(`索引重建完成:${lines.length} 条会话`);
|
||||
return { rebuilt: lines.length };
|
||||
} catch (e) {
|
||||
log(`索引重建失败:${e.message || e}`);
|
||||
return { rebuilt: 0, error: e.message || String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function syncProviderToCurrent(log = () => {}, onProgress = () => {}) {
|
||||
if (codexRunning()) throw new Error("检测到 Codex 正在运行,请先完全退出 Codex 再同步。");
|
||||
const target = targetProvider();
|
||||
@@ -394,6 +452,11 @@ async function syncProviderToCurrent(log = () => {}, onProgress = () => {}) {
|
||||
}
|
||||
}
|
||||
log(`数据库更新:provider ${provRows} 行、has_user_event ${ueRows} 行、新增线程 ${insertedRows} 行、项目索引 +${projectState.addedProjects}`);
|
||||
|
||||
// 【增强】4) 重建 session_index.jsonl,确保所有会话都在索引中
|
||||
onProgress({ phase: "重建索引", done: 0, total: 0 });
|
||||
const indexResult = rebuildSessionIndex(records, log);
|
||||
|
||||
onProgress({ phase: "完成", done: 0, total: 0 });
|
||||
|
||||
return {
|
||||
@@ -407,6 +470,7 @@ async function syncProviderToCurrent(log = () => {}, onProgress = () => {}) {
|
||||
insertedRows,
|
||||
projectRows: projectState.addedProjects,
|
||||
projectlessRemoved: projectState.removedProjectless,
|
||||
indexRebuilt: indexResult.rebuilt || 0,
|
||||
backup,
|
||||
};
|
||||
}
|
||||
@@ -457,6 +521,11 @@ async function syncProviderToCurrent(log = () => {}, onProgress = () => {}) {
|
||||
}
|
||||
}
|
||||
log(`数据库更新:provider ${provRows} 行、has_user_event ${ueRows} 行、cwd ${cwdRows} 行`);
|
||||
|
||||
// 【增强】4) 重建 session_index.jsonl,确保所有会话都在索引中
|
||||
onProgress({ phase: "重建索引", done: 0, total: 0 });
|
||||
const indexResult = rebuildSessionIndex(records, log);
|
||||
|
||||
onProgress({ phase: "完成", done: 0, total: 0 });
|
||||
|
||||
return {
|
||||
@@ -470,6 +539,7 @@ async function syncProviderToCurrent(log = () => {}, onProgress = () => {}) {
|
||||
insertedRows: 0,
|
||||
projectRows: projectState.addedProjects,
|
||||
projectlessRemoved: projectState.removedProjectless,
|
||||
indexRebuilt: indexResult.rebuilt || 0,
|
||||
backup,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user