docs: 更新README文档内容与结构
refactor(useThinkFlow): 改进节点布局算法以支持动态尺寸计算 style(i18n): 修改"deepDive"翻译为"回答"以更准确表达功能
This commit is contained in:
@@ -510,35 +510,112 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
|
||||
/**
|
||||
* 布局:从根节点开始按“横向树形”重新排布节点位置
|
||||
* - 主要用于整理节点过多导致的视觉拥挤
|
||||
* - 动态计算节点宽高与子树高度,确保在节点展开(回答/生图)后仍能整齐排布
|
||||
*/
|
||||
const resetLayout = () => {
|
||||
const rootNode = flowNodes.value.find(n => n.data.type === 'root')
|
||||
if (!rootNode) return
|
||||
|
||||
const nodeGapX = 150 // 节点层级间的横向间距
|
||||
const nodeGapY = 40 // 同级节点间的纵向间距
|
||||
|
||||
/**
|
||||
* 获取节点的实际尺寸,优先使用已测量尺寸,否则使用默认值
|
||||
*/
|
||||
const getNodeSize = (node: any) => ({
|
||||
width: node.dimensions?.width ?? node.measured?.width ?? 280,
|
||||
height: node.dimensions?.height ?? node.measured?.height ?? 180
|
||||
})
|
||||
|
||||
// 存储每个节点及其子树所需的总高度
|
||||
const subtreeHeights = new Map<string, number>()
|
||||
|
||||
/**
|
||||
* 第一遍遍历:递归计算每个节点及其子树占用的总高度
|
||||
*/
|
||||
const calculateSubtreeHeight = (nodeId: string): number => {
|
||||
const node = flowNodes.value.find(n => n.id === nodeId)
|
||||
if (!node || node.hidden) return 0
|
||||
|
||||
const size = getNodeSize(node)
|
||||
const children = flowEdges.value
|
||||
.filter(e => e.source === nodeId)
|
||||
.map(e => e.target)
|
||||
.filter(id => {
|
||||
const childNode = flowNodes.value.find(n => n.id === id)
|
||||
return childNode && !childNode.hidden
|
||||
})
|
||||
|
||||
if (children.length === 0) {
|
||||
subtreeHeights.set(nodeId, size.height)
|
||||
return size.height
|
||||
}
|
||||
|
||||
let childrenTotalHeight = 0
|
||||
children.forEach((childId, index) => {
|
||||
childrenTotalHeight += calculateSubtreeHeight(childId)
|
||||
if (index < children.length - 1) {
|
||||
childrenTotalHeight += nodeGapY
|
||||
}
|
||||
})
|
||||
|
||||
// 节点自身高度与子树高度取较大值
|
||||
const totalHeight = Math.max(size.height, childrenTotalHeight)
|
||||
subtreeHeights.set(nodeId, totalHeight)
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
// 开始计算
|
||||
calculateSubtreeHeight(rootNode.id)
|
||||
|
||||
const visited = new Set<string>()
|
||||
const layoutNode = (nodeId: string, x: number, y: number) => {
|
||||
|
||||
/**
|
||||
* 第二遍遍历:根据计算出的子树高度,递归设置节点位置
|
||||
* @param nodeId 当前节点 ID
|
||||
* @param x 当前起始 X 坐标
|
||||
* @param ySubtreeTop 当前子树顶部的 Y 坐标
|
||||
*/
|
||||
const layoutNode = (nodeId: string, x: number, ySubtreeTop: number) => {
|
||||
if (visited.has(nodeId)) return
|
||||
visited.add(nodeId)
|
||||
|
||||
const node = flowNodes.value.find(n => n.id === nodeId)
|
||||
if (node) {
|
||||
node.position = { x, y }
|
||||
if (!node || node.hidden) return
|
||||
|
||||
const childEdges = flowEdges.value.filter(e => e.source === nodeId)
|
||||
childEdges.forEach((edge, index) => {
|
||||
const offsetX = 450
|
||||
const totalHeight = (childEdges.length - 1) * 280
|
||||
const startY = y - totalHeight / 2
|
||||
const offsetY = index * 280
|
||||
const size = getNodeSize(node)
|
||||
const subtreeHeight = subtreeHeights.get(nodeId) || size.height
|
||||
|
||||
layoutNode(edge.target, x + offsetX, startY + offsetY)
|
||||
// 将节点放置在子树区域的垂直中心
|
||||
node.position = {
|
||||
x,
|
||||
y: ySubtreeTop + (subtreeHeight - size.height) / 2
|
||||
}
|
||||
|
||||
const children = flowEdges.value
|
||||
.filter(e => e.source === nodeId)
|
||||
.map(e => e.target)
|
||||
.filter(id => {
|
||||
const childNode = flowNodes.value.find(n => n.id === id)
|
||||
return childNode && !childNode.hidden
|
||||
})
|
||||
|
||||
if (children.length > 0) {
|
||||
const nextX = x + size.width + nodeGapX
|
||||
let currentY = ySubtreeTop
|
||||
|
||||
children.forEach(childId => {
|
||||
const childSubtreeHeight = subtreeHeights.get(childId) || 0
|
||||
layoutNode(childId, nextX, currentY)
|
||||
currentY += childSubtreeHeight + nodeGapY
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
layoutNode(rootNode.id, 50, 300)
|
||||
// 从根节点开始排版
|
||||
layoutNode(rootNode.id, 50, 100)
|
||||
|
||||
// 延迟执行 fitView 确保位置更新已应用
|
||||
setTimeout(() => {
|
||||
fitView({ padding: 0.2, duration: 800 })
|
||||
}, 100)
|
||||
@@ -990,4 +1067,3 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref<string>
|
||||
expandIdea
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"coreIdea": "Core Idea",
|
||||
"followUp": "Ask a follow-up...",
|
||||
"imgAction": "IMG",
|
||||
"deepDive": "Detail",
|
||||
"deepDive": "Answer",
|
||||
"view": "View",
|
||||
"regenerate": "Regenerate",
|
||||
"mainTitle": "Root Node",
|
||||
|
||||
@@ -72,14 +72,14 @@
|
||||
"coreIdea": "核心想法",
|
||||
"followUp": "输入后续问题...",
|
||||
"imgAction": "生图",
|
||||
"deepDive": "详情",
|
||||
"deepDive": "回答",
|
||||
"view": "查看",
|
||||
"regenerate": "重新生成",
|
||||
"mainTitle": "主节点",
|
||||
"moduleTitle": "子节点"
|
||||
},
|
||||
"prompts": {
|
||||
"system": "你是一个思维发散助手,帮助用户将想法逐层展开,构建思维树。\n\n工作流程:\n1. 用户给出一个初始想法(或选择一个已有节点继续追问)。\n2. 你需要根据【思考上下文路径】(即从根节点到当前节点的思考链路)来理解用户的意图。\n3. 生成 3-5 个更深层或相关维度的子想法。\n4. 每个子想法包含简短名称和极简描述。\n\n返回格式必须为严格 JSON:\n{'{'}\n \"nodes\": [\n {'{'} \"text\": \"子想法1名称\", \"description\": \"一句话描述\" {'}'},\n {'{'} \"text\": \"子想法2名称\", \"description\": \"一句话描述\" {'}'}\n ]\n{'}'}\n\n注意:只返回 JSON,不附加解释。",
|
||||
"system": "你是一个思维发散助手,帮助用户将想法逐层展开,构建思维树。\n\n工作流程:\n1. 用户给出一个初始想法(或选择一个已有节点继续追问)。\n2. 你需要根据【思考上下文路径】(即从根节点到当前节点的思考链路)来理解用户的意图。\n3. 生成 3-5 个更深层或相关维度的子想法。\n4. 每个子想法包含简短名称和简单描述。\n\n返回格式必须为严格 JSON:\n{'{'}\n \"nodes\": [\n {'{'} \"text\": \"子想法1名称\", \"description\": \"一句话描述\" {'}'},\n {'{'} \"text\": \"子想法2名称\", \"description\": \"一句话描述\" {'}'}\n ]\n{'}'}\n\n注意:只返回 JSON,不附加解释。",
|
||||
"image": "这是一张高质量写实摄影风格的图片。上下文(从根到当前的概念链路):{context}。主体/概念:{topic}。详情/特征:{detail}。画面要求:真实材质与细节、准确透视与比例、自然光或电影级布光、干净背景与高级构图、色彩克制,适合作为思维导图的视觉核心。质量:超清、锐利、噪点控制自然、层次分明的景深。严禁:任何文字/水印/Logo;卡通、插画、绘本、手绘、漫画、动漫、3D 渲染感、过度滤镜、变形、模糊、低清。",
|
||||
"continue": "请继续深入发散",
|
||||
"coreIdeaPrefix": "核心想法",
|
||||
@@ -89,4 +89,4 @@
|
||||
"deepDivePrompt": "请针对【{topic}】提供一个深度且详细的解析。要求:\n1. 结构清晰,包含背景、核心原理、关键要素和实际应用。\n2. 语言专业且易懂。\n3. 总字数控制在 300-500 字左右。\n4. 直接输出正文内容,不要包含 JSON 格式,也不要包含任何开场白或结束语。",
|
||||
"summaryPrompt": "你是一个思维总结专家。请根据以下提供的思维导图节点信息(包含核心想法及其发散出的所有子想法),生成一份结构化的全篇总结。\n\n节点数据如下:\n{nodes}\n\n要求:\n1. 提炼出核心主题及其背后的核心逻辑。\n2. 归纳出几个主要的维度或分支方向。\n3. 总结最终达成的洞察或结论。\n4. 语言要精炼、专业,富有启发性。\n5. 直接输出总结内容,不要包含 JSON 格式,不要有任何开场白。"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user