|
@@ -0,0 +1,931 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 将 v6.1.2.8 的 run_context.json 转换成按 Round > 步骤 > 数据 组织的图结构
|
|
|
|
|
+ * v3: 增加 [Q] 和 [SUG] 标识前缀
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+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: '[Q] ' + 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] || [];
|
|
|
|
|
+ const qScore = qData.score || 0; // 获取父Q的得分
|
|
|
|
|
+
|
|
|
|
|
+ sugs.forEach((sug, sugIndex) => {
|
|
|
|
|
+ const sugScore = sug.score || 0;
|
|
|
|
|
+ // 比较得分决定颜色:SUG得分 > Q得分 → 绿色,否则 → 红色
|
|
|
|
|
+ const scoreColor = sugScore > qScore ? '#22c55e' : '#ef4444';
|
|
|
|
|
+
|
|
|
|
|
+ const sugId = `sug_${sug.text}_r${roundNum}_q${qIndex}_${sugIndex}`;
|
|
|
|
|
+ nodes[sugId] = {
|
|
|
|
|
+ type: 'sug',
|
|
|
|
|
+ query: '[SUG] ' + sug.text,
|
|
|
|
|
+ level: roundNum * 10 + 3,
|
|
|
|
|
+ relevance_score: sugScore,
|
|
|
|
|
+ evaluationReason: sug.reason || '',
|
|
|
|
|
+ strategy: '推荐词',
|
|
|
|
|
+ iteration: roundNum,
|
|
|
|
|
+ is_selected: true,
|
|
|
|
|
+ scoreColor: scoreColor, // 新增:用于控制文字颜色
|
|
|
|
|
+ parentQScore: qScore // 新增:保存父Q得分用于调试
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ 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] ' + 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: '[R] ' + 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: seedInfo.score || 0, // 从seedInfo读取种子的得分
|
|
|
|
|
+ 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 wordScore = word.score || 0;
|
|
|
|
|
+ const seedScore = word.seed_score || 0;
|
|
|
|
|
+ // 比较得分决定颜色:组合词得分 > 种子得分 → 绿色,否则 → 红色
|
|
|
|
|
+ const scoreColor = wordScore > seedScore ? '#22c55e' : '#ef4444';
|
|
|
|
|
+
|
|
|
|
|
+ const wordId = `add_${word.text}_r${roundNum}_seed${seedIndex}_${wordIndex}`;
|
|
|
|
|
+ nodes[wordId] = {
|
|
|
|
|
+ type: 'add_word',
|
|
|
|
|
+ query: '[Q] ' + word.text,
|
|
|
|
|
+ level: roundNum * 10 + 3,
|
|
|
|
|
+ relevance_score: wordScore,
|
|
|
|
|
+ evaluationReason: word.reason || '',
|
|
|
|
|
+ strategy: '加词生成',
|
|
|
|
|
+ iteration: roundNum,
|
|
|
|
|
+ is_selected: true,
|
|
|
|
|
+ selected_word: word.selected_word,
|
|
|
|
|
+ seed_score: seedScore, // 原始种子的得分
|
|
|
|
|
+ scoreColor: scoreColor // 用于控制文字颜色
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ 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 sugScore = sug.score || 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试从sug_details中找到这个sug对应的父Q得分
|
|
|
|
|
+ let parentQScore = 0;
|
|
|
|
|
+ if (round.sug_details) {
|
|
|
|
|
+ for (const [qText, sugs] of Object.entries(round.sug_details)) {
|
|
|
|
|
+ const matchingSug = sugs.find(s => s.text === sug.text);
|
|
|
|
|
+ if (matchingSug) {
|
|
|
|
|
+ // 找到父Q的得分
|
|
|
|
|
+ let qData = {};
|
|
|
|
|
+ if (roundNum === 0) {
|
|
|
|
|
+ qData = round.q_list_1?.find(q => q.text === qText) || {};
|
|
|
|
|
+ } else {
|
|
|
|
|
+ qData = round.input_q_list?.find(q => q.text === qText) || {};
|
|
|
|
|
+ }
|
|
|
|
|
+ parentQScore = qData.score || 0;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 比较得分决定颜色:SUG得分 > Q得分 → 绿色,否则 → 红色
|
|
|
|
|
+ const scoreColor = sugScore > parentQScore ? '#22c55e' : '#ef4444';
|
|
|
|
|
+
|
|
|
|
|
+ const sugId = `filtered_sug_${sug.text}_r${roundNum}_${sugIndex}`;
|
|
|
|
|
+ nodes[sugId] = {
|
|
|
|
|
+ type: 'filtered_sug',
|
|
|
|
|
+ query: '[SUG] ' + sug.text,
|
|
|
|
|
+ level: roundNum * 10 + 2,
|
|
|
|
|
+ relevance_score: sugScore,
|
|
|
|
|
+ strategy: '进入下轮',
|
|
|
|
|
+ iteration: roundNum,
|
|
|
|
|
+ is_selected: true,
|
|
|
|
|
+ scoreColor: scoreColor, // 新增:用于控制文字颜色
|
|
|
|
|
+ parentQScore: parentQScore // 新增:保存父Q得分用于调试
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ 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] ' + 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,
|
|
|
|
|
+ seedScore: word.seed_score // 添加原始种子的得分
|
|
|
|
|
+ });
|
|
|
|
|
+ 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: '[Q] ' + 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 || '',
|
|
|
|
|
+ seed_score: data.occurrences.find(o => o.seedScore)?.seedScore // 添加原始种子的得分
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 添加到对应的轮次
|
|
|
|
|
+ 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: '[R] ' + 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 };
|