|
@@ -558,4 +558,285 @@ function convertV8ToGraphV2(runContext, searchResults) {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-module.exports = { convertV8ToGraphV2 };
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 简化版转换:专注于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(' + '),
|
|
|
|
|
+ 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 };
|