parent
640ca19cba
commit
eba746a3bc
@ -64,6 +64,10 @@
|
||||
"@llm-tools/embedjs-loader-xml": "^0.1.28",
|
||||
"@llm-tools/embedjs-openai": "^0.1.28",
|
||||
"@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||
"@xyflow/react": "^12.4.4",
|
||||
"adm-zip": "^0.5.16",
|
||||
"docx": "^9.0.2",
|
||||
"electron-log": "^5.1.5",
|
||||
|
||||
@ -200,6 +200,25 @@
|
||||
"topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic",
|
||||
"topics.title": "Topics",
|
||||
"topics.unpinned": "Unpinned Topics",
|
||||
"topics.new": "New Topic",
|
||||
"translate": "Translate",
|
||||
"navigation": {
|
||||
"prev": "Previous Message",
|
||||
"next": "Next Message",
|
||||
"first": "Already at the first message",
|
||||
"last": "Already at the last message",
|
||||
"history": "Chat History"
|
||||
},
|
||||
"history": {
|
||||
"title": "Chat History",
|
||||
"coming_soon": "Chat workflow diagram coming soon",
|
||||
"no_messages": "No Messages Found",
|
||||
"start_conversation": "Start a conversation to see the chat flow diagram",
|
||||
"user_node": "User",
|
||||
"assistant_node": "Assistant",
|
||||
"view_full_content": "View Full Content",
|
||||
"click_to_navigate": "Click to navigate to the message"
|
||||
}
|
||||
"translate": "Translate"
|
||||
},
|
||||
"code_block": {
|
||||
|
||||
@ -200,6 +200,25 @@
|
||||
"topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供",
|
||||
"topics.title": "トピック",
|
||||
"topics.unpinned": "固定解除",
|
||||
"topics.new": "新しいトピック",
|
||||
"translate": "翻訳",
|
||||
"navigation": {
|
||||
"prev": "前のメッセージ",
|
||||
"next": "次のメッセージ",
|
||||
"first": "最初のメッセージです",
|
||||
"last": "最後のメッセージです",
|
||||
"history": "チャット履歴"
|
||||
},
|
||||
"history": {
|
||||
"title": "チャット履歴",
|
||||
"coming_soon": "チャットワークフロー図がすぐに登場します",
|
||||
"no_messages": "メッセージが見つかりませんでした",
|
||||
"start_conversation": "チャットを開始してチャットワークフロー図を確認してください",
|
||||
"user_node": "ユーザー",
|
||||
"assistant_node": "アシスタント",
|
||||
"view_full_content": "完全な内容を表示",
|
||||
"click_to_navigate": "メッセージに移動"
|
||||
}
|
||||
"translate": "翻訳"
|
||||
},
|
||||
"code_block": {
|
||||
|
||||
@ -200,6 +200,25 @@
|
||||
"topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы",
|
||||
"topics.title": "Топики",
|
||||
"topics.unpinned": "Открепленные темы",
|
||||
"topics.new": "Новый топик",
|
||||
"translate": "Перевести",
|
||||
"navigation": {
|
||||
"prev": "Предыдущее сообщение",
|
||||
"next": "Следующее сообщение",
|
||||
"first": "Уже первое сообщение",
|
||||
"last": "Уже последнее сообщение",
|
||||
"history": "История чата"
|
||||
},
|
||||
"history": {
|
||||
"title": "История чата",
|
||||
"coming_soon": "График работы чата скоро появится",
|
||||
"no_messages": "Сообщения не найдены",
|
||||
"start_conversation": "Начните диалог, чтобы просмотреть график работы чата",
|
||||
"user_node": "Пользователь",
|
||||
"assistant_node": "Ассистент",
|
||||
"view_full_content": "Показать полное содержимое",
|
||||
"click_to_navigate": "Перейти к сообщению"
|
||||
}
|
||||
"translate": "Перевести"
|
||||
},
|
||||
"code_block": {
|
||||
|
||||
@ -200,6 +200,25 @@
|
||||
"topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词",
|
||||
"topics.title": "话题",
|
||||
"topics.unpinned": "取消固定",
|
||||
"topics.new": "开始新对话",
|
||||
"translate": "翻译",
|
||||
"navigation": {
|
||||
"prev": "上一条消息",
|
||||
"next": "下一条消息",
|
||||
"first": "已经是第一条消息",
|
||||
"last": "已经是最后一条消息",
|
||||
"history": "聊天历史"
|
||||
},
|
||||
"history": {
|
||||
"title": "聊天历史",
|
||||
"coming_soon": "聊天工作流图表即将上线",
|
||||
"no_messages": "没有找到消息",
|
||||
"start_conversation": "开始对话以查看聊天流程图",
|
||||
"user_node": "用户",
|
||||
"assistant_node": "助手",
|
||||
"view_full_content": "查看完整内容",
|
||||
"click_to_navigate": "点击跳转到对应消息"
|
||||
}
|
||||
"translate": "翻译"
|
||||
},
|
||||
"code_block": {
|
||||
|
||||
@ -200,6 +200,25 @@
|
||||
"topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞",
|
||||
"topics.title": "話題",
|
||||
"topics.unpinned": "取消固定",
|
||||
"topics.new": "開始新對話",
|
||||
"translate": "翻譯",
|
||||
"navigation": {
|
||||
"prev": "上一條訊息",
|
||||
"next": "下一條訊息",
|
||||
"first": "已經是第一條訊息",
|
||||
"last": "已經是最後一條訊息",
|
||||
"history": "聊天歷史"
|
||||
},
|
||||
"history": {
|
||||
"title": "聊天歷史",
|
||||
"coming_soon": "聊天工作流圖表即將上線",
|
||||
"no_messages": "沒有找到訊息",
|
||||
"start_conversation": "開始對話以查看聊天流程圖",
|
||||
"user_node": "用戶",
|
||||
"assistant_node": "助手",
|
||||
"view_full_content": "查看完整內容",
|
||||
"click_to_navigate": "點擊跳轉到對應訊息"
|
||||
}
|
||||
"translate": "翻譯"
|
||||
},
|
||||
"code_block": {
|
||||
|
||||
614
src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx
Normal file
614
src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx
Normal file
@ -0,0 +1,614 @@
|
||||
import '@xyflow/react/dist/style.css'
|
||||
|
||||
import { RobotOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { RootState } from '@renderer/store'
|
||||
import { selectTopicMessages } from '@renderer/store/messages'
|
||||
import { Model } from '@renderer/types'
|
||||
import { Controls, Handle, MiniMap, ReactFlow, ReactFlowProvider } from '@xyflow/react'
|
||||
import { Edge, Node, NodeTypes, Position, useEdgesState, useNodesState } from '@xyflow/react'
|
||||
import { Avatar, Spin, Tooltip } from 'antd'
|
||||
import { isEqual } from 'lodash'
|
||||
import { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
||||
// 定义Tooltip相关样式组件
|
||||
const TooltipContent = styled.div`
|
||||
max-width: 300px;
|
||||
`
|
||||
|
||||
const TooltipTitle = styled.div`
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding-bottom: 4px;
|
||||
`
|
||||
|
||||
const TooltipBody = styled.div`
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 8px;
|
||||
white-space: pre-wrap;
|
||||
`
|
||||
|
||||
const TooltipFooter = styled.div`
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-style: italic;
|
||||
`
|
||||
|
||||
// 自定义节点组件
|
||||
const CustomNode: FC<{ data: any }> = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
const nodeType = data.type
|
||||
let borderColor = 'var(--color-border)'
|
||||
let title = ''
|
||||
let backgroundColor = 'var(--bg-color)'
|
||||
let gradientColor = 'rgba(0, 0, 0, 0.03)'
|
||||
let avatar: JSX.Element | null = null
|
||||
|
||||
// 根据消息类型设置不同的样式和图标
|
||||
if (nodeType === 'user') {
|
||||
borderColor = 'var(--color-icon)'
|
||||
backgroundColor = 'rgba(var(--color-info-rgb), 0.03)'
|
||||
gradientColor = 'rgba(var(--color-info-rgb), 0.08)'
|
||||
title = data.userName || t('chat.history.user_node')
|
||||
|
||||
// 用户头像
|
||||
if (data.userAvatar) {
|
||||
avatar = <Avatar src={data.userAvatar} alt={title} />
|
||||
} else {
|
||||
avatar = <Avatar icon={<UserOutlined />} style={{ backgroundColor: 'var(--color-info)' }} />
|
||||
}
|
||||
} else if (nodeType === 'assistant') {
|
||||
borderColor = 'var(--color-primary)'
|
||||
backgroundColor = 'rgba(var(--color-primary-rgb), 0.03)'
|
||||
gradientColor = 'rgba(var(--color-primary-rgb), 0.08)'
|
||||
title = `${data.model || t('chat.history.assistant_node')}`
|
||||
|
||||
// 模型头像
|
||||
if (data.modelInfo) {
|
||||
avatar = <ModelAvatar model={data.modelInfo} size={32} />
|
||||
} else if (data.modelId) {
|
||||
const modelLogo = getModelLogo(data.modelId)
|
||||
avatar = (
|
||||
<Avatar
|
||||
src={modelLogo}
|
||||
icon={!modelLogo ? <RobotOutlined /> : undefined}
|
||||
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
avatar = <Avatar icon={<RobotOutlined />} style={{ backgroundColor: 'var(--color-primary)' }} />
|
||||
}
|
||||
}
|
||||
|
||||
// 处理节点点击事件,滚动到对应消息
|
||||
const handleNodeClick = () => {
|
||||
if (data.messageId) {
|
||||
// 创建一个自定义事件来定位消息并切换标签
|
||||
const customEvent = new CustomEvent('flow-navigate-to-message', {
|
||||
detail: {
|
||||
messageId: data.messageId,
|
||||
modelId: data.modelId,
|
||||
modelName: data.model,
|
||||
nodeType: nodeType
|
||||
},
|
||||
bubbles: true
|
||||
})
|
||||
|
||||
// 让监听器处理标签切换
|
||||
document.dispatchEvent(customEvent)
|
||||
|
||||
setTimeout(() => {
|
||||
EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + data.messageId)
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏连接点的通用样式
|
||||
const handleStyle = {
|
||||
opacity: 0,
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
background: 'transparent',
|
||||
border: 'none'
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<TooltipContent>
|
||||
<TooltipTitle>{title}</TooltipTitle>
|
||||
<TooltipBody>{data.content}</TooltipBody>
|
||||
<TooltipFooter>{t('chat.history.click_to_navigate')}</TooltipFooter>
|
||||
</TooltipContent>
|
||||
}
|
||||
placement="top"
|
||||
color="rgba(0, 0, 0, 0.85)"
|
||||
mouseEnterDelay={0.3}
|
||||
mouseLeaveDelay={0.1}
|
||||
destroyTooltipOnHide>
|
||||
<CustomNodeContainer
|
||||
style={{
|
||||
borderColor,
|
||||
background: `linear-gradient(135deg, ${backgroundColor} 0%, ${gradientColor} 100%)`,
|
||||
boxShadow: `0 4px 10px rgba(0, 0, 0, 0.1), 0 0 0 2px ${borderColor}40`
|
||||
}}
|
||||
onClick={handleNodeClick}>
|
||||
<Handle type="target" position={Position.Top} style={handleStyle} isConnectable={false} />
|
||||
<Handle type="target" position={Position.Left} style={handleStyle} isConnectable={false} />
|
||||
|
||||
<NodeHeader>
|
||||
<NodeAvatar>{avatar}</NodeAvatar>
|
||||
<NodeTitle>{title}</NodeTitle>
|
||||
</NodeHeader>
|
||||
<NodeContent title={data.content}>{data.content}</NodeContent>
|
||||
|
||||
<Handle type="source" position={Position.Bottom} style={handleStyle} isConnectable={false} />
|
||||
<Handle type="source" position={Position.Right} style={handleStyle} isConnectable={false} />
|
||||
</CustomNodeContainer>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
// 创建自定义节点类型
|
||||
const nodeTypes: NodeTypes = { custom: CustomNode }
|
||||
|
||||
interface ChatFlowHistoryProps {
|
||||
conversationId?: string
|
||||
}
|
||||
|
||||
// 定义节点和边的类型
|
||||
type FlowNode = Node<any, string>
|
||||
type FlowEdge = Edge<any>
|
||||
|
||||
// 统一的边样式
|
||||
const commonEdgeStyle = {
|
||||
stroke: 'var(--color-border)',
|
||||
strokeDasharray: '4,4',
|
||||
strokeWidth: 2
|
||||
}
|
||||
|
||||
// 统一的边配置
|
||||
const defaultEdgeOptions = {
|
||||
animated: true,
|
||||
style: commonEdgeStyle,
|
||||
type: 'step',
|
||||
markerEnd: undefined,
|
||||
zIndex: 5
|
||||
}
|
||||
|
||||
const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
|
||||
const { t } = useTranslation()
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { userName } = useSettings()
|
||||
|
||||
const topicId = conversationId
|
||||
|
||||
// 只在消息实际内容变化时更新,而不是属性变化(如foldSelected)
|
||||
const messages = useSelector(
|
||||
(state: RootState) => selectTopicMessages(state, topicId || ''),
|
||||
(prev, next) => {
|
||||
// 只比较消息的关键属性,忽略展示相关的属性(如foldSelected)
|
||||
if (prev.length !== next.length) return false
|
||||
|
||||
// 比较每条消息的内容和关键属性,忽略UI状态相关属性
|
||||
return prev.every((prevMsg, index) => {
|
||||
const nextMsg = next[index]
|
||||
return (
|
||||
prevMsg.id === nextMsg.id &&
|
||||
prevMsg.content === nextMsg.content &&
|
||||
prevMsg.role === nextMsg.role &&
|
||||
prevMsg.createdAt === nextMsg.createdAt &&
|
||||
prevMsg.askId === nextMsg.askId &&
|
||||
isEqual(prevMsg.model, nextMsg.model)
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// 获取用户头像
|
||||
const userAvatar = useSelector((state: RootState) => state.runtime.avatar)
|
||||
|
||||
// 消息过滤
|
||||
const { userMessages, assistantMessages } = useMemo(() => {
|
||||
const userMsgs = messages.filter((msg) => msg.role === 'user')
|
||||
const assistantMsgs = messages.filter((msg) => msg.role === 'assistant')
|
||||
return { userMessages: userMsgs, assistantMessages: assistantMsgs }
|
||||
}, [messages])
|
||||
|
||||
const buildConversationFlowData = useCallback(() => {
|
||||
if (!topicId || !messages.length) return { nodes: [], edges: [] }
|
||||
|
||||
// 创建节点和边
|
||||
const flowNodes: FlowNode[] = []
|
||||
const flowEdges: FlowEdge[] = []
|
||||
|
||||
// 布局参数
|
||||
const verticalGap = 200
|
||||
const horizontalGap = 350
|
||||
const baseX = 150
|
||||
|
||||
// 如果没有任何消息可以显示,返回空结果
|
||||
if (userMessages.length === 0 && assistantMessages.length === 0) {
|
||||
return { nodes: [], edges: [] }
|
||||
}
|
||||
|
||||
// 为所有用户消息创建节点
|
||||
userMessages.forEach((message, index) => {
|
||||
const nodeId = `user-${message.id}`
|
||||
const yPosition = index * verticalGap * 2
|
||||
|
||||
// 获取用户名
|
||||
const userNameValue = userName || t('chat.history.user_node')
|
||||
|
||||
// 获取用户头像
|
||||
const msgUserAvatar = userAvatar || null
|
||||
|
||||
flowNodes.push({
|
||||
id: nodeId,
|
||||
type: 'custom',
|
||||
data: {
|
||||
userName: userNameValue,
|
||||
content: message.content,
|
||||
type: 'user',
|
||||
messageId: message.id,
|
||||
userAvatar: msgUserAvatar
|
||||
},
|
||||
position: { x: baseX, y: yPosition },
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top
|
||||
})
|
||||
|
||||
// 找到用户消息之后的助手回复
|
||||
const userMsgTime = new Date(message.createdAt).getTime()
|
||||
const relatedAssistantMsgs = assistantMessages.filter((aMsg) => {
|
||||
const aMsgTime = new Date(aMsg.createdAt).getTime()
|
||||
return (
|
||||
aMsgTime > userMsgTime &&
|
||||
(index === userMessages.length - 1 || aMsgTime < new Date(userMessages[index + 1].createdAt).getTime())
|
||||
)
|
||||
})
|
||||
|
||||
// 为相关的助手消息创建节点
|
||||
relatedAssistantMsgs.forEach((aMsg, aIndex) => {
|
||||
const assistantNodeId = `assistant-${aMsg.id}`
|
||||
const isMultipleResponses = relatedAssistantMsgs.length > 1
|
||||
const assistantX = baseX + (isMultipleResponses ? horizontalGap * aIndex : 0)
|
||||
const assistantY = yPosition + verticalGap
|
||||
|
||||
// 根据位置确定连接点位置
|
||||
let sourcePos = Position.Bottom // 默认向下输出
|
||||
let targetPos = Position.Top // 默认从上方输入
|
||||
|
||||
// 横向排列多个助手消息时调整连接点
|
||||
// 注意:现在所有助手节点都直接连接到用户节点,而不是相互连接
|
||||
if (isMultipleResponses) {
|
||||
// 所有助手节点都使用顶部作为输入点(从用户节点)
|
||||
targetPos = Position.Top
|
||||
|
||||
// 所有助手节点都使用底部作为输出点(到下一个用户节点)
|
||||
sourcePos = Position.Bottom
|
||||
}
|
||||
|
||||
const aMsgAny = aMsg as any
|
||||
|
||||
// 获取模型名称
|
||||
const modelName = (aMsgAny.model && aMsgAny.model.name) || t('chat.history.assistant_node')
|
||||
|
||||
// 获取模型ID
|
||||
const modelId = (aMsgAny.model && aMsgAny.model.id) || ''
|
||||
|
||||
// 完整的模型信息
|
||||
const modelInfo = aMsgAny.model as Model | undefined
|
||||
|
||||
flowNodes.push({
|
||||
id: assistantNodeId,
|
||||
type: 'custom',
|
||||
data: {
|
||||
model: modelName,
|
||||
content: aMsg.content,
|
||||
type: 'assistant',
|
||||
messageId: aMsg.id,
|
||||
modelId: modelId,
|
||||
modelInfo
|
||||
},
|
||||
position: { x: assistantX, y: assistantY },
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos
|
||||
})
|
||||
|
||||
// 连接消息 - 将每个助手节点直接连接到用户节点
|
||||
if (aIndex === 0) {
|
||||
// 连接用户消息到第一个助手回复
|
||||
flowEdges.push({
|
||||
id: `edge-${nodeId}-to-${assistantNodeId}`,
|
||||
source: nodeId,
|
||||
target: assistantNodeId
|
||||
})
|
||||
} else {
|
||||
// 直接连接用户消息到所有其他助手回复
|
||||
flowEdges.push({
|
||||
id: `edge-${nodeId}-to-${assistantNodeId}`,
|
||||
source: nodeId,
|
||||
target: assistantNodeId
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 连接相邻的用户消息
|
||||
if (index > 0) {
|
||||
const prevUserNodeId = `user-${userMessages[index - 1].id}`
|
||||
const prevUserTime = new Date(userMessages[index - 1].createdAt).getTime()
|
||||
|
||||
// 查找前一个用户消息的所有助手回复
|
||||
const prevAssistantMsgs = assistantMessages.filter((aMsg) => {
|
||||
const aMsgTime = new Date(aMsg.createdAt).getTime()
|
||||
return aMsgTime > prevUserTime && aMsgTime < userMsgTime
|
||||
})
|
||||
|
||||
if (prevAssistantMsgs.length > 0) {
|
||||
// 所有前一个用户的助手消息都连接到当前用户消息
|
||||
prevAssistantMsgs.forEach((aMsg) => {
|
||||
const assistantId = `assistant-${aMsg.id}`
|
||||
flowEdges.push({
|
||||
id: `edge-${assistantId}-to-${nodeId}`,
|
||||
source: assistantId,
|
||||
target: nodeId
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 如果没有助手消息,直接连接两个用户消息
|
||||
flowEdges.push({
|
||||
id: `edge-${prevUserNodeId}-to-${nodeId}`,
|
||||
source: prevUserNodeId,
|
||||
target: nodeId
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 处理孤立的助手消息(没有对应的用户消息)
|
||||
const orphanAssistantMsgs = assistantMessages.filter(
|
||||
(aMsg) => !flowNodes.some((node) => node.id === `assistant-${aMsg.id}`)
|
||||
)
|
||||
|
||||
if (orphanAssistantMsgs.length > 0) {
|
||||
// 在图表顶部添加这些孤立消息
|
||||
const startY = flowNodes.length > 0 ? Math.min(...flowNodes.map((node) => node.position.y)) - verticalGap * 2 : 0
|
||||
|
||||
orphanAssistantMsgs.forEach((aMsg, index) => {
|
||||
const assistantNodeId = `orphan-assistant-${aMsg.id}`
|
||||
|
||||
// 获取模型数据
|
||||
const aMsgAny = aMsg as any
|
||||
|
||||
// 获取模型名称
|
||||
const modelName = (aMsgAny.model && aMsgAny.model.name) || t('chat.history.assistant_node')
|
||||
|
||||
// 获取模型ID
|
||||
const modelId = (aMsgAny.model && aMsgAny.model.id) || ''
|
||||
|
||||
// 完整的模型信息
|
||||
const modelInfo = aMsgAny.model as Model | undefined
|
||||
|
||||
flowNodes.push({
|
||||
id: assistantNodeId,
|
||||
type: 'custom',
|
||||
data: {
|
||||
model: modelName,
|
||||
content: aMsg.content,
|
||||
type: 'assistant',
|
||||
messageId: aMsg.id,
|
||||
modelId: modelId,
|
||||
modelInfo
|
||||
},
|
||||
position: { x: baseX, y: startY - index * verticalGap },
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top
|
||||
})
|
||||
|
||||
// 连接相邻的孤立消息
|
||||
if (index > 0) {
|
||||
const prevNodeId = `orphan-assistant-${orphanAssistantMsgs[index - 1].id}`
|
||||
flowEdges.push({
|
||||
id: `edge-${prevNodeId}-to-${assistantNodeId}`,
|
||||
source: prevNodeId,
|
||||
target: assistantNodeId
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { nodes: flowNodes, edges: flowEdges }
|
||||
}, [topicId, messages, userMessages, assistantMessages, t])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
const { nodes: flowNodes, edges: flowEdges } = buildConversationFlowData()
|
||||
setNodes([...flowNodes])
|
||||
setEdges([...flowEdges])
|
||||
setLoading(false)
|
||||
}, 500)
|
||||
}, [buildConversationFlowData, setNodes, setEdges])
|
||||
|
||||
return (
|
||||
<FlowContainer>
|
||||
{loading ? (
|
||||
<LoadingContainer>
|
||||
<Spin size="large" />
|
||||
</LoadingContainer>
|
||||
) : nodes.length > 0 ? (
|
||||
<ReactFlowProvider>
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodeTypes={nodeTypes}
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
edgesFocusable={true}
|
||||
zoomOnDoubleClick={true}
|
||||
preventScrolling={true}
|
||||
elementsSelectable={true}
|
||||
selectNodesOnDrag={false}
|
||||
nodesFocusable={true}
|
||||
zoomOnScroll={true}
|
||||
panOnScroll={false}
|
||||
minZoom={0.4}
|
||||
maxZoom={1}
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
fitView={true}
|
||||
fitViewOptions={{
|
||||
padding: 0.3,
|
||||
includeHiddenNodes: false,
|
||||
minZoom: 0.4,
|
||||
maxZoom: 1
|
||||
}}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
className="react-flow-container">
|
||||
<Controls showInteractive={false} />
|
||||
<MiniMap
|
||||
nodeStrokeWidth={3}
|
||||
zoomable
|
||||
pannable
|
||||
nodeColor={(node) => (node.data.type === 'user' ? 'var(--color-info)' : 'var(--color-primary)')}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
) : (
|
||||
<EmptyContainer>
|
||||
<EmptyText>{t('chat.history.no_messages')}</EmptyText>
|
||||
</EmptyContainer>
|
||||
)}
|
||||
</FlowContainer>
|
||||
)
|
||||
}
|
||||
|
||||
// 样式组件定义
|
||||
const FlowContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
`
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const EmptyContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-text-secondary);
|
||||
`
|
||||
|
||||
const EmptyText = styled.div`
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
`
|
||||
|
||||
const CustomNodeContainer = styled.div`
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid;
|
||||
width: 280px;
|
||||
height: 120px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
0 6px 10px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 2px ${(props) => props.style?.borderColor || 'var(--color-border)'}80 !important;
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
|
||||
/* 添加点击动画效果 */
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
`
|
||||
|
||||
const NodeHeader = styled.div`
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 32px;
|
||||
`
|
||||
|
||||
const NodeAvatar = styled.span`
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
const NodeTitle = styled.span`
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const NodeContent = styled.div`
|
||||
margin: 2px 0;
|
||||
color: var(--color-text);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
font-size: 14px;
|
||||
padding: 3px;
|
||||
`
|
||||
|
||||
// 确保组件使用React.memo包装以减少不必要的重渲染
|
||||
export default memo(ChatFlowHistory, (prevProps, nextProps) => {
|
||||
return prevProps.conversationId === nextProps.conversationId
|
||||
})
|
||||
@ -1,10 +1,15 @@
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons'
|
||||
import { DownOutlined, HistoryOutlined, UpOutlined } from '@ant-design/icons'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { RootState } from '@renderer/store'
|
||||
import { selectCurrentTopicId } from '@renderer/store/messages'
|
||||
import { Button, Drawer, Tooltip } from 'antd'
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ChatFlowHistory from './ChatFlowHistory'
|
||||
|
||||
interface ChatNavigationProps {
|
||||
containerId: string
|
||||
}
|
||||
@ -14,6 +19,8 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [isNearButtons, setIsNearButtons] = useState(false)
|
||||
const [hideTimer, setHideTimer] = useState<NodeJS.Timeout | null>(null)
|
||||
const [showChatHistory, setShowChatHistory] = useState(false)
|
||||
const currentTopicId = useSelector((state: RootState) => selectCurrentTopicId(state))
|
||||
const lastMoveTime = useRef(0)
|
||||
const { topicPosition, showTopics } = useSettings()
|
||||
const showRightTopics = topicPosition === 'right' && showTopics
|
||||
@ -59,6 +66,15 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
||||
setHideTimer(timer)
|
||||
}, [])
|
||||
|
||||
const handleChatHistoryClick = () => {
|
||||
setShowChatHistory(true)
|
||||
resetHideTimer()
|
||||
}
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
setShowChatHistory(false)
|
||||
}
|
||||
|
||||
const findUserMessages = () => {
|
||||
const container = document.getElementById(containerId)
|
||||
if (!container) return []
|
||||
@ -259,31 +275,58 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
||||
])
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
$isVisible={isVisible}
|
||||
$right={right}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}>
|
||||
<ButtonGroup>
|
||||
<Tooltip title={t('chat.navigation.prev')} placement="left">
|
||||
<NavigationButton
|
||||
type="text"
|
||||
icon={<UpOutlined />}
|
||||
onClick={handlePrevMessage}
|
||||
aria-label={t('chat.navigation.prev')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider />
|
||||
<Tooltip title={t('chat.navigation.next')} placement="left">
|
||||
<NavigationButton
|
||||
type="text"
|
||||
icon={<DownOutlined />}
|
||||
onClick={handleNextMessage}
|
||||
aria-label={t('chat.navigation.next')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</NavigationContainer>
|
||||
<>
|
||||
<NavigationContainer
|
||||
$isVisible={isVisible}
|
||||
$right={right}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}>
|
||||
<ButtonGroup>
|
||||
<Tooltip title={t('chat.navigation.prev')} placement="left">
|
||||
<NavigationButton
|
||||
type="text"
|
||||
icon={<UpOutlined />}
|
||||
onClick={handlePrevMessage}
|
||||
aria-label={t('chat.navigation.prev')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider />
|
||||
<Tooltip title={t('chat.navigation.next')} placement="left">
|
||||
<NavigationButton
|
||||
type="text"
|
||||
icon={<DownOutlined />}
|
||||
onClick={handleNextMessage}
|
||||
aria-label={t('chat.navigation.next')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider />
|
||||
<Tooltip title={t('chat.navigation.history')} placement="left">
|
||||
<NavigationButton
|
||||
type="text"
|
||||
icon={<HistoryOutlined />}
|
||||
onClick={handleChatHistoryClick}
|
||||
aria-label={t('chat.navigation.history')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</NavigationContainer>
|
||||
|
||||
<Drawer
|
||||
title={t('chat.history.title')}
|
||||
placement="right"
|
||||
onClose={handleDrawerClose}
|
||||
open={showChatHistory}
|
||||
width={680}
|
||||
destroyOnClose
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
height: 'calc(100% - 55px)'
|
||||
}
|
||||
}}>
|
||||
<ChatFlowHistory conversationId={currentTopicId || undefined} />
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
||||
import type { Message, Topic } from '@renderer/types'
|
||||
import { classNames } from '@renderer/utils'
|
||||
@ -36,6 +37,38 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
|
||||
setSelectedIndex(messageLength - 1)
|
||||
}, [messageLength])
|
||||
|
||||
// 添加对流程图节点点击事件的监听
|
||||
useEffect(() => {
|
||||
// 只在组件挂载和消息数组变化时添加监听器
|
||||
if (!isGrouped || messageLength <= 1) return
|
||||
|
||||
const handleFlowNavigate = (event: CustomEvent) => {
|
||||
const { messageId } = event.detail
|
||||
|
||||
// 查找对应的消息在当前消息组中的索引
|
||||
const targetIndex = messages.findIndex((msg) => msg.id === messageId)
|
||||
|
||||
// 如果找到消息且不是当前选中的索引,则切换标签
|
||||
if (targetIndex !== -1 && targetIndex !== selectedIndex) {
|
||||
setSelectedIndex(targetIndex)
|
||||
|
||||
// 使用setSelectedMessage函数来切换标签,这是处理foldSelected的关键
|
||||
const targetMessage = messages[targetIndex]
|
||||
if (targetMessage) {
|
||||
setSelectedMessage(targetMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加事件监听器
|
||||
document.addEventListener('flow-navigate-to-message', handleFlowNavigate as EventListener)
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
document.removeEventListener('flow-navigate-to-message', handleFlowNavigate as EventListener)
|
||||
}
|
||||
}, [messages, selectedIndex, isGrouped, messageLength])
|
||||
|
||||
const setSelectedMessage = useCallback(
|
||||
(message: Message) => {
|
||||
messages.forEach(async (m) => {
|
||||
@ -47,11 +80,47 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
}, 100)
|
||||
}, 200)
|
||||
},
|
||||
[editMessage, messages]
|
||||
)
|
||||
|
||||
// 添加对LOCATE_MESSAGE事件的监听
|
||||
useEffect(() => {
|
||||
// 为每个消息注册一个定位事件监听器
|
||||
const eventHandlers: { [key: string]: () => void } = {}
|
||||
|
||||
messages.forEach((message) => {
|
||||
const eventName = EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id
|
||||
const handler = () => {
|
||||
// 检查消息是否处于可见状态
|
||||
const element = document.getElementById(`message-${message.id}`)
|
||||
if (element) {
|
||||
const display = window.getComputedStyle(element).display
|
||||
|
||||
if (display === 'none') {
|
||||
// 如果消息隐藏,先切换标签
|
||||
setSelectedMessage(message)
|
||||
} else {
|
||||
// 直接滚动
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventHandlers[eventName] = handler
|
||||
EventEmitter.on(eventName, handler)
|
||||
})
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
// 移除所有事件监听器
|
||||
Object.entries(eventHandlers).forEach(([eventName, handler]) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
})
|
||||
}
|
||||
}, [messages, setSelectedMessage])
|
||||
|
||||
const renderMessage = useCallback(
|
||||
(message: Message & { index: number }, index: number) => {
|
||||
const isGridGroupMessage = isGrid && message.role === 'assistant' && isGrouped
|
||||
|
||||
206
yarn.lock
206
yarn.lock
@ -2798,6 +2798,71 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/command-line-args@npm:^5.2.3":
|
||||
version: 5.2.3
|
||||
resolution: "@types/command-line-args@npm:5.2.3"
|
||||
checksum: 10c0/3a9bc58fd26e546391f6369dd28c03d59349dc4ac39eada1a5c39cc3578e02e4aac222615170e0db79b198ffba2af84fdbdda46e08c6edc4da42bc17ea85200f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/command-line-usage@npm:^5.0.4":
|
||||
version: 5.0.4
|
||||
resolution: "@types/command-line-usage@npm:5.0.4"
|
||||
checksum: 10c0/67840ebf4bcfee200c07d978669ad596fe2adc350fd5c19d44ec2248623575d96ec917f513d1d59453f8f57e879133861a4cc41c20045c07f6c959f1fcaac7ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-color@npm:*":
|
||||
version: 3.1.3
|
||||
resolution: "@types/d3-color@npm:3.1.3"
|
||||
checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-drag@npm:^3.0.7":
|
||||
version: 3.0.7
|
||||
resolution: "@types/d3-drag@npm:3.0.7"
|
||||
dependencies:
|
||||
"@types/d3-selection": "npm:*"
|
||||
checksum: 10c0/65e29fa32a87c72d26c44b5e2df3bf15af21cd128386bcc05bcacca255927c0397d0cd7e6062aed5f0abd623490544a9d061c195f5ed9f018fe0b698d99c079d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-interpolate@npm:*":
|
||||
version: 3.0.4
|
||||
resolution: "@types/d3-interpolate@npm:3.0.4"
|
||||
dependencies:
|
||||
"@types/d3-color": "npm:*"
|
||||
checksum: 10c0/066ebb8da570b518dd332df6b12ae3b1eaa0a7f4f0c702e3c57f812cf529cc3500ec2aac8dc094f31897790346c6b1ebd8cd7a077176727f4860c2b181a65ca4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10":
|
||||
version: 3.0.11
|
||||
resolution: "@types/d3-selection@npm:3.0.11"
|
||||
checksum: 10c0/0c512956c7503ff5def4bb32e0c568cc757b9a2cc400a104fc0f4cfe5e56d83ebde2a97821b6f2cb26a7148079d3b86a2f28e11d68324ed311cf35c2ed980d1d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-transition@npm:^3.0.8":
|
||||
version: 3.0.9
|
||||
resolution: "@types/d3-transition@npm:3.0.9"
|
||||
dependencies:
|
||||
"@types/d3-selection": "npm:*"
|
||||
checksum: 10c0/4f68b9df7ac745b3491216c54203cbbfa0f117ae4c60e2609cdef2db963582152035407fdff995b10ee383bae2f05b7743493f48e1b8e46df54faa836a8fb7b5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-zoom@npm:^3.0.8":
|
||||
version: 3.0.8
|
||||
resolution: "@types/d3-zoom@npm:3.0.8"
|
||||
dependencies:
|
||||
"@types/d3-interpolate": "npm:*"
|
||||
"@types/d3-selection": "npm:*"
|
||||
checksum: 10c0/1dbdbcafddcae12efb5beb6948546963f29599e18bc7f2a91fb69cc617c2299a65354f2d47e282dfb86fec0968406cd4fb7f76ba2d2fb67baa8e8d146eb4a547
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6":
|
||||
version: 4.1.12
|
||||
resolution: "@types/debug@npm:4.1.12"
|
||||
@ -3306,6 +3371,35 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xyflow/react@npm:^12.4.4":
|
||||
version: 12.4.4
|
||||
resolution: "@xyflow/react@npm:12.4.4"
|
||||
dependencies:
|
||||
"@xyflow/system": "npm:0.0.52"
|
||||
classcat: "npm:^5.0.3"
|
||||
zustand: "npm:^4.4.0"
|
||||
peerDependencies:
|
||||
react: ">=17"
|
||||
react-dom: ">=17"
|
||||
checksum: 10c0/abce710e98a414def5f5cb847831ad49e25e3de5ebeaee4e4f9c8b651c1a507b1d61b8e11b8f1f519e9f7500f37d07218fbbd23d797c137e1e3f5515dc759c98
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xyflow/system@npm:0.0.52":
|
||||
version: 0.0.52
|
||||
resolution: "@xyflow/system@npm:0.0.52"
|
||||
dependencies:
|
||||
"@types/d3-drag": "npm:^3.0.7"
|
||||
"@types/d3-selection": "npm:^3.0.10"
|
||||
"@types/d3-transition": "npm:^3.0.8"
|
||||
"@types/d3-zoom": "npm:^3.0.8"
|
||||
d3-drag: "npm:^3.0.0"
|
||||
d3-selection: "npm:^3.0.0"
|
||||
d3-zoom: "npm:^3.0.0"
|
||||
checksum: 10c0/34822192065f4d1358c781882b5f7e1eba6d6953aa741e480de704080a7d0609823b124d90fec17a747226150e980c12d9cd145642aa795ff94054c6cc67dbaa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"CherryStudio@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "CherryStudio@workspace:."
|
||||
@ -3355,6 +3449,7 @@ __metadata:
|
||||
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
||||
"@types/tinycolor2": "npm:^1"
|
||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
||||
"@xyflow/react": "npm:^12.4.4"
|
||||
adm-zip: "npm:^0.5.16"
|
||||
antd: "npm:^5.22.5"
|
||||
applescript: "npm:^1.0.0"
|
||||
@ -4592,6 +4687,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classcat@npm:^5.0.3":
|
||||
version: 5.0.5
|
||||
resolution: "classcat@npm:5.0.5"
|
||||
checksum: 10c0/ff8d273055ef9b518529cfe80fd0486f7057a9917373807ff802d75ceb46e8f8e148f41fa094ee7625c8f34642cfaa98395ff182d9519898da7cbf383d4a210d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classnames@npm:2.x, classnames@npm:^2.2.1, classnames@npm:^2.2.3, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.3.2, classnames@npm:^2.5.1":
|
||||
version: 2.5.1
|
||||
resolution: "classnames@npm:2.5.1"
|
||||
@ -5010,6 +5112,88 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-color@npm:1 - 3":
|
||||
version: 3.1.0
|
||||
resolution: "d3-color@npm:3.1.0"
|
||||
checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-dispatch@npm:1 - 3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-dispatch@npm:3.0.1"
|
||||
checksum: 10c0/6eca77008ce2dc33380e45d4410c67d150941df7ab45b91d116dbe6d0a3092c0f6ac184dd4602c796dc9e790222bad3ff7142025f5fd22694efe088d1d941753
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-drag@npm:2 - 3, d3-drag@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-drag@npm:3.0.0"
|
||||
dependencies:
|
||||
d3-dispatch: "npm:1 - 3"
|
||||
d3-selection: "npm:3"
|
||||
checksum: 10c0/d2556e8dc720741a443b595a30af403dd60642dfd938d44d6e9bfc4c71a962142f9a028c56b61f8b4790b65a34acad177d1263d66f103c3c527767b0926ef5aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-ease@npm:1 - 3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-ease@npm:3.0.1"
|
||||
checksum: 10c0/fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-interpolate@npm:1 - 3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-interpolate@npm:3.0.1"
|
||||
dependencies:
|
||||
d3-color: "npm:1 - 3"
|
||||
checksum: 10c0/19f4b4daa8d733906671afff7767c19488f51a43d251f8b7f484d5d3cfc36c663f0a66c38fe91eee30f40327443d799be17169f55a293a3ba949e84e57a33e6a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-selection@npm:3.0.0"
|
||||
checksum: 10c0/e59096bbe8f0cb0daa1001d9bdd6dbc93a688019abc97d1d8b37f85cd3c286a6875b22adea0931b0c88410d025563e1643019161a883c516acf50c190a11b56b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-timer@npm:1 - 3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-timer@npm:3.0.1"
|
||||
checksum: 10c0/d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-transition@npm:2 - 3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-transition@npm:3.0.1"
|
||||
dependencies:
|
||||
d3-color: "npm:1 - 3"
|
||||
d3-dispatch: "npm:1 - 3"
|
||||
d3-ease: "npm:1 - 3"
|
||||
d3-interpolate: "npm:1 - 3"
|
||||
d3-timer: "npm:1 - 3"
|
||||
peerDependencies:
|
||||
d3-selection: 2 - 3
|
||||
checksum: 10c0/4e74535dda7024aa43e141635b7522bb70cf9d3dfefed975eb643b36b864762eca67f88fafc2ca798174f83ca7c8a65e892624f824b3f65b8145c6a1a88dbbad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-zoom@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-zoom@npm:3.0.0"
|
||||
dependencies:
|
||||
d3-dispatch: "npm:1 - 3"
|
||||
d3-drag: "npm:2 - 3"
|
||||
d3-interpolate: "npm:1 - 3"
|
||||
d3-selection: "npm:2 - 3"
|
||||
d3-transition: "npm:2 - 3"
|
||||
checksum: 10c0/ee2036479049e70d8c783d594c444fe00e398246048e3f11a59755cd0e21de62ece3126181b0d7a31bf37bcf32fd726f83ae7dea4495ff86ec7736ce5ad36fd3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dashdash@npm:^1.12.0":
|
||||
version: 1.14.1
|
||||
resolution: "dashdash@npm:1.14.1"
|
||||
@ -15166,7 +15350,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.4.0":
|
||||
"use-sync-external-store@npm:^1.0.0, use-sync-external-store@npm:^1.2.2, use-sync-external-store@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "use-sync-external-store@npm:1.4.0"
|
||||
peerDependencies:
|
||||
@ -15861,6 +16045,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zustand@npm:^4.4.0":
|
||||
version: 4.5.6
|
||||
resolution: "zustand@npm:4.5.6"
|
||||
dependencies:
|
||||
use-sync-external-store: "npm:^1.2.2"
|
||||
peerDependencies:
|
||||
"@types/react": ">=16.8"
|
||||
immer: ">=9.0.6"
|
||||
react: ">=16.8"
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
checksum: 10c0/5b39aff2ef57e5a8ada647261ec1115697d397be311c51461d9ea81b5b63c6d2c498b960477ad2db72dc21db6aa229a92bdf644f6a8ecf7b1d71df5b4a5e95d3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zwitch@npm:^1.0.0":
|
||||
version: 1.0.5
|
||||
resolution: "zwitch@npm:1.0.5"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user