docs: 更新README文档内容与结构

refactor(useThinkFlow): 改进节点布局算法以支持动态尺寸计算
style(i18n): 修改"deepDive"翻译为"回答"以更准确表达功能
This commit is contained in:
liuziting
2026-01-22 08:50:54 +08:00
parent c2970c3348
commit e442296757
5 changed files with 248 additions and 271 deletions

View File

@@ -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
}
}

View File

@@ -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",

View File

@@ -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 格式,不要有任何开场白。"
}
}
}