/** * 将 v6.1.2.8 的 run_context.json 转换成按 Round > 步骤 > 数据 组织的图结构 */ function convertV8ToGraphV2(runContext, searchResults) { const nodes = {}; const edges = []; const iterations = {}; const o = runContext.o || '原始问题'; const rounds = runContext.rounds || []; // 添加原始问题根节点 const rootId = 'root_o'; nodes[rootId] = { type: 'root', query: o, level: 0, relevance_score: 1.0, strategy: '原始问题', iteration: 0, is_selected: true }; iterations[0] = [rootId]; // 处理每一轮 rounds.forEach((round, roundIndex) => { if (round.type === 'initialization') { // Round 0: 初始化阶段 const roundNum = 0; const roundId = `round_${roundNum}`; // 创建 Round 节点 nodes[roundId] = { type: 'round', query: `Round ${roundNum} (初始化)`, level: roundNum, relevance_score: 0, strategy: '初始化', iteration: roundNum, is_selected: true }; edges.push({ from: rootId, to: roundId, edge_type: 'root_to_round', strategy: '初始化' }); if (!iterations[roundNum]) iterations[roundNum] = []; iterations[roundNum].push(roundId); // 创建分词步骤节点 const segStepId = `step_seg_r${roundNum}`; nodes[segStepId] = { type: 'step', query: `步骤:分词 (${round.seg_list?.length || 0}个分词)`, level: roundNum, relevance_score: 0, strategy: '分词', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: segStepId, edge_type: 'round_to_step', strategy: '分词' }); iterations[roundNum].push(segStepId); // 添加分词结果作为步骤的子节点 round.seg_list?.forEach((seg, segIndex) => { const segId = `seg_${seg.text}_${roundNum}_${segIndex}`; nodes[segId] = { type: 'seg', query: seg.text, level: roundNum + 1, relevance_score: seg.score || 0, evaluationReason: seg.reason || '', strategy: '分词结果', iteration: roundNum, is_selected: true }; edges.push({ from: segStepId, to: segId, edge_type: 'step_to_data', strategy: '分词结果' }); if (!iterations[roundNum + 1]) iterations[roundNum + 1] = []; iterations[roundNum + 1].push(segId); }); } else { // 普通轮次 const roundNum = round.round_num; const roundId = `round_${roundNum}`; // 创建 Round 节点 nodes[roundId] = { type: 'round', query: `Round ${roundNum}`, level: roundNum * 10, // 使用10的倍数作为层级 relevance_score: 0, strategy: `第${roundNum}轮`, iteration: roundNum, is_selected: true }; edges.push({ from: rootId, to: roundId, edge_type: 'root_to_round', strategy: `第${roundNum}轮` }); if (!iterations[roundNum * 10]) iterations[roundNum * 10] = []; iterations[roundNum * 10].push(roundId); // 步骤1: 请求&评估推荐词 if (round.sug_details && Object.keys(round.sug_details).length > 0) { const sugStepId = `step_sug_r${roundNum}`; const totalSugs = Object.values(round.sug_details).reduce((sum, list) => sum + list.length, 0); nodes[sugStepId] = { type: 'step', query: `步骤1: 请求&评估推荐词 (${totalSugs}个)`, level: roundNum * 10 + 1, relevance_score: 0, strategy: '请求&评估推荐词', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: sugStepId, edge_type: 'round_to_step', strategy: '推荐词' }); iterations[roundNum * 10].push(sugStepId); // 为每个 Q 创建节点 Object.keys(round.sug_details).forEach((qText, qIndex) => { // 从q_list_1中查找对应的q获取分数和理由 // Round 0: 从q_list_1查找; Round 1+: 从input_q_list查找 let qData = {}; if (roundNum === 0) { qData = round.q_list_1?.find(q => q.text === qText) || {}; } else { // 从当前轮的input_q_list中查找 qData = round.input_q_list?.find(q => q.text === qText) || {}; } const qId = `q_${qText}_r${roundNum}_${qIndex}`; nodes[qId] = { type: 'q', query: qText, level: roundNum * 10 + 2, relevance_score: qData.score || 0, evaluationReason: qData.reason || '', strategy: 'Query', iteration: roundNum, is_selected: true }; edges.push({ from: sugStepId, to: qId, edge_type: 'step_to_q', strategy: 'Query' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(qId); // 为每个 Q 的 sug 创建节点 const sugs = round.sug_details[qText] || []; sugs.forEach((sug, sugIndex) => { const sugId = `sug_${sug.text}_r${roundNum}_q${qIndex}_${sugIndex}`; nodes[sugId] = { type: 'sug', query: sug.text, level: roundNum * 10 + 3, relevance_score: sug.score || 0, evaluationReason: sug.reason || '', strategy: '推荐词', iteration: roundNum, is_selected: true }; edges.push({ from: qId, to: sugId, edge_type: 'q_to_sug', strategy: '推荐词' }); if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = []; iterations[roundNum * 10 + 3].push(sugId); }); }); } // 步骤2: 筛选并执行搜索 const searchStepId = `step_search_r${roundNum}`; const searchCountText = round.search_count > 0 ? `筛选${round.high_score_sug_count}个高分词,搜索${round.search_count}次,${round.total_posts}个帖子` : `无高分推荐词,未执行搜索`; nodes[searchStepId] = { type: 'step', query: `步骤2: 筛选并执行搜索 (${searchCountText})`, level: roundNum * 10 + 1, relevance_score: 0, strategy: '筛选并执行搜索', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: searchStepId, edge_type: 'round_to_step', strategy: '搜索' }); iterations[roundNum * 10].push(searchStepId); // 只有在有搜索结果时才添加搜索词和帖子 // 优先使用 round.search_results(新格式),否则使用外部传入的 searchResults(兼容旧版本) const roundSearchResults = round.search_results || searchResults; if (round.search_count > 0 && roundSearchResults) { if (Array.isArray(roundSearchResults)) { roundSearchResults.forEach((search, searchIndex) => { const searchWordId = `search_${search.text}_r${roundNum}_${searchIndex}`; nodes[searchWordId] = { type: 'search_word', query: search.text, level: roundNum * 10 + 2, relevance_score: search.score_with_o || 0, strategy: '搜索词', iteration: roundNum, is_selected: true }; edges.push({ from: searchStepId, to: searchWordId, edge_type: 'step_to_search_word', strategy: '搜索词' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(searchWordId); // 添加帖子 if (search.post_list && search.post_list.length > 0) { search.post_list.forEach((post, postIndex) => { const postId = `post_${post.note_id}_${searchIndex}_${postIndex}`; // 准备图片列表,将URL字符串转换为对象格式供轮播图使用 const imageList = (post.images || []).map(url => ({ image_url: url })); nodes[postId] = { type: 'post', query: post.title, level: roundNum * 10 + 3, relevance_score: 0, strategy: '帖子', iteration: roundNum, is_selected: true, note_id: post.note_id, note_url: post.note_url, body_text: post.body_text || '', images: post.images || [], image_list: imageList, interact_info: post.interact_info || {} }; edges.push({ from: searchWordId, to: postId, edge_type: 'search_word_to_post', strategy: '搜索结果' }); if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = []; iterations[roundNum * 10 + 3].push(postId); }); } }); } } // 步骤3: 加词生成新查询 if (round.add_word_details && Object.keys(round.add_word_details).length > 0) { const addWordStepId = `step_add_r${roundNum}`; const totalAddWords = Object.values(round.add_word_details).reduce((sum, list) => sum + list.length, 0); nodes[addWordStepId] = { type: 'step', query: `步骤3: 加词生成新查询 (${totalAddWords}个)`, level: roundNum * 10 + 1, relevance_score: 0, strategy: '加词生成新查询', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: addWordStepId, edge_type: 'round_to_step', strategy: '加词' }); iterations[roundNum * 10].push(addWordStepId); // 为每个 Seed 创建节点 Object.keys(round.add_word_details).forEach((seedText, seedIndex) => { const seedId = `seed_${seedText}_r${roundNum}_${seedIndex}`; // 查找seed的来源信息 - 从Round 0的seed_list查找基础种子的from_type const round0 = rounds.find(r => r.round_num === 0 || r.type === 'initialization'); const seedInfo = round0?.seed_list?.find(s => s.text === seedText) || {}; const fromType = seedInfo.from_type || 'unknown'; // 根据来源设置strategy let strategy; if (fromType === 'seg') { strategy = '初始分词'; } else if (fromType === 'add') { strategy = '加词'; } else if (fromType === 'sug') { strategy = '调用sug'; } else { strategy = 'Seed'; // 默认灰色 } nodes[seedId] = { type: 'seed', query: seedText, level: roundNum * 10 + 2, relevance_score: 0, strategy: strategy, iteration: roundNum, is_selected: true }; edges.push({ from: addWordStepId, to: seedId, edge_type: 'step_to_seed', strategy: 'Seed' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(seedId); // 为每个 Seed 的组合词创建节点 const combinedWords = round.add_word_details[seedText] || []; combinedWords.forEach((word, wordIndex) => { const wordId = `add_${word.text}_r${roundNum}_seed${seedIndex}_${wordIndex}`; nodes[wordId] = { type: 'add_word', query: word.text, level: roundNum * 10 + 3, relevance_score: word.score || 0, evaluationReason: word.reason || '', strategy: '加词生成', iteration: roundNum, is_selected: true, selected_word: word.selected_word }; edges.push({ from: seedId, to: wordId, edge_type: 'seed_to_add_word', strategy: '组合词' }); if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = []; iterations[roundNum * 10 + 3].push(wordId); }); }); } // 步骤4: 筛选推荐词进入下轮 const filteredSugs = round.output_q_list?.filter(q => q.from === 'sug') || []; if (filteredSugs.length > 0) { const filterStepId = `step_filter_r${roundNum}`; nodes[filterStepId] = { type: 'step', query: `步骤4: 筛选推荐词进入下轮 (${filteredSugs.length}个)`, level: roundNum * 10 + 1, relevance_score: 0, strategy: '筛选推荐词进入下轮', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: filterStepId, edge_type: 'round_to_step', strategy: '筛选' }); iterations[roundNum * 10].push(filterStepId); // 添加筛选出的sug filteredSugs.forEach((sug, sugIndex) => { const sugId = `filtered_sug_${sug.text}_r${roundNum}_${sugIndex}`; nodes[sugId] = { type: 'filtered_sug', query: sug.text, level: roundNum * 10 + 2, relevance_score: sug.score || 0, strategy: '进入下轮', iteration: roundNum, is_selected: true }; edges.push({ from: filterStepId, to: sugId, edge_type: 'step_to_filtered_sug', strategy: '进入下轮' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(sugId); }); } // 步骤5: 构建下一轮 const nextRoundStepId = `step_next_round_r${roundNum}`; const nextQCount = round.output_q_list?.length || 0; const nextSeedCount = round.seed_list_next_size || 0; nodes[nextRoundStepId] = { type: 'step', query: `步骤5: 构建下一轮 (${nextQCount}个查询, ${nextSeedCount}个种子)`, level: roundNum * 10 + 1, relevance_score: 0, strategy: '构建下一轮', iteration: roundNum, is_selected: true }; edges.push({ from: roundId, to: nextRoundStepId, edge_type: 'round_to_step', strategy: '构建下一轮' }); iterations[roundNum * 10].push(nextRoundStepId); // 5.1: 构建下轮查询 if (round.output_q_list && round.output_q_list.length > 0) { const nextQStepId = `step_next_q_r${roundNum}`; nodes[nextQStepId] = { type: 'step', query: `构建下轮查询 (${nextQCount}个)`, level: roundNum * 10 + 2, relevance_score: 0, strategy: '下轮查询', iteration: roundNum, is_selected: true }; edges.push({ from: nextRoundStepId, to: nextQStepId, edge_type: 'step_to_step', strategy: '查询' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(nextQStepId); // 添加下轮查询列表 round.output_q_list.forEach((q, qIndex) => { const nextQId = `next_q_${q.text}_r${roundNum}_${qIndex}`; // 根据来源设置strategy let strategy; if (q.from === 'seg') { strategy = '初始分词'; } else if (q.from === 'add') { strategy = '加词'; } else if (q.from === 'sug') { strategy = '调用sug'; } else { strategy = 'Query'; // 默认 } nodes[nextQId] = { type: 'next_q', query: q.text, level: roundNum * 10 + 3, relevance_score: q.score || 0, evaluationReason: q.reason || '', strategy: strategy, iteration: roundNum, is_selected: true, from_source: q.from }; edges.push({ from: nextQStepId, to: nextQId, edge_type: 'step_to_next_q', strategy: strategy }); if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = []; iterations[roundNum * 10 + 3].push(nextQId); }); } // 5.2: 构建下轮种子(如果有数据的话) if (nextSeedCount > 0 && round.seed_list_next) { const nextSeedStepId = `step_next_seed_r${roundNum}`; nodes[nextSeedStepId] = { type: 'step', query: `构建下轮种子 (${nextSeedCount}个)`, level: roundNum * 10 + 2, relevance_score: 0, strategy: '下轮种子', iteration: roundNum, is_selected: true }; edges.push({ from: nextRoundStepId, to: nextSeedStepId, edge_type: 'step_to_step', strategy: '种子' }); if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = []; iterations[roundNum * 10 + 2].push(nextSeedStepId); // 添加下轮种子列表 round.seed_list_next.forEach((seed, seedIndex) => { const nextSeedId = `next_seed_${seed.text}_r${roundNum}_${seedIndex}`; // 根据来源设置strategy let strategy; if (seed.from === 'seg') { strategy = '初始分词'; } else if (seed.from === 'add') { strategy = '加词'; } else if (seed.from === 'sug') { strategy = '调用sug'; } else { strategy = 'Seed'; // 默认 } nodes[nextSeedId] = { type: 'next_seed', query: seed.text, level: roundNum * 10 + 3, relevance_score: seed.score || 0, strategy: strategy, iteration: roundNum, is_selected: true, from_source: seed.from }; edges.push({ from: nextSeedStepId, to: nextSeedId, edge_type: 'step_to_next_seed', strategy: strategy }); if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = []; iterations[roundNum * 10 + 3].push(nextSeedId); }); } } }); return { nodes, edges, iterations }; } /** * 简化版转换:专注于query和post的演化 * - 合并所有query节点(不区分seg/sug/add_word) * - 合并相同的帖子节点 * - 步骤信息放在边上 * - 隐藏Round/Step节点 */ function convertV8ToGraphSimplified(runContext, searchResults) { const mergedNodes = {}; const edges = []; const iterations = {}; const o = runContext.o || '原始问题'; const rounds = runContext.rounds || []; // 添加原始问题根节点 const rootId = 'root_o'; mergedNodes[rootId] = { type: 'root', query: o, level: 0, relevance_score: 1.0, strategy: '原始问题', iteration: 0, is_selected: true, occurrences: [{round: 0, role: 'root', score: 1.0}] }; iterations[0] = [rootId]; // 用于记录节点之间的演化关系 const queryEvolution = {}; // {text: {occurrences: [], parentTexts: [], childTexts: []}} const postMap = {}; // {note_id: {...}} // 第一遍:收集所有query和post rounds.forEach((round, roundIndex) => { const roundNum = round.round_num || roundIndex; if (round.type === 'initialization') { // Round 0: 收集分词结果 (round.q_list_1 || []).forEach(q => { if (!queryEvolution[q.text]) { queryEvolution[q.text] = { occurrences: [], parentTexts: new Set([o]), // 来自原始问题 childTexts: new Set() }; } queryEvolution[q.text].occurrences.push({ round: roundNum, role: 'segmentation', strategy: '分词', score: q.score, reason: q.reason }); }); } else { // Round 1+ // 收集sug_details (推荐词) Object.entries(round.sug_details || {}).forEach(([parentText, sugs]) => { sugs.forEach(sug => { if (!queryEvolution[sug.text]) { queryEvolution[sug.text] = { occurrences: [], parentTexts: new Set(), childTexts: new Set() }; } queryEvolution[sug.text].occurrences.push({ round: roundNum, role: 'sug', strategy: '调用sug', score: sug.score, reason: sug.reason }); queryEvolution[sug.text].parentTexts.add(parentText); if (queryEvolution[parentText]) { queryEvolution[parentText].childTexts.add(sug.text); } }); }); // 收集add_word_details (加词结果) Object.entries(round.add_word_details || {}).forEach(([seedText, words]) => { words.forEach(word => { if (!queryEvolution[word.text]) { queryEvolution[word.text] = { occurrences: [], parentTexts: new Set(), childTexts: new Set() }; } queryEvolution[word.text].occurrences.push({ round: roundNum, role: 'add_word', strategy: '加词', score: word.score, reason: word.reason, selectedWord: word.selected_word }); queryEvolution[word.text].parentTexts.add(seedText); if (queryEvolution[seedText]) { queryEvolution[seedText].childTexts.add(word.text); } }); }); // 收集搜索结果和帖子 const roundSearchResults = round.search_results || searchResults; if (roundSearchResults && Array.isArray(roundSearchResults)) { roundSearchResults.forEach(search => { const searchText = search.text; // 标记这个query被用于搜索 if (queryEvolution[searchText]) { queryEvolution[searchText].occurrences.push({ round: roundNum, role: 'search', strategy: '执行搜索', score: search.score_with_o, postCount: search.post_list ? search.post_list.length : 0 }); } // 收集帖子 if (search.post_list && search.post_list.length > 0) { search.post_list.forEach(post => { if (!postMap[post.note_id]) { postMap[post.note_id] = { ...post, foundByQueries: new Set(), foundInRounds: new Set() }; } postMap[post.note_id].foundByQueries.add(searchText); postMap[post.note_id].foundInRounds.add(roundNum); // 建立query到post的关系 if (!queryEvolution[searchText].posts) { queryEvolution[searchText].posts = new Set(); } queryEvolution[searchText].posts.add(post.note_id); }); } }); } } }); // 第二遍:创建合并后的节点 Object.entries(queryEvolution).forEach(([text, data]) => { const nodeId = `query_${text}`; // 获取最新的分数 const latestOccurrence = data.occurrences[data.occurrences.length - 1] || {}; const hasSearchResults = data.posts && data.posts.size > 0; mergedNodes[nodeId] = { type: 'query', query: text, level: Math.max(...data.occurrences.map(o => o.round), 0) * 10 + 2, relevance_score: latestOccurrence.score || 0, evaluationReason: latestOccurrence.reason || '', strategy: data.occurrences.map(o => o.strategy).join(' + '), primaryStrategy: latestOccurrence.strategy || '未知', // 添加主要策略字段 iteration: Math.max(...data.occurrences.map(o => o.round), 0), is_selected: true, occurrences: data.occurrences, hasSearchResults: hasSearchResults, postCount: data.posts ? data.posts.size : 0, selectedWord: data.occurrences.find(o => o.selectedWord)?.selectedWord || '' }; // 添加到对应的轮次 const maxRound = Math.max(...data.occurrences.map(o => o.round), 0); const iterKey = maxRound * 10 + 2; if (!iterations[iterKey]) iterations[iterKey] = []; iterations[iterKey].push(nodeId); }); // 创建帖子节点 Object.entries(postMap).forEach(([noteId, post]) => { const postId = `post_${noteId}`; const imageList = (post.images || []).map(url => ({ image_url: url })); mergedNodes[postId] = { type: 'post', query: post.title, level: 100, // 放在最后 relevance_score: 0, strategy: '帖子', iteration: Math.max(...Array.from(post.foundInRounds)), is_selected: true, note_id: post.note_id, note_url: post.note_url, body_text: post.body_text || '', images: post.images || [], image_list: imageList, interact_info: post.interact_info || {}, foundByQueries: Array.from(post.foundByQueries), foundInRounds: Array.from(post.foundInRounds) }; if (!iterations[100]) iterations[100] = []; iterations[100].push(postId); }); // 第三遍:创建边 // 1. 原始问题 -> 分词结果 Object.entries(queryEvolution).forEach(([text, data]) => { const nodeId = `query_${text}`; const segOccurrence = data.occurrences.find(o => o.role === 'segmentation'); if (segOccurrence && data.parentTexts.has(o)) { edges.push({ from: rootId, to: nodeId, edge_type: 'segmentation', strategy: '分词', label: '分词', round: 0 }); } }); // 2. Query演化关系 Object.entries(queryEvolution).forEach(([text, data]) => { const nodeId = `query_${text}`; data.parentTexts.forEach(parentText => { if (parentText === o) return; // 跳过原始问题(已处理) const parentNodeId = `query_${parentText}`; if (!mergedNodes[parentNodeId]) return; // 找到这个演化的策略和轮次 const occurrence = data.occurrences.find(o => o.role === 'sug' || o.role === 'add_word' ); edges.push({ from: parentNodeId, to: nodeId, edge_type: occurrence?.role || 'evolution', strategy: occurrence?.strategy || '演化', label: `${occurrence?.strategy || '演化'} (R${occurrence?.round || 0})`, round: occurrence?.round || 0 }); }); }); // 3. Query -> Post (搜索关系) Object.entries(queryEvolution).forEach(([text, data]) => { const nodeId = `query_${text}`; if (data.posts && data.posts.size > 0) { const searchOccurrence = data.occurrences.find(o => o.role === 'search'); data.posts.forEach(noteId => { const postId = `post_${noteId}`; edges.push({ from: nodeId, to: postId, edge_type: 'search', strategy: '搜索', label: `搜索 (${data.posts.size}个帖子)`, round: searchOccurrence?.round || 0 }); }); } }); return { nodes: mergedNodes, edges, iterations }; } module.exports = { convertV8ToGraphV2, convertV8ToGraphSimplified };