| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124 |
- /**
- * 将 v6.1.2.8 的 run_context.json 转换成按 Round > 步骤 > 数据 组织的图结构
- * v3: 增加 [Q] 和 [SUG] 标识前缀
- */
- // 得分提升阈值:与Python代码保持一致
- const REQUIRED_SCORE_GAIN = 0.02;
- function convertV8ToGraphV2(runContext, searchResults, extractionData) {
- const nodes = {};
- const edges = [];
- const iterations = {};
- extractionData = extractionData || {}; // 默认为空对象
- 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: `初始化-原始检索query分段`,
- level: roundNum * 10,
- relevance_score: 0,
- strategy: '初始化',
- iteration: roundNum,
- is_selected: true
- };
- edges.push({
- from: rootId,
- to: roundId,
- edge_type: 'root_to_round',
- strategy: '初始化'
- });
- if (!iterations[roundNum * 10]) iterations[roundNum * 10] = [];
- iterations[roundNum * 10].push(roundId);
- // 步骤1: 分段
- if (round.segments && round.segments.length > 0) {
- const segStepId = `step_seg_r${roundNum}`;
- nodes[segStepId] = {
- type: 'step',
- query: `步骤1: 分段 (${round.segments.length}个segment)`,
- level: roundNum * 10 + 1,
- relevance_score: 0,
- strategy: '分段',
- iteration: roundNum,
- is_selected: true
- };
- edges.push({
- from: roundId,
- to: segStepId,
- edge_type: 'round_to_step',
- strategy: '分段'
- });
- iterations[roundNum * 10 + 1] = [segStepId];
- // 为每个 Segment 创建节点
- round.segments.forEach((seg, segIndex) => {
- const segId = `segment_${segIndex}_r${roundNum}`;
- nodes[segId] = {
- type: 'segment',
- query: `[${seg.type}] ${seg.text}`,
- level: roundNum * 10 + 2,
- relevance_score: seg.score || 0,
- evaluationReason: seg.reason || '',
- strategy: seg.type,
- iteration: roundNum,
- is_selected: true,
- segment_type: seg.type,
- domain_index: seg.domain_index,
- domain_type: seg.type // 新增:让可视化显示类型而不是D0
- };
- edges.push({
- from: segStepId,
- to: segId,
- edge_type: 'step_to_segment',
- strategy: seg.type
- });
- if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = [];
- iterations[roundNum * 10 + 2].push(segId);
- // 为每个 Word 创建节点
- if (seg.words && seg.words.length > 0) {
- seg.words.forEach((word, wordIndex) => {
- const wordId = `word_${word.text}_seg${segIndex}_${wordIndex}`;
- nodes[wordId] = {
- type: 'word',
- query: word.text,
- level: roundNum * 10 + 3,
- relevance_score: word.score || 0,
- evaluationReason: word.reason || '',
- strategy: 'Word',
- iteration: roundNum,
- is_selected: true
- };
- edges.push({
- from: segId,
- to: wordId,
- edge_type: 'segment_to_word',
- strategy: 'Word'
- });
- if (!iterations[roundNum * 10 + 3]) iterations[roundNum * 10 + 3] = [];
- iterations[roundNum * 10 + 3].push(wordId);
- });
- }
- });
- }
- // 步骤2: 域内组词
- if (round.step2_domain_combinations && round.step2_domain_combinations.length > 0) {
- const combStepId = `step_comb_r${roundNum}`;
- nodes[combStepId] = {
- type: 'step',
- query: `步骤2: 域内组词 (${round.step2_domain_combinations.length}个组合)`,
- level: roundNum * 10 + 1,
- relevance_score: 0,
- strategy: '域内组词',
- iteration: roundNum,
- is_selected: true
- };
- edges.push({
- from: roundId,
- to: combStepId,
- edge_type: 'round_to_step',
- strategy: '域内组词'
- });
- if (!iterations[roundNum * 10 + 1]) iterations[roundNum * 10 + 1] = [];
- iterations[roundNum * 10 + 1].push(combStepId);
- // 为每个域内组合创建节点
- round.step2_domain_combinations.forEach((comb, combIndex) => {
- const combId = `comb_${comb.text}_r${roundNum}_${combIndex}`;
- const sourceWordsStr = comb.source_words ? comb.source_words.map(words => words.join(',')).join(' + ') : '';
- nodes[combId] = {
- type: 'domain_combination',
- query: `${comb.text} ${comb.type_label}`,
- level: roundNum * 10 + 2,
- relevance_score: comb.score || 0,
- evaluationReason: comb.reason || '',
- strategy: '域内组合',
- iteration: roundNum,
- is_selected: true,
- type_label: comb.type_label,
- source_words: comb.source_words,
- source_segment: comb.source_segment,
- domain_index: comb.domain_index
- };
- edges.push({
- from: combStepId,
- to: combId,
- edge_type: 'step_to_comb',
- strategy: '域内组合'
- });
- if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = [];
- iterations[roundNum * 10 + 2].push(combId);
- });
- }
- } else {
- // 普通轮次
- const roundNum = round.round_num;
- const roundId = `round_${roundNum}`;
- // 创建 Round 节点
- nodes[roundId] = {
- type: 'round',
- query: `第${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_queries查找
- let qData = {};
- if (roundNum === 0) {
- qData = round.q_list_1?.find(q => q.text === qText) || {};
- } else {
- // 从当前轮的input_queries中查找
- qData = round.input_queries?.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,
- type_label: qData.type_label || qData.typeLabel || '',
- domain_index: qData.domain_index,
- domain_type: qData.domain_type || ''
- };
- 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得分 + 0.05 → 绿色,否则 → 红色
- const scoreColor = (sugScore >= qScore + REQUIRED_SCORE_GAIN) ? '#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: 域内组词(Round 1+)
- // 兼容旧字段名 domain_combinations_top10
- const domainCombinations = round.domain_combinations || round.domain_combinations_top10 || [];
- if (domainCombinations.length > 0) {
- const combStepId = `step_comb_r${roundNum}`;
- nodes[combStepId] = {
- type: 'step',
- query: roundNum === 1
- ? `步骤2: 域内组合 (${domainCombinations.length}个组合)`
- : `步骤2: 跨${roundNum}个域组合 (${domainCombinations.length}个组合)`,
- level: roundNum * 10 + 1,
- relevance_score: 0,
- strategy: '域内组词',
- iteration: roundNum,
- is_selected: true
- };
- edges.push({
- from: roundId,
- to: combStepId,
- edge_type: 'round_to_step',
- strategy: '域内组词'
- });
- iterations[roundNum * 10].push(combStepId);
- // 为每个域内组合创建节点
- domainCombinations.forEach((comb, combIndex) => {
- const combId = `comb_${comb.text}_r${roundNum}_${combIndex}`;
- const domainsStr = comb.domains ? comb.domains.map(d => `D${d}`).join(',') : '';
- const wordDetails = comb.source_word_details || [];
- const isAboveSources = comb.is_above_source_scores || false;
- const scoreColor = wordDetails.length > 0
- ? (isAboveSources ? '#22c55e' : '#ef4444')
- : null;
- nodes[combId] = {
- type: 'domain_combination',
- query: `${comb.text}`, // 移除 type_label,稍后在UI中单独显示
- level: roundNum * 10 + 2,
- relevance_score: comb.score || 0,
- evaluationReason: comb.reason || '',
- strategy: '域内组合',
- iteration: roundNum,
- is_selected: true,
- type_label: comb.type_label || '',
- source_words: comb.source_words || [],
- from_segments: comb.from_segments || [],
- domains: comb.domains || [],
- domains_str: domainsStr,
- source_word_details: wordDetails,
- source_scores: comb.source_scores || [],
- is_above_sources: isAboveSources,
- max_source_score: comb.max_source_score ?? null,
- scoreColor: scoreColor
- };
- edges.push({
- from: combStepId,
- to: combId,
- edge_type: 'step_to_comb',
- strategy: '域内组合'
- });
- if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = [];
- iterations[roundNum * 10 + 2].push(combId);
- });
- }
- // 步骤3: 筛选并执行搜索
- 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: `步骤3: 筛选并执行搜索 (${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 || {},
- // 附加多模态提取数据
- extraction: extractionData && extractionData[post.note_id] ? extractionData[post.note_id] : null,
- // 评估数据 (V2)
- is_knowledge: post.is_knowledge !== undefined ? post.is_knowledge : null,
- knowledge_reason: post.knowledge_reason || '',
- knowledge_score: post.knowledge_score !== undefined ? post.knowledge_score : null,
- knowledge_level: post.knowledge_level !== undefined ? post.knowledge_level : null,
- knowledge_evaluation: post.knowledge_evaluation || null,
- post_relevance_score: post.relevance_score !== undefined ? post.relevance_score : null,
- relevance_level: post.relevance_level || '',
- relevance_reason: post.relevance_reason || '',
- relevance_conclusion: post.relevance_conclusion || '',
- relevance_evaluation: post.relevance_evaluation || null,
- // 评估数据 (V3)
- is_content_knowledge: post.is_content_knowledge !== undefined ? post.is_content_knowledge : null,
- purpose_score: post.purpose_score !== undefined ? post.purpose_score : null,
- category_score: post.category_score !== undefined ? post.category_score : null,
- final_score: post.final_score !== undefined ? post.final_score : null,
- match_level: post.match_level || '',
- evaluator_version: post.evaluator_version || '',
- content_knowledge_evaluation: post.content_knowledge_evaluation || null,
- purpose_evaluation: post.purpose_evaluation || null,
- category_evaluation: post.category_evaluation || null
- };
- edges.push({
- from: searchWordId,
- to: postId,
- edge_type: 'search_word_to_post',
- strategy: '搜索结果'
- });
- // 如果有提取数据,创建对应的分析节点
- if (extractionData && extractionData[post.note_id]) {
- const analysisId = `analysis_${post.note_id}_${searchIndex}_${postIndex}`;
- nodes[analysisId] = {
- type: 'analysis',
- query: '[AI分析] ' + post.title,
- level: roundNum * 10 + 4,
- relevance_score: 0,
- strategy: 'AI分析',
- iteration: roundNum,
- is_selected: true,
- note_id: post.note_id,
- note_url: post.note_url,
- title: post.title,
- body_text: post.body_text || '',
- interact_info: post.interact_info || {},
- extraction: extractionData[post.note_id],
- image_list: imageList
- };
- edges.push({
- from: postId,
- to: analysisId,
- edge_type: 'post_to_analysis',
- strategy: 'AI分析'
- });
- if (!iterations[roundNum * 10 + 4]) iterations[roundNum * 10 + 4] = [];
- iterations[roundNum * 10 + 4].push(analysisId);
- }
- 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的来源信息和分数 - 动态从正确的轮次查找
- let seedInfo = {};
- if (roundNum === 1) {
- // Round 1:种子来自 Round 0 的 seed_list
- const round0 = rounds.find(r => r.round_num === 0 || r.type === 'initialization');
- seedInfo = round0?.seed_list?.find(s => s.text === seedText) || {};
- } else {
- // Round 2+:种子来自前一轮的 seed_list_next
- const prevRound = rounds.find(r => r.round_num === roundNum - 1);
- seedInfo = prevRound?.seed_list_next?.find(s => s.text === seedText) || {};
- }
- const fromType = seedInfo.from_type || seedInfo.from || '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_queries?.find(q => q.text === qText) || {};
- }
- parentQScore = qData.score || 0;
- break;
- }
- }
- }
- // 比较得分决定颜色:SUG得分 > Q得分 → 绿色,否则 → 红色
- const scoreColor = (sugScore >= parentQScore + REQUIRED_SCORE_GAIN) ? '#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);
- });
- }
- // 步骤4: 构建下一轮(Round 1+)
- const highScoreCombinations = round.high_score_combinations || [];
- const highGainSugs = round.high_gain_sugs || [];
- const nextRoundItems = [...highScoreCombinations, ...highGainSugs];
- if (nextRoundItems.length > 0) {
- const nextRoundStepId = `step_next_round_r${roundNum}`;
- nodes[nextRoundStepId] = {
- type: 'step',
- query: `步骤4: 构建下一轮 (${nextRoundItems.length}个查询)`,
- 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);
- // 创建查询节点
- nextRoundItems.forEach((item, index) => {
- const itemId = `next_round_${item.text}_r${roundNum}_${index}`;
- const isSugItem = item.type === 'sug';
- nodes[itemId] = {
- type: 'next_round_item',
- query: '[Q] ' + item.text,
- level: roundNum * 10 + 2,
- relevance_score: item.score || 0,
- strategy: item.type === 'combination' ? '域内组合' : '高增益SUG',
- iteration: roundNum,
- is_selected: true,
- type_label: item.type_label || '',
- item_type: item.type,
- is_suggestion: isSugItem,
- suggestion_label: isSugItem ? '[suggestion]' : ''
- };
- edges.push({
- from: nextRoundStepId,
- to: itemId,
- edge_type: 'step_to_next_round',
- strategy: item.type === 'combination' ? '域内组合' : 'SUG'
- });
- if (!iterations[roundNum * 10 + 2]) iterations[roundNum * 10 + 2] = [];
- iterations[roundNum * 10 + 2].push(itemId);
- });
- }
- }
- });
- return {
- nodes,
- edges,
- iterations
- };
- }
- /**
- * 简化版转换:专注于query和post的演化
- * - 合并所有query节点(不区分seg/sug/add_word)
- * - 合并相同的帖子节点
- * - 步骤信息放在边上
- * - 隐藏Round/Step节点
- */
- function convertV8ToGraphSimplified(runContext, searchResults, extractionData) {
- const mergedNodes = {};
- const edges = [];
- const iterations = {};
- extractionData = extractionData || {}; // 默认为空对象
- 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,
- type_label: q.type_label || q.typeLabel || ''
- });
- });
- } 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,
- type_label: sug.type_label || sug.typeLabel || ''
- });
- 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, // 添加原始种子的得分
- type_label: word.type_label || word.typeLabel || ''
- });
- 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, // 添加原始种子的得分
- type_label: latestOccurrence.type_label || '' // 使用最新的 type_label
- };
- // 添加到对应的轮次
- 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),
- // 附加多模态提取数据
- extraction: extractionData && extractionData[post.note_id] ? extractionData[post.note_id] : null,
- // 评估数据 (V2)
- is_knowledge: post.is_knowledge !== undefined ? post.is_knowledge : null,
- knowledge_reason: post.knowledge_reason || '',
- post_relevance_score: post.relevance_score !== undefined ? post.relevance_score : null,
- relevance_level: post.relevance_level || '',
- relevance_reason: post.relevance_reason || '',
- // 评估数据 (V3)
- is_content_knowledge: post.is_content_knowledge !== undefined ? post.is_content_knowledge : null,
- purpose_score: post.purpose_score !== undefined ? post.purpose_score : null,
- category_score: post.category_score !== undefined ? post.category_score : null,
- final_score: post.final_score !== undefined ? post.final_score : null,
- match_level: post.match_level || '',
- evaluator_version: post.evaluator_version || '',
- content_knowledge_evaluation: post.content_knowledge_evaluation || null,
- purpose_evaluation: post.purpose_evaluation || null,
- category_evaluation: post.category_evaluation || null
- };
- if (!iterations[100]) iterations[100] = [];
- iterations[100].push(postId);
- // 如果有提取数据,创建对应的分析节点
- if (extractionData && extractionData[post.note_id]) {
- const analysisId = `analysis_${noteId}`;
- mergedNodes[analysisId] = {
- type: 'analysis',
- query: '[AI分析] ' + post.title,
- level: 101,
- relevance_score: 0,
- strategy: 'AI分析',
- iteration: Math.max(...Array.from(post.foundInRounds)),
- is_selected: true,
- note_id: post.note_id,
- note_url: post.note_url,
- title: post.title,
- body_text: post.body_text || '',
- interact_info: post.interact_info || {},
- extraction: extractionData[post.note_id],
- image_list: imageList
- };
- edges.push({
- from: postId,
- to: analysisId,
- edge_type: 'post_to_analysis',
- strategy: 'AI分析',
- label: 'AI分析',
- round: Math.max(...Array.from(post.foundInRounds))
- });
- if (!iterations[101]) iterations[101] = [];
- iterations[101].push(analysisId);
- }
- });
- // 第三遍:创建边
- // 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 };
|