Skip to content

Commit b7c0b89

Browse files
committed
feat: Implement chat creation from object nodes, enhancing user interaction with detailed context and lineage information
1 parent dec6225 commit b7c0b89

5 files changed

Lines changed: 336 additions & 3 deletions

File tree

src/renderer/src/components/common/PageLineageDisplay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const PageLineageDisplay: React.FC<PageLineageDisplayProps> = ({
9191
case 'crosstab_to_chat':
9292
return '从交叉分析表生成'
9393
case 'object_to_chat':
94-
return '从对象页面生成'
94+
return '从对象节点生成'
9595
case 'chat_to_object':
9696
return '从聊天页面生成'
9797
default:

src/renderer/src/components/pages/object/ObjectBrowser.tsx

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const ObjectBrowser: React.FC<ObjectBrowserProps> = ({ chatId }) => {
6262
onDelete={() => handleNodeDelete(nodeId)}
6363
onClearChildren={() => handleClearChildren(nodeId)}
6464
onSaveEdit={(id, newValue) => handleSaveEdit(id, newValue)}
65+
onCreateChat={(id, name) => handleCreateChat(id, name)}
6566
/>
6667
),
6768
key: nodeId,
@@ -200,6 +201,230 @@ const ObjectBrowser: React.FC<ObjectBrowserProps> = ({ chatId }) => {
200201
[dispatch, chat.id, nodes, modal]
201202
)
202203

204+
// 获取节点上下文信息
205+
const getNodeContext = useCallback((currentNode: ObjectNodeType) => {
206+
if (!currentNode) return ''
207+
208+
// 获取节点完整信息
209+
const getNodeInformation = (node: ObjectNodeType): string => {
210+
let information = `# 节点 - [${node.name}]\n${node.description || ''}`
211+
212+
// 添加属性信息
213+
if (node.properties && Object.keys(node.properties).length > 0) {
214+
const properties = Object.entries(node.properties).map(([key, value]) => ({
215+
Name: key,
216+
Value: value
217+
}))
218+
information += `\n## 属性列表\n${JSON.stringify(properties)}`
219+
}
220+
221+
// 添加子节点信息
222+
if (node.children && node.children.length > 0) {
223+
const children = node.children
224+
.map((childId) => {
225+
const childNode = nodes[childId]
226+
return childNode
227+
? childNode.description
228+
? {
229+
Name: childNode.name,
230+
Description: childNode.description
231+
}
232+
: {
233+
Name: childNode.name
234+
}
235+
: null
236+
})
237+
.filter(Boolean)
238+
239+
if (children.length > 0) {
240+
information += `\n## 子节点列表\n${JSON.stringify(children)}`
241+
}
242+
}
243+
244+
// 添加引用信息
245+
if (node.references && node.references.length > 0) {
246+
const references = node.references.map((ref) => {
247+
const refNode = nodes[ref.id]
248+
return {
249+
Name: ref.name,
250+
Description: ref.description || '',
251+
Type: ref.type,
252+
Strength: ref.strength,
253+
NodeExists: !!refNode,
254+
NodeDescription: refNode?.description || ''
255+
}
256+
})
257+
information += `\n## 引用关系\n${JSON.stringify(references)}`
258+
}
259+
260+
return information
261+
}
262+
263+
// 从当前节点向上追踪到根节点,构建完整链路
264+
const buildAncestorChain = (node: ObjectNodeType): ObjectNodeType[] => {
265+
const chain: ObjectNodeType[] = []
266+
let current = node
267+
268+
// 向上追踪到根节点
269+
while (current) {
270+
chain.unshift(current)
271+
if (current.parentId && nodes[current.parentId]) {
272+
current = nodes[current.parentId]
273+
} else {
274+
break
275+
}
276+
}
277+
278+
return chain
279+
}
280+
281+
// 获取同级节点
282+
const getSiblings = (node: ObjectNodeType): ObjectNodeType[] => {
283+
if (!node.parentId) return []
284+
const parent = nodes[node.parentId]
285+
if (!parent || !parent.children) return []
286+
return parent.children
287+
.filter((id) => id !== node.id)
288+
.map((id) => nodes[id])
289+
.filter(Boolean)
290+
}
291+
292+
// 获取当前节点的引用信息
293+
const getCurrentReferences = (node: ObjectNodeType) => {
294+
if (!node.references) return []
295+
return node.references.map((ref) => ({
296+
...ref,
297+
referencedNode: nodes[ref.id] || null
298+
}))
299+
}
300+
301+
// 获取引用当前节点的其他节点(反向引用)
302+
const getIncomingReferences = (node: ObjectNodeType) => {
303+
const incomingRefs: Array<{
304+
fromNode: ObjectNodeType
305+
reference: any
306+
}> = []
307+
308+
Object.values(nodes).forEach((otherNode) => {
309+
if (otherNode.id !== node.id && otherNode.references) {
310+
otherNode.references.forEach((ref) => {
311+
if (ref.id === node.id) {
312+
incomingRefs.push({
313+
fromNode: otherNode,
314+
reference: ref
315+
})
316+
}
317+
})
318+
}
319+
})
320+
321+
return incomingRefs
322+
}
323+
324+
const ancestorChain = buildAncestorChain(currentNode)
325+
const siblings = getSiblings(currentNode)
326+
const currentReferences = getCurrentReferences(currentNode)
327+
const incomingReferences = getIncomingReferences(currentNode)
328+
329+
// 构建完整的上下文信息
330+
let contextInfo = '# 完整上下文信息\n\n'
331+
332+
// 添加层级结构信息
333+
contextInfo += '## 节点层级结构\n'
334+
contextInfo += '从根节点到当前节点的完整路径:\n'
335+
ancestorChain.forEach((ancestor, index) => {
336+
const indent = ' '.repeat(index)
337+
contextInfo += `${indent}- ${ancestor.name}`
338+
if (ancestor.description) {
339+
contextInfo += ` (${ancestor.description})`
340+
}
341+
contextInfo += '\n'
342+
})
343+
344+
// 添加每个层级节点的详细信息
345+
contextInfo += '\n## 路径节点详细信息\n'
346+
ancestorChain.forEach((ancestor, index) => {
347+
contextInfo += `\n### 第${index + 1}层节点\n`
348+
contextInfo += getNodeInformation(ancestor)
349+
contextInfo += '\n'
350+
})
351+
352+
// 添加同级节点信息
353+
if (siblings.length > 0) {
354+
contextInfo += '\n## 同级节点信息\n'
355+
siblings.forEach((sibling) => {
356+
contextInfo += `\n### 同级节点 - ${sibling.name}\n`
357+
contextInfo += getNodeInformation(sibling)
358+
contextInfo += '\n'
359+
})
360+
} else {
361+
contextInfo += '\n## 同级节点信息\n无同级节点\n'
362+
}
363+
364+
// 添加引用关系信息
365+
if (currentReferences.length > 0) {
366+
contextInfo += '\n## 当前节点的引用关系\n'
367+
currentReferences.forEach((ref) => {
368+
contextInfo += `\n### 引用节点 - ${ref.name}\n`
369+
contextInfo += `- **引用类型**: ${ref.type}\n`
370+
contextInfo += `- **引用强度**: ${ref.strength}\n`
371+
if (ref.description) {
372+
contextInfo += `- **引用描述**: ${ref.description}\n`
373+
}
374+
if (ref.referencedNode) {
375+
contextInfo += `- **节点存在**: 是\n`
376+
contextInfo += `- **节点描述**: ${ref.referencedNode.description || '无'}\n`
377+
} else {
378+
contextInfo += `- **节点存在**: 否(可能已被删除)\n`
379+
}
380+
contextInfo += '\n'
381+
})
382+
} else {
383+
contextInfo += '\n## 当前节点的引用关系\n当前节点无引用其他节点\n'
384+
}
385+
386+
// 添加反向引用信息
387+
if (incomingReferences.length > 0) {
388+
contextInfo += '\n## 被其他节点引用的情况\n'
389+
incomingReferences.forEach((incomingRef) => {
390+
contextInfo += `\n### 来自节点 - ${incomingRef.fromNode.name}\n`
391+
contextInfo += `- **引用类型**: ${incomingRef.reference.type}\n`
392+
contextInfo += `- **引用强度**: ${incomingRef.reference.strength}\n`
393+
if (incomingRef.reference.description) {
394+
contextInfo += `- **引用描述**: ${incomingRef.reference.description}\n`
395+
}
396+
contextInfo += `- **来源节点描述**: ${incomingRef.fromNode.description || '无'}\n`
397+
contextInfo += '\n'
398+
})
399+
} else {
400+
contextInfo += '\n## 被其他节点引用的情况\n当前节点未被其他节点引用\n'
401+
}
402+
403+
return contextInfo
404+
}, [nodes])
405+
406+
// 处理创建对话
407+
const handleCreateChat = useCallback(
408+
(nodeId: string, nodeName: string) => {
409+
const node = nodes[nodeId]
410+
if (!node) return
411+
412+
const nodeContext = getNodeContext(node)
413+
414+
dispatch({
415+
type: 'CREATE_CHAT_FROM_OBJECT_NODE',
416+
payload: {
417+
folderId: chat.folderId,
418+
nodeId,
419+
nodeName,
420+
nodeContext,
421+
sourcePageId: chatId
422+
}
423+
})
424+
},
425+
[dispatch, chat.folderId, chatId, nodes, getNodeContext]
426+
)
427+
203428
// 搜索处理
204429
const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
205430
const query = e.target.value

src/renderer/src/components/pages/object/ObjectTreeNode.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
EditOutlined,
55
DeleteOutlined,
66
ClearOutlined,
7-
MoreOutlined
7+
MoreOutlined,
8+
CommentOutlined
89
} from '@ant-design/icons'
910
import type { MenuProps } from 'antd'
1011
import { ObjectNode as ObjectNodeType } from '../../../types'
@@ -19,6 +20,7 @@ interface ObjectTreeNodeProps {
1920
onDelete?: () => void
2021
onClearChildren?: () => void
2122
onSaveEdit?: (nodeId: string, newValue: string) => void
23+
onCreateChat?: (nodeId: string, nodeName: string) => void
2224
}
2325

2426
export default function ObjectTreeNode({
@@ -28,7 +30,8 @@ export default function ObjectTreeNode({
2830
onEdit,
2931
onDelete,
3032
onClearChildren,
31-
onSaveEdit
33+
onSaveEdit,
34+
onCreateChat
3235
}: ObjectTreeNodeProps) {
3336
const [isHovered, setIsHovered] = useState(false)
3437
const [isInlineEditing, setIsInlineEditing] = useState(false)
@@ -74,6 +77,18 @@ export default function ObjectTreeNode({
7477
}
7578
]
7679

80+
if (onCreateChat) {
81+
items.push({
82+
key: 'chat',
83+
label: '创建对话',
84+
icon: <CommentOutlined />,
85+
onClick: (e) => {
86+
e?.domEvent?.stopPropagation()
87+
onCreateChat(node.id, node.name)
88+
}
89+
})
90+
}
91+
7792
if (!isRoot) {
7893
items.push({
7994
key: 'delete',

src/renderer/src/store/reducers/chatReducer.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,89 @@ ${cellContent}
160160
}
161161
}
162162

163+
case 'CREATE_CHAT_FROM_OBJECT_NODE': {
164+
const { folderId, nodeId, nodeName, nodeContext, sourcePageId } = action.payload
165+
166+
// 构建用户提示词
167+
const prompt = `# 基于对象节点的深度分析
168+
169+
## 节点信息
170+
- **节点名称**: ${nodeName}
171+
- **节点ID**: ${nodeId}
172+
173+
## 节点上下文
174+
${nodeContext}
175+
176+
## 请求
177+
请基于以上节点信息和上下文进行深度分析,你可以:
178+
1. 详细解释这个节点的含义、作用和在整个对象结构中的位置
179+
2. 分析节点的层级关系、属性特征和引用关系
180+
3. 基于上下文信息提供相关的扩展见解和建议
181+
4. 探讨可能的改进方向、相关问题或进一步的发展方向
182+
183+
请开始你的分析:`
184+
185+
// 生成聊天标题
186+
const chatTitle = `${nodeName} - 深度分析`
187+
188+
// 创建用户消息
189+
const userMessage = {
190+
id: uuidv4(),
191+
role: 'user' as const,
192+
content: prompt,
193+
timestamp: Date.now()
194+
}
195+
196+
// 创建溯源信息
197+
const lineage = {
198+
source: 'object_to_chat' as const,
199+
sourcePageId,
200+
sourceContext: {
201+
customContext: {
202+
nodeId,
203+
nodeName,
204+
context: nodeContext
205+
}
206+
},
207+
generatedPageIds: [],
208+
generatedAt: Date.now(),
209+
description: `从对象页面的节点 "${nodeName}" 生成的深度分析聊天`
210+
}
211+
212+
// 创建并打开新的普通聊天窗口
213+
const newChat = createNewChat(chatTitle, folderId, lineage)
214+
const regularChat = newChat as RegularChat
215+
regularChat.messages = [userMessage]
216+
regularChat.currentPath = [userMessage.id]
217+
218+
const newOpenTabs = state.openTabs.includes(newChat.id)
219+
? state.openTabs
220+
: [...state.openTabs, newChat.id]
221+
222+
// 更新源页面的generatedPageIds
223+
const updatedPages = state.pages.map((page) => {
224+
if (page.id === sourcePageId && page.lineage) {
225+
return {
226+
...page,
227+
lineage: {
228+
...page.lineage,
229+
generatedPageIds: [...page.lineage.generatedPageIds, newChat.id]
230+
}
231+
}
232+
}
233+
return page
234+
})
235+
236+
return {
237+
...state,
238+
pages: [...updatedPages, newChat],
239+
openTabs: newOpenTabs,
240+
activeTabId: newChat.id,
241+
selectedNodeId: newChat.id,
242+
selectedNodeType: 'chat'
243+
}
244+
}
245+
163246
case 'CREATE_CROSSTAB_CHAT': {
164247
const newCrosstabChat = createNewCrosstabChat(
165248
action.payload.title,

0 commit comments

Comments
 (0)