From 66b865f2f37e368d43188b3abe9f9917a5dab1cd Mon Sep 17 00:00:00 2001 From: experdotxie Date: Tue, 30 Dec 2025 14:24:38 +0800 Subject: [PATCH 001/140] remove crosstab/object --- .claude/settings.local.json | 5 +- README.md | 40 +- README.zh-CN.md | 38 +- docs/idea.crosstab.impl.md | 71 -- docs/idea.crosstab.md | 278 ----- .../components/common/PageLineageDisplay.tsx | 37 +- .../src/components/layout/sidebar/Sidebar.tsx | 36 - .../layout/sidebar_items/SidebarActions.tsx | 18 - .../layout/sidebar_items/task/TaskMonitor.tsx | 41 - .../src/components/layout/tabs/TabsArea.tsx | 96 +- .../src/components/pages/chat/ChatHeader.tsx | 10 +- .../src/components/pages/chat/MessageList.tsx | 4 +- .../pages/crosstab/AxisDataManager.tsx | 465 ------- .../pages/crosstab/CrosstabChat.tsx | 1081 ----------------- .../pages/crosstab/CrosstabTable.tsx | 553 --------- .../pages/crosstab/CrosstabUtils.ts | 433 ------- .../pages/crosstab/MetadataDisplay.tsx | 496 -------- .../components/pages/crosstab/TopicInput.tsx | 95 -- .../crosstab/components/DimensionList.tsx | 61 - .../pages/crosstab/crosstab-page.css | 980 --------------- .../pages/crosstab/crosstab-table.css | 347 ------ .../pages/crosstab/hooks/useAITaskManager.ts | 48 - .../crosstab/hooks/useCrosstabWorkflow.ts | 512 -------- .../crosstab/hooks/useDimensionAIManager.ts | 91 -- .../pages/crosstab/hooks/useLLMConfig.ts | 26 - .../crosstab/hooks/useTableDataAIManager.ts | 56 - .../src/components/pages/crosstab/index.ts | 11 - .../pages/crosstab/services/aiTaskExecutor.ts | 81 -- .../crosstab/services/crosstabGenerator.ts | 144 --- .../pages/favorites/FavoriteDetailPage.tsx | 4 - .../pages/object/ConnectionEditor.tsx | 279 ----- .../pages/object/ObjectAIGenerator.tsx | 861 ------------- .../pages/object/ObjectAIService.tsx | 426 ------- .../pages/object/ObjectAIService.tsx.backup | 988 --------------- .../components/pages/object/ObjectBrowser.tsx | 431 ------- .../pages/object/ObjectCrosstabAnalyzer.tsx | 471 ------- .../components/pages/object/ObjectNode.tsx | 198 --- .../components/pages/object/ObjectPage.tsx | 341 ------ .../pages/object/ObjectPropertyView.tsx | 303 ----- .../components/pages/object/ObjectToolbar.tsx | 811 ------------- .../pages/object/ObjectTreeNode.tsx | 286 ----- .../pages/object/PropertyTableEditor.tsx | 491 -------- .../pages/object/RelationManager.tsx | 237 ---- .../pages/object/RelationshipGraph.tsx | 368 ------ .../pages/object/ai/AITaskManager.ts | 101 -- .../pages/object/ai/PromptBuilder.ts | 212 ---- .../pages/object/components/ToolbarButton.tsx | 35 - .../src/components/pages/object/index.ts | 9 - .../src/components/pages/object/object.css | 76 -- .../pages/object/utils/fileDownloadUtils.ts | 21 - .../pages/object/utils/objectDataValidator.ts | 32 - .../pages/object/utils/objectNodeFactory.ts | 45 - .../pages/object/utils/treeDataBuilder.ts | 37 - .../components/settings/DataManagement.tsx | 2 - src/renderer/src/stores/ZustandAppContext.tsx | 28 - src/renderer/src/stores/crosstabStore.ts | 219 ---- .../src/stores/helpers/chatHelpers.ts | 89 +- src/renderer/src/stores/helpers/helpers.ts | 3 - src/renderer/src/stores/helpers/index.ts | 24 +- .../src/stores/helpers/objectNodeHelpers.ts | 160 --- .../src/stores/helpers/objectRootFactory.ts | 120 -- .../src/stores/helpers/relationNodeFactory.ts | 212 ---- src/renderer/src/stores/index.ts | 2 - src/renderer/src/stores/objectStore.ts | 653 ---------- src/renderer/src/stores/pagesStore.ts | 413 +------ .../src/stores/persistence/storeConfig.ts | 4 - src/renderer/src/stores/useAppStores.ts | 18 +- src/renderer/src/types/type.ts | 216 +--- src/renderer/src/utils/taskDisplayHelpers.tsx | 14 +- 69 files changed, 30 insertions(+), 15364 deletions(-) delete mode 100644 docs/idea.crosstab.impl.md delete mode 100644 docs/idea.crosstab.md delete mode 100644 src/renderer/src/components/pages/crosstab/AxisDataManager.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/CrosstabChat.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/CrosstabTable.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/CrosstabUtils.ts delete mode 100644 src/renderer/src/components/pages/crosstab/MetadataDisplay.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/TopicInput.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/components/DimensionList.tsx delete mode 100644 src/renderer/src/components/pages/crosstab/crosstab-page.css delete mode 100644 src/renderer/src/components/pages/crosstab/crosstab-table.css delete mode 100644 src/renderer/src/components/pages/crosstab/hooks/useAITaskManager.ts delete mode 100644 src/renderer/src/components/pages/crosstab/hooks/useCrosstabWorkflow.ts delete mode 100644 src/renderer/src/components/pages/crosstab/hooks/useDimensionAIManager.ts delete mode 100644 src/renderer/src/components/pages/crosstab/hooks/useLLMConfig.ts delete mode 100644 src/renderer/src/components/pages/crosstab/hooks/useTableDataAIManager.ts delete mode 100644 src/renderer/src/components/pages/crosstab/index.ts delete mode 100644 src/renderer/src/components/pages/crosstab/services/aiTaskExecutor.ts delete mode 100644 src/renderer/src/components/pages/crosstab/services/crosstabGenerator.ts delete mode 100644 src/renderer/src/components/pages/object/ConnectionEditor.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectAIGenerator.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectAIService.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectAIService.tsx.backup delete mode 100644 src/renderer/src/components/pages/object/ObjectBrowser.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectCrosstabAnalyzer.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectNode.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectPage.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectPropertyView.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectToolbar.tsx delete mode 100644 src/renderer/src/components/pages/object/ObjectTreeNode.tsx delete mode 100644 src/renderer/src/components/pages/object/PropertyTableEditor.tsx delete mode 100644 src/renderer/src/components/pages/object/RelationManager.tsx delete mode 100644 src/renderer/src/components/pages/object/RelationshipGraph.tsx delete mode 100644 src/renderer/src/components/pages/object/ai/AITaskManager.ts delete mode 100644 src/renderer/src/components/pages/object/ai/PromptBuilder.ts delete mode 100644 src/renderer/src/components/pages/object/components/ToolbarButton.tsx delete mode 100644 src/renderer/src/components/pages/object/index.ts delete mode 100644 src/renderer/src/components/pages/object/object.css delete mode 100644 src/renderer/src/components/pages/object/utils/fileDownloadUtils.ts delete mode 100644 src/renderer/src/components/pages/object/utils/objectDataValidator.ts delete mode 100644 src/renderer/src/components/pages/object/utils/objectNodeFactory.ts delete mode 100644 src/renderer/src/components/pages/object/utils/treeDataBuilder.ts delete mode 100644 src/renderer/src/stores/crosstabStore.ts delete mode 100644 src/renderer/src/stores/helpers/objectNodeHelpers.ts delete mode 100644 src/renderer/src/stores/helpers/objectRootFactory.ts delete mode 100644 src/renderer/src/stores/helpers/relationNodeFactory.ts delete mode 100644 src/renderer/src/stores/objectStore.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b3972b0..6644193 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,9 +4,10 @@ "Bash(npm run typecheck:*)", "Bash(npm run lint)", "Bash(npm audit:*)", - "Bash(pnpm audit:*)" + "Bash(pnpm audit:*)", + "Bash(cat:*)" ], "deny": [], "ask": [] } -} \ No newline at end of file +} diff --git a/README.md b/README.md index a71d5cd..d3403e6 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@ [中文版](README.zh-CN.md) | **English** -An AI chat application built with Electron + React + TypeScript, supporting **multi-model conversations**, **intelligent crosstab data analysis**, and **knowledge organization management**. +An AI chat application built with Electron + React + TypeScript, supporting **multi-model conversations** and **knowledge organization management**. -基于 Electron + React + TypeScript 开发的AI聊天应用,支持**多模型对话**、**交叉数据分析**和**知识组织管理**。 +基于 Electron + React + TypeScript 开发的AI聊天应用,支持**多模型对话**和**知识组织管理**。 - 博客1:[AI 聊天应用的 10 条高级需求](https://www.cnblogs.com/experdot/p/18924253) -- 博客2:[解决了AI聊天的 10 个痛点后,新的功能:交叉分析表](https://www.cnblogs.com/experdot/p/18974641) [查看中文 README 介绍](README.zh-CN.md) @@ -33,8 +32,6 @@ An AI chat application built with Electron + React + TypeScript, supporting **mu ### Unique Features -- **AI Crosstab Analysis**: Automatically generate structured comparison analysis tables -- **AI Object Manager**: Visual knowledge data structure management - **Data Import/Export**: Support for mainstream AI platform data migration (OpenAI ChatGPT / Deepseek Chat) ### Knowledge Management @@ -48,14 +45,6 @@ An AI chat application built with Electron + React + TypeScript, supporting **mu ![Main Interface](./Screenshot-1.png) -#### Crosstab Analysis - -![Crosstab Analysis](./Screenshot-2.png) - -#### Object Manager - -![Object Manager](./Screenshot-3.png) - ## Quick Start ### Requirements @@ -90,31 +79,6 @@ pnpm build:linux # Linux ## Core Features -### Crosstab Analysis - -Convert any topic into structured comparison analysis tables, suitable for: - -- Academic research literature comparison -- Business decision solution evaluation -- Educational material knowledge organization -- Product feature competitive analysis - -Workflow: - -1. Input analysis topic -2. AI automatically generates table structure -3. Fill intersection data -4. Manual editing and optimization - -### Object Browser - -Visualize complex data structures with support for: - -- Tree structure display -- AI automatic node generation -- Manual editing and organization -- Structured data export - ### Chat Branch Management - Message tree structure diff --git a/README.zh-CN.md b/README.zh-CN.md index c84e242..a7ad84c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,10 +6,9 @@ **中文版** | [English](README.md) -基于 Electron + React + TypeScript 开发的AI聊天应用,支持**多模型对话**、**交叉数据分析**和**知识组织管理**。 +基于 Electron + React + TypeScript 开发的AI聊天应用,支持**多模型对话**和**知识组织管理**。 - 博客1:[AI 聊天应用的 10 条高级需求](https://www.cnblogs.com/experdot/p/18924253) -- 博客2:[解决了AI聊天的 10 个痛点后,新的功能:交叉分析表](https://www.cnblogs.com/experdot/p/18974641) > 示例:渐进式交互,生成小说设定 @@ -29,8 +28,6 @@ ### 独特功能 -- **AI 交叉分析表**: 自动生成结构化对比分析表格 -- **AI 对象管理器**: 可视化知识数据结构管理 - **数据导入导出**: 支持主流AI平台数据迁移(OpenAI ChatGPT / Deepseek Chat) ### 知识管理 @@ -44,14 +41,6 @@ ![主界面](./Screenshot-1.png) -#### 交叉分析表 - -![交叉分析表](./Screenshot-2.png) - -#### 对象管理器 - -![对象管理器](./Screenshot-3.png) - ## 快速开始 ### 环境要求 @@ -86,31 +75,6 @@ pnpm build:linux # Linux ## 核心功能 -### 交叉分析表 - -将任意主题转换为结构化对比分析表格,适用于: - -- 学术研究的文献对比 -- 商业决策的方案评估 -- 教学材料的知识点整理 -- 产品功能的竞品分析 - -操作流程: - -1. 输入分析主题 -2. AI自动生成表格结构 -3. 填充交叉点数据 -4. 手动编辑和优化 - -### 对象浏览器 - -可视化管理复杂数据结构,支持: - -- 树状结构展示 -- AI自动生成节点 -- 手动编辑和组织 -- 结构化数据导出 - ### 聊天分支管理 - 消息树状结构 diff --git a/docs/idea.crosstab.impl.md b/docs/idea.crosstab.impl.md deleted file mode 100644 index dcc50a4..0000000 --- a/docs/idea.crosstab.impl.md +++ /dev/null @@ -1,71 +0,0 @@ -✅ **交叉视图对话页签功能已完成实现!** - -我成功地为您的聊天应用添加了全新的交叉视图功能,基于您的idea.md文档实现了完整的交叉表生成工作流程。 - -## 🚀 已实现的功能 - -### 1. 新的聊天类型支持 -- 扩展了应用类型系统,支持普通聊天和交叉视图聊天两种类型 -- 每种类型都有独特的UI界面和交互逻辑 - -### 2. 交叉视图生成器 (CrosstabChat) -基于idea.md实现了完整的4步工作流程: - -**第1步:主题分析** -- 用户输入主题(如"世界简史"、"编程语言对比"等) -- AI分析并生成交叉表结构JSON(主题、横轴、纵轴、值) - -**第2步:横轴数据生成** -- 根据主题结构自动生成横轴的代表性值列表 -- 例如:地理区域→["东亚", "欧洲", "北美洲"...] - -**第3步:纵轴数据生成** -- 自动生成纵轴的代表性值列表 -- 例如:时间年代→["古代文明", "中世纪", "近代史"...] - -**第4步:交叉表数据填充** -- 为每个横轴-纵轴交叉点生成具体的描述性内容 -- 生成完整的交叉分析表格 - -### 3. 专业的UI界面 -- **步骤导航**:左侧垂直步骤指示器,清晰展示进度 -- **工作区**:右侧动态内容区域,根据当前步骤显示不同界面 -- **数据展示**: - - 主题结构卡片显示 - - 横轴/纵轴数据标签展示 - - 专业的交叉表格展示 -- **响应式设计**:支持不同屏幕尺寸 -- **美观的渐变色彩**:不同卡片采用不同主题色彩 - -### 4. 完整的应用集成 -- **标签页支持**:交叉视图聊天有独特的表格图标 -- **状态管理**:完整的Redux状态管理支持 -- **创建入口**: - - 侧边栏"新建交叉视图"按钮 - - 空状态页面的创建选项 -- **状态指示**:标签页显示生成进度(如"进度: 2/4") - -## 🎯 使用方法 - -1. **创建交叉视图聊天**: - - 点击侧边栏的表格图标按钮 - - 或在空状态页面点击"新建交叉视图" - -2. **生成交叉表**: - - 输入您想分析的主题 - - 按步骤点击生成按钮 - - 等待AI完成每个步骤的分析 - -3. **查看结果**: - - 查看生成的交叉分析表格 - - 所有数据都会自动保存 - -## 🔧 技术特点 - -- **类型安全**:完整的TypeScript类型定义 -- **模块化设计**:组件可复用和扩展 -- **智能提示词**:直接使用idea.md中的专业提示词模板 -- **错误处理**:完善的错误处理和用户提示 -- **状态持久化**:所有数据自动保存到应用状态 - -现在您可以试用这个全新的交叉视图功能了!它将帮助用户通过AI智能生成各种主题的结构化分析表格,非常适合用于学习、研究和内容创作。 \ No newline at end of file diff --git a/docs/idea.crosstab.md b/docs/idea.crosstab.md deleted file mode 100644 index 407a1fd..0000000 --- a/docs/idea.crosstab.md +++ /dev/null @@ -1,278 +0,0 @@ -核心价值主张 -激发结构化思维: 帮助用户从“交叉表”的维度化视角思考问题,发现新的分析角度。 -提升信息组织效率: 自动化地将零散的想法或复杂主题整理成清晰的矩阵表格。 -加速内容初步生成: 快速填充矩阵内容,为报告、文章或分析提供高质量的初稿和素材。 -降低认知负荷: 将复杂的多维信息以二维表格的形式直观呈现,易于理解和比较。 - ---- 生成主题提示词 - -# 角色 -你是一位专业的交叉表(Crosstab)元数据设计师。你的核心任务是根据用户提供的主题,设计出最符合逻辑、最具洞察力的交叉表结构元数据。 - -# 任务 -分析用户输入的主题 `[%AiUserInput%]`,并生成一个描述交叉表结构的JSON对象。这个交叉表用于组织和展示关于该主题的**定性信息**。 - -# 核心原则 -1. **维度选择**:`横轴`和`纵轴`必须是该主题下两个相互正交、有意义的分类维度。例如:“时间”、“地理区域”、“人物角色”、“产品类别”、“理论流派”等。 -2. **值的性质**:这是最重要的规则。`值`代表在横轴和纵轴交叉点上呈现的具体内容。它**必须是定性的、描述性的文本**,而不是可量化的数值。 - * **合适的“值”:** “事件描述”、“关键发现”、“人物简介”、“策略摘要”、“症状表现”、“法律条款”、“代表作品”、“用户评价文本”。 - * **绝对禁止的“值”:** “销售额”、“人口数量”、“评分”、“温度”、“百分比”、“库存量”等任何数值指标。 - -# 工作流程 -1. **分析主题**:深入理解用户输入主题的核心概念。 -2. **构思维度**:找出最能有效组织该主题信息的两个核心维度,分别作为`横轴`和`纵轴`。 -3. **定义交叉点内容**:思考当一个横轴项目和一个纵轴项目相交时,交叉单元格里应该存放什么类型的**描述性、纪实性内容**。将这种内容类型定义为`值`。 -4. **格式化输出**:将“主题”、“横轴”、“纵轴”、“值”这四个元素,严格按照下面指定的JSON格式进行输出。 - -# 输出格式 -```json -{"主题":"","横轴":"","纵轴":"","值":""} -``` - -# 示例 -* **示例 1 (用户输入: "世界简史")** - ```json - {"主题":"世界简史","横轴":"地理区域","纵轴":"时间年代","值":"关键历史事件描述"} - ``` -* **示例 2 (用户输入: "漫威电影宇宙角色分析")** - ```json - {"主题":"漫威电影宇宙角色分析","横轴":"超级英雄名称","纵轴":"分析维度","值":"具体分析内容"} - ``` -* **示例 3 (用户输入: "常见编程语言对比")** - ```json - {"主题":"常见编程语言对比","横轴":"编程语言","纵轴":"对比特性","值":"特性描述与优缺点"} - ``` - ---- 生成横轴提示词 - -# 角色 -你是一位专业的数据分析师和内容策略师。你的任务是为一个已经设计好的交叉表结构进行内容填充的准备工作。 - -# 任务 -根据下方提供的交叉表元数据JSON `[主题JSON]`,为该表的`横轴`(Horizontal Axis)生成一组具有代表性的、符合逻辑的示例值列表。 - -# 输入数据 -`[=主题JSON]` - -这是一个描述交叉表结构的JSON对象,由上一个任务生成。 - -# 生成准则 -1. **定位目标**: 首先,从输入的JSON中准确提取 `横轴` 字段的值。这个值(例如“地理区域”、“超级英雄名称”)就是你要为其生成实例的分类。 -2. **代表性**: 生成的列表不求完全穷尽,但必须包含该分类下**最广为人知、最具代表性**的几个项目。例如,对于“地理区域”,应优先考虑大洲或核心文化圈。 -3. **多样性**: 确保列表中的项目具有一定的多样性,能覆盖该分类的不同方面。例如,对于“编程语言”,应包含前端、后端、系统级等不同用途的语言。 -4. **数量适中**: 生成一个包含若干个值的列表。这个数量既能提供丰富的上下文,又不会过于冗长。 -5. **格式简洁**: 每个值都应是一个简洁的字符串,可以直接用作表格的列标题。 - -# 输出格式 -严格遵守以下JSON数组格式,数组内只包含字符串。 -```json -["值1", "值2", "值3", "..."] -``` - -# 完整示例 - ---- -### **示例 1** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"世界简史","横轴":"地理区域","纵轴":"时间年代","值":"关键历史事件描述"} - ``` - -* **分析:** `横轴`是“地理区域”。需要生成一组代表性的世界地理区域。 - -* **输出:** - ```json - ["东亚", "南亚", "中东", "欧洲", "北美洲", "南美洲", "非洲"] - ``` - ---- -### **示例 2** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"漫威电影宇宙角色分析","横轴":"超级英雄名称","纵轴":"分析维度","值":"具体分析内容"} - ``` - -* **分析:** `横轴`是“超级英雄名称”。需要列出漫威宇宙中家喻户晓的英雄。 - -* **输出:** - ```json - ["钢铁侠", "美国队长", "雷神托尔", "绿巨人浩克", "黑寡妇", "奇异博士", "蜘蛛侠"] - ``` - ---- -### **示例 3** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"常见编程语言对比","横轴":"编程语言","纵轴":"对比特性","值":"特性描述与优缺点"} - ``` - -* **分析:** `横轴`是“编程语言”。需要列出在不同领域有代表性的流行编程语言。 - -* **输出:** - ```json - ["Python", "JavaScript", "Java", "C++", "Go", "Rust", "TypeScript"] - ``` - ---- 生成纵轴提示词 - -# 角色 -你是一位专业的数据分析师和内容策略师。你的任务是为一个已经设计好的交叉表结构进行内容填充的准备工作。 - -# 任务 -根据下方提供的交叉表元数据JSON `[主题JSON]`,为该表的`纵轴`(Vertical Axis)生成一组具有代表性的、符合逻辑的示例值列表。这些值将作为表格的**行标题**。 - -# 输入数据 -`[=主题JSON]` - -这是一个描述交叉表结构的JSON对象,由第一个任务生成。 - -# 生成准则 -1. **定位目标**: 首先,从输入的JSON中准确提取 `纵轴` 字段的值。这个值(例如“时间年代”、“分析维度”)就是你要为其生成实例的分类。 -2. **代表性与逻辑性**: 生成的列表应包含该分类下最重要、最符合逻辑的几个项目。这些项目需要能够构成分析该主题的有效框架。 -3. **多样性**: 确保列表中的项目具有一定的多样性,能够从不同角度或层面来剖析主题。 -4. **数量适中**: 生成一个包含若干个值的列表。这个数量足以构建一个有深度的分析框架,而又不会过于复杂。 -5. **格式简洁**: 每个值都应是一个简洁的字符串,可以直接用作表格的行标题。 - -# 输出格式 -严格遵守以下JSON数组格式,数组内只包含字符串。 -```json -["值1", "值2", "值3", "..."] -``` - -# 完整示例 - ---- -### **示例 1** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"世界简史","横轴":"地理区域","纵轴":"时间年代","值":"关键历史事件描述"} - ``` - -* **分析:** `纵轴`是“时间年代”。需要生成一组代表性的历史分期。 - -* **输出:** - ```json - ["史前时期", "古代文明", "中世纪", "文艺复兴与大航海", "近代史", "世界大战时期", "当代史"] - ``` - ---- -### **示例 2** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"漫威电影宇宙角色分析","横轴":"超级英雄名称","纵轴":"分析维度","值":"具体分析内容"} - ``` - -* **分析:** `纵轴`是“分析维度”。需要列出分析一个虚构角色的常用维度。 - -* **输出:** - ```json - ["背景故事与起源", "核心能力与装备", "性格与动机", "关键情节与成长", "与其他角色的关系", "在宇宙中的地位"] - ``` - ---- -### **示例 3** - -* **输入 `[主题JSON]`:** - ```json - {"主题":"常见编程语言对比","横轴":"编程语言","纵轴":"对比特性","值":"特性描述与优缺点"} - ``` - -* **分析:** `纵轴`是“对比特性”。需要列出对比不同编程语言时常用的技术和非技术特性。 - -* **输出:** - ```json - ["创始人与诞生年份", "设计范式", "主要应用领域", "性能表现", "学习曲线", "生态系统与社区支持", "语法特点"] - ``` - ---- 生成值提示词(遍历每一个横轴项目) - -# 角色 -你是一位资深的领域专家和内容创作者。你不仅理解数据结构,更能基于给定的主题和维度,创作出精确、凝练的核心内容。 - -# 任务 -你的任务是为一个概念交叉表**填充一整列的数据**。具体来说,你需要根据提供的**交叉表元数据**、一个指定的**横轴项目(列标题)**,以及一个**纵轴项目列表(行标题)**,为每一个行列交叉点生成一个简洁但信息丰富的文本值。 - -# 输入数据 -你将收到以下三项输入: - -1. **交叉表元数据 (`[主题JSON]`)**: 定义了整个表格的主题、坐标轴的含义以及交叉点内容的类型。 -2. **当前列标题 (`[横坐标项.轴坐标]`)**: 这是你当前需要填充数据的那一列的标题。 -3. **行标题列表 (`[纵坐标列表JSON]`)**: 这是你需要为之生成内容的每一行的标题。 - -## 主题 -[=主题JSON] - -## 横坐标项 -[=横坐标项.轴坐标] - -## 纵轴列表 -[=纵坐标列表JSON] - - -# 生成准则 -1. **情境合成 (Context Synthesis)**: 对于列表中的每一个“行标题”,你必须将其与“当前列标题”结合起来,形成一个具体的查询情境。这是生成内容的核心步骤。 -2. **遵循“值”的定义**: 仔细查看`交叉表元数据`中的 `值` 字段(例如“关键历史事件描述”)。你生成的每一条内容都必须严格符合这个定义。 -3. **简洁且信息丰富 (Concise and Informative)**: 生成的文本应该是对该交叉点核心信息的浓缩概括。力求用一个短语或一句话点明最关键的内容。避免无关的废话。 -4. **事实准确或逻辑自洽**: 对于纪实类主题(如历史、科技),确保内容事实准确。对于虚构类主题(如电影、小说),确保内容符合其世界观和逻辑。 - -# 输出格式 -生成一个单一的JSON对象。对象的**键 (key)** 必须与`行标题列表`中的字符串**完全匹配**,对象的**值 (value)** 则是你为该行列交叉点生成的文本。 -```json -{ - "行标题1": "为“列标题”和“行标题1”交叉点生成的值", - "行标题2": "为“列标题”和“行标题2”交叉点生成的值", - "..." : "..." -} -``` - -# 完整示例 - ---- -### **输入数据** - -* **1. 交叉表元数据 `[主题JSON]`**: - ```json - {"主题":"世界简史","横轴":"地理区域","纵轴":"时间年代","值":"关键历史事件描述"} - ``` - -* **2. 当前列标题 `[横坐标项.轴坐标]`**: - ``` - 欧洲 - ``` - -* **3. 行标题列表 `[纵坐标列表JSON]`**: - ```json - ["史前时期", "古代文明", "中世纪", "文艺复兴与大航海", "近代史", "世界大战时期", "当代史"] - ``` - -### **分析 (AI的思考过程)** - -1. **目标**: 为“欧洲”这一列,填充各个“时间年代”的“关键历史事件描述”。 -2. **逐项合成**: - * `"史前时期"` + `"欧洲"` -> 欧洲史前时期的代表? -> "洞穴壁画艺术的出现,如法国拉斯科洞窟。" - * `"古代文明"` + `"欧洲"` -> 欧洲古典文明的基石? -> "古希腊哲学、民主政治与古罗马法律、工程的奠基。" - * `"中世纪"` + `"欧洲"` -> 欧洲中世纪的重大事件? -> "封建制度的确立、基督教的统治地位与黑死病的肆虐。" - * `"文艺复兴与大航海"` + `"欧洲"` -> 这个时代的核心变革? -> "人文主义兴起,艺术与科学复兴,哥伦布发现新大陆。" - * `"近代史"` + `"欧洲"` -> 欧洲近代史的标志? -> "工业革命与法国大革命,民族国家崛起。" - * `"世界大战时期"` + `"欧洲"` -> 这个时期欧洲发生了什么? -> "成为两次世界大战的主战场,经历了空前的破坏与动荡。" - * `"当代史"` + `"欧洲"` -> 战后欧洲的主题? -> "冷战对峙下的东西分裂,以及欧洲一体化进程(欧盟的形成)。" -3. **格式化输出**: 将上述结果整理为JSON。 - -### **输出** - -```json -{ - "史前时期": "以法国拉斯科为代表的洞穴壁画艺术的出现。", - "古代文明": "古希腊哲学与民主政治,以及古罗马法制与工程的奠基。", - "中世纪": "封建庄园经济的统治,天主教会的至高权威以及黑死病的冲击。", - "文艺复兴与大航海": "人文主义光辉照耀下的艺术科学繁荣,以及地理大发现时代的开启。", - "近代史": "始于英国的工业革命和法国大革命,彻底改变社会结构。", - "世界大战时期": "作为两次世界大战的核心战场,经历了前所未有的毁灭与重构。", - "当代史": "从冷战的东西对峙到欧洲煤钢共同体,逐步走向一体化联盟。" -} -``` \ No newline at end of file diff --git a/src/renderer/src/components/common/PageLineageDisplay.tsx b/src/renderer/src/components/common/PageLineageDisplay.tsx index 24e6150..721139b 100644 --- a/src/renderer/src/components/common/PageLineageDisplay.tsx +++ b/src/renderer/src/components/common/PageLineageDisplay.tsx @@ -2,12 +2,9 @@ import React from 'react' import { Card, Typography, Space, Tag, Button, Empty, Divider, Timeline } from 'antd' import { UserOutlined, - NodeIndexOutlined, - TableOutlined, MessageOutlined, ArrowRightOutlined, HistoryOutlined, - BranchesOutlined, InfoCircleOutlined, LinkOutlined, CaretRightOutlined, @@ -74,14 +71,6 @@ const PageLineageDisplay: React.FC = ({ switch (source) { case 'user': return - case 'object_to_crosstab': - return - case 'crosstab_to_chat': - return - case 'object_to_chat': - return - case 'chat_to_object': - return default: return } @@ -91,14 +80,6 @@ const PageLineageDisplay: React.FC = ({ switch (source) { case 'user': return '用户手动创建' - case 'object_to_crosstab': - return '从对象页面生成' - case 'crosstab_to_chat': - return '从交叉分析表生成' - case 'object_to_chat': - return '从对象节点生成' - case 'chat_to_object': - return '从聊天页面生成' default: return '其他来源' } @@ -108,10 +89,6 @@ const PageLineageDisplay: React.FC = ({ switch (type) { case 'regular': return - case 'crosstab': - return - case 'object': - return default: return } @@ -121,10 +98,6 @@ const PageLineageDisplay: React.FC = ({ switch (type) { case 'regular': return '聊天页面' - case 'crosstab': - return '交叉分析表' - case 'object': - return '对象页面' default: return '未知页面' } @@ -142,15 +115,7 @@ const PageLineageDisplay: React.FC = ({ const getContextDescription = () => { if (!lineage?.sourceContext) return null - const { objectCrosstab, crosstabChat, customContext } = lineage.sourceContext - - if (objectCrosstab) { - return `基于对象节点 "${objectCrosstab.horizontalNodeName}" 和 "${objectCrosstab.verticalNodeName}" 的交叉分析` - } - - if (crosstabChat) { - return `基于交叉分析表单元格 "${crosstabChat.horizontalItem} × ${crosstabChat.verticalItem}" 的深度分析` - } + const { customContext } = lineage.sourceContext if (customContext) { return JSON.stringify(customContext, null, 2) diff --git a/src/renderer/src/components/layout/sidebar/Sidebar.tsx b/src/renderer/src/components/layout/sidebar/Sidebar.tsx index dc223ca..67921f6 100644 --- a/src/renderer/src/components/layout/sidebar/Sidebar.tsx +++ b/src/renderer/src/components/layout/sidebar/Sidebar.tsx @@ -33,8 +33,6 @@ export default function Sidebar({ folders, createFolder, createAndOpenChat, - createAndOpenCrosstabChat, - createAndOpenObjectChat, deleteMultiplePages } = usePagesStore() const { openTab } = useTabsStore() @@ -59,38 +57,6 @@ export default function Sidebar({ createAndOpenChat('新建聊天', folderId) }, [selectedNodeType, selectedNodeId, pages, createAndOpenChat]) - const handleCreateCrosstabChat = useCallback(() => { - // 根据当前选中的节点决定新交叉视图聊天的位置 - let folderId: string | undefined = undefined - - if (selectedNodeType === 'folder' && selectedNodeId) { - // 如果选中的是文件夹,在文件夹内创建聊天 - folderId = selectedNodeId - } else if (selectedNodeType === 'chat' && selectedNodeId) { - // 如果选中的是聊天,找到聊天所在的文件夹(如果有的话) - const selectedChat = pages.find((chat) => chat.id === selectedNodeId) - folderId = selectedChat?.folderId - } - - createAndOpenCrosstabChat('新建交叉视图', folderId) - }, [selectedNodeType, selectedNodeId, pages, createAndOpenCrosstabChat]) - - const handleCreateObjectChat = useCallback(() => { - // 根据当前选中的节点决定新对象页面的位置 - let folderId: string | undefined = undefined - - if (selectedNodeType === 'folder' && selectedNodeId) { - // 如果选中的是文件夹,在文件夹内创建对象页面 - folderId = selectedNodeId - } else if (selectedNodeType === 'chat' && selectedNodeId) { - // 如果选中的是聊天,找到聊天所在的文件夹(如果有的话) - const selectedChat = pages.find((chat) => chat.id === selectedNodeId) - folderId = selectedChat?.folderId - } - - createAndOpenObjectChat('新建对象页面', folderId) - }, [selectedNodeType, selectedNodeId, pages, createAndOpenObjectChat]) - const handleCreateFolder = useCallback(() => { createFolder('新建文件夹') }, [createFolder]) @@ -128,8 +94,6 @@ export default function Sidebar({ collapsed={false} hasCheckedItems={hasCheckedItems} onCreateChat={handleCreateChat} - onCreateCrosstabChat={handleCreateCrosstabChat} - onCreateObjectChat={handleCreateObjectChat} onCreateFolder={handleCreateFolder} onBatchDelete={handleBatchDelete} /> diff --git a/src/renderer/src/components/layout/sidebar_items/SidebarActions.tsx b/src/renderer/src/components/layout/sidebar_items/SidebarActions.tsx index b5e2b8b..c4d394b 100644 --- a/src/renderer/src/components/layout/sidebar_items/SidebarActions.tsx +++ b/src/renderer/src/components/layout/sidebar_items/SidebarActions.tsx @@ -5,8 +5,6 @@ import { PlusOutlined, FolderAddOutlined, DeleteOutlined, - TableOutlined, - BlockOutlined, DownOutlined } from '@ant-design/icons' @@ -14,8 +12,6 @@ interface SidebarActionsProps { collapsed?: boolean hasCheckedItems?: boolean onCreateChat: () => void - onCreateCrosstabChat: () => void - onCreateObjectChat: () => void onCreateFolder: () => void onBatchDelete?: () => void } @@ -24,24 +20,10 @@ export default function SidebarActions({ collapsed = false, hasCheckedItems = false, onCreateChat, - onCreateCrosstabChat, - onCreateObjectChat, onCreateFolder, onBatchDelete }: SidebarActionsProps) { const createOptions: MenuProps['items'] = [ - { - key: 'crosstab', - label: '新建交叉视图', - icon: , - onClick: onCreateCrosstabChat - }, - { - key: 'object', - label: '新建对象页面', - icon: , - onClick: onCreateObjectChat - }, { key: 'folder', label: '新建文件夹', diff --git a/src/renderer/src/components/layout/sidebar_items/task/TaskMonitor.tsx b/src/renderer/src/components/layout/sidebar_items/task/TaskMonitor.tsx index 8367383..7f140e0 100644 --- a/src/renderer/src/components/layout/sidebar_items/task/TaskMonitor.tsx +++ b/src/renderer/src/components/layout/sidebar_items/task/TaskMonitor.tsx @@ -21,8 +21,6 @@ import { StopOutlined, DeleteOutlined, MessageOutlined, - TableOutlined, - NodeIndexOutlined, RedoOutlined, EditOutlined, SwapOutlined, @@ -43,10 +41,6 @@ const getTaskIcon = (type: AITaskType) => { switch (type) { case 'chat': return - case 'crosstab_cell': - return - case 'object_generation': - return case 'retry': return case 'edit_resend': @@ -81,10 +75,6 @@ const getTaskTypeName = (type: AITaskType) => { switch (type) { case 'chat': return '聊天对话' - case 'crosstab_cell': - return '交叉分析单元格' - case 'object_generation': - return '对象生成' case 'retry': return '重试消息' case 'edit_resend': @@ -281,37 +271,6 @@ export default function TaskMonitor() { } } - if (context.crosstab) { - items.push( - - - {context.crosstab.horizontalItem} - - , - - - {context.crosstab.verticalItem} - - , - - - {JSON.stringify(context.crosstab.metadata, null, 2)} - - - ) - } - - if (context.object) { - items.push( - - {context.object.nodeId} - , - - {context.object.prompt} - - ) - } - if (context.retry) { items.push( diff --git a/src/renderer/src/components/layout/tabs/TabsArea.tsx b/src/renderer/src/components/layout/tabs/TabsArea.tsx index f6a40d2..6a64a18 100644 --- a/src/renderer/src/components/layout/tabs/TabsArea.tsx +++ b/src/renderer/src/components/layout/tabs/TabsArea.tsx @@ -8,9 +8,7 @@ import { CloseCircleOutlined, DeleteOutlined, SettingOutlined, - PlusOutlined, - TableOutlined, - BlockOutlined + PlusOutlined } from '@ant-design/icons' import { DndContext, @@ -36,8 +34,6 @@ import { useTabsStore } from '../../../stores/tabsStore' import { useUIStore } from '../../../stores/uiStore' import { useSettingsStore } from '../../../stores/settingsStore' import { ChatWindow, ChatWindowRef } from '../../pages/chat/index' -import { CrosstabChat } from '../../pages/crosstab/index' -import { ObjectPage } from '../../pages/object/index' import SettingsPage from '../../pages/settings/SettingsPage' import FavoriteDetailPage from '../../pages/favorites/FavoriteDetailPage' import { useFavoritesStore } from '../../../stores/favoritesStore' @@ -59,11 +55,7 @@ const TabLabelContent: React.FC = ({ }) => { return ( - {chat.type === 'crosstab' ? ( - - ) : chat.type === 'object' ? ( - - ) : chat.type === 'settings' ? ( + {chat.type === 'settings' ? ( ) : ( @@ -120,8 +112,6 @@ export default function TabsArea() { const { pages, createAndOpenChat, - createAndOpenCrosstabChat, - createAndOpenObjectChat, createAndOpenSettingsPage } = usePagesStore() const { @@ -176,16 +166,6 @@ export default function TabsArea() { createAndOpenChat('新建聊天') }, [createAndOpenChat]) - // 创建交叉视图聊天 - const handleCreateCrosstabChat = useCallback(() => { - createAndOpenCrosstabChat('新建交叉视图') - }, [createAndOpenCrosstabChat]) - - // 创建对象页面聊天 - const handleCreateObjectChat = useCallback(() => { - createAndOpenObjectChat('新建对象页面') - }, [createAndOpenObjectChat]) - // 处理打开设置 const handleOpenSettings = useCallback(() => { const settingsPageId = createAndOpenSettingsPage('llm') // 配置模型按钮应该打开LLM配置 @@ -437,54 +417,6 @@ export default function TabsArea() { } } - // 对于交叉视图聊天,使用不同的状态计算 - if (chat.type === 'crosstab') { - const currentStep = chat.crosstabData?.currentStep || 0 - const totalSteps = chat.crosstabData?.steps?.length || 4 - const completedSteps = - chat.crosstabData?.steps?.filter((step: any) => step.isCompleted).length || 0 - - if (completedSteps === totalSteps) { - return { - status: 'success' as const, - text: '交叉表已完成' - } - } else if (completedSteps > 0) { - return { - status: 'processing' as const, - text: `进度: ${completedSteps}/${totalSteps}` - } - } else { - return { - status: 'default' as const, - text: '未开始生成' - } - } - } - - // 对于对象页面,使用节点数量计算状态 - if (chat.type === 'object') { - const nodeCount = chat.objectData?.nodes ? Object.keys(chat.objectData.nodes).length : 0 - const hasGenerationHistory = chat.objectData?.generationHistory?.length > 0 - - if (nodeCount > 1 && hasGenerationHistory) { - return { - status: 'success' as const, - text: `已创建 ${nodeCount} 个节点` - } - } else if (nodeCount > 1) { - return { - status: 'processing' as const, - text: `包含 ${nodeCount} 个节点` - } - } else { - return { - status: 'default' as const, - text: '空对象结构' - } - } - } - // 普通聊天的状态计算 const messageCount = chat.messages?.length || 0 const hasStreamingMessage = !!chat.streamingMessage @@ -543,7 +475,7 @@ export default function TabsArea() {

暂无打开的聊天

- 创建一个新聊天开始对话,或者尝试新的交叉视图分析 + 创建一个新聊天开始对话

- -
} @@ -637,11 +553,7 @@ export default function TabsArea() { key: chatId, label: tabLabel, children: - chat.type === 'crosstab' ? ( - - ) : chat.type === 'object' ? ( - - ) : chat.type === 'settings' ? ( + chat.type === 'settings' ? ( ) : ( setChatWindowRef(chatId, ref)} /> diff --git a/src/renderer/src/components/pages/chat/ChatHeader.tsx b/src/renderer/src/components/pages/chat/ChatHeader.tsx index 3d9f0ab..529cb12 100644 --- a/src/renderer/src/components/pages/chat/ChatHeader.tsx +++ b/src/renderer/src/components/pages/chat/ChatHeader.tsx @@ -289,7 +289,7 @@ export default function ChatHeader({ // 基本信息 metadata.push( - `类型: ${chat.type === 'regular' ? '普通聊天' : chat.type === 'crosstab' ? '交叉表' : chat.type === 'object' ? '对象' : chat.type}` + `类型: ${chat.type === 'regular' ? '普通聊天' : chat.type === 'settings' ? '设置' : chat.type}` ) metadata.push(`创建时间: ${formatExactDateTime(chat.createdAt)}`) metadata.push(`更新时间: ${formatExactDateTime(chat.updatedAt)}`) @@ -307,10 +307,6 @@ export default function ChatHeader({ metadata.push(`\n--- 溯源信息 ---`) const sourceMap = { user: '用户创建', - object_to_crosstab: '对象→交叉表', - crosstab_to_chat: '交叉表→聊天', - object_to_chat: '对象→聊天', - chat_to_object: '聊天→对象', other: '其他' } metadata.push(`来源: ${sourceMap[chat.lineage.source] || chat.lineage.source}`) @@ -329,7 +325,7 @@ export default function ChatHeader({ } // 消息统计 - if (messages.length > 0) { + if (messages && messages.length > 0) { metadata.push(`\n--- 消息统计 ---`) metadata.push(`总消息数: ${messages.length}`) const userMessages = messages.filter((m) => m.role === 'user').length @@ -420,7 +416,7 @@ export default function ChatHeader({
{/* 消息折叠/展开下拉按钮 */} - {messages.length > 0 && ( + {messages && messages.length > 0 && ( { - const currentMessagesLength = messages.length + const currentMessagesLength = messages?.length ?? 0 const currentPathString = JSON.stringify(currentPath) const prevPathString = JSON.stringify(prevCurrentPath.current) @@ -380,7 +380,7 @@ const MessageList = React.memo(function MessageList({ prevCurrentPath.current = [...currentPath] prevSelectedMessageId.current = selectedMessageId }, [ - messages.length, + messages?.length, currentStreamingContent, currentStreamingReasoningContent, currentPath, diff --git a/src/renderer/src/components/pages/crosstab/AxisDataManager.tsx b/src/renderer/src/components/pages/crosstab/AxisDataManager.tsx deleted file mode 100644 index f44cc2e..0000000 --- a/src/renderer/src/components/pages/crosstab/AxisDataManager.tsx +++ /dev/null @@ -1,465 +0,0 @@ -import React, { useState } from 'react' -import { Card, Button, Input, Space, Popconfirm, Typography, List, Tooltip, message } from 'antd' -import { - PlusOutlined, - DeleteOutlined, - ColumnWidthOutlined, - EditOutlined, - HolderOutlined, - SaveOutlined, - CloseOutlined, - ArrowRightOutlined, - RobotOutlined, - LoadingOutlined -} from '@ant-design/icons' -import { CrosstabMetadata, CrosstabAxisDimension } from '../../../types/type' -import { - DndContext, - closestCenter, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors -} from '@dnd-kit/core' -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - verticalListSortingStrategy -} from '@dnd-kit/sortable' -import { useSortable } from '@dnd-kit/sortable' -import { CSS } from '@dnd-kit/utilities' - -const { Text } = Typography - -interface AxisDataManagerProps { - metadata: CrosstabMetadata | null - onUpdateDimension: ( - dimensionId: string, - dimensionType: 'horizontal' | 'vertical', - updates: Partial - ) => void - onGenerateDimensionValues: (dimensionId: string, dimensionType: 'horizontal' | 'vertical') => void - isGeneratingDimensionValues?: { [dimensionId: string]: boolean } - onGenerateTableData?: () => void - isGeneratingTableData?: boolean - canGenerateTableData?: boolean - onGoNext?: () => void -} - -interface SortableItemProps { - id: string - value: string - index: number - isEditing: boolean - onEdit: () => void - onSave: (newValue: string) => void - onCancel: () => void - onDelete: () => void - editingValue: string - setEditingValue: (value: string) => void -} - -const SortableItem: React.FC = ({ - id, - value, - index, - isEditing, - onEdit, - onSave, - onCancel, - onDelete, - editingValue, - setEditingValue -}) => { - const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id - }) - - const style = { - transform: CSS.Transform.toString(transform), - transition, - opacity: isDragging ? 0.5 : 1 - } - - return ( -
- -
- } - /> - -
- ) -} - -export default function AxisDataManager({ - metadata, - onUpdateDimension, - onGenerateDimensionValues, - isGeneratingDimensionValues, - onGenerateTableData, - isGeneratingTableData, - canGenerateTableData, - onGoNext -}: AxisDataManagerProps) { - const [editingItem, setEditingItem] = useState<{ - dimensionId: string - valueIndex: number - } | null>(null) - const [editingValue, setEditingValue] = useState('') - const [newValueInputs, setNewValueInputs] = useState<{ [dimensionId: string]: string }>({}) - - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates - }) - ) - - const handleDragEnd = ( - event: any, - dimensionId: string, - dimensionType: 'horizontal' | 'vertical' - ) => { - const { active, over } = event - - if (active.id !== over.id) { - const currentDimension = getDimensionById(dimensionId, dimensionType) - if (currentDimension) { - const oldIndex = currentDimension.values.findIndex( - (_, index) => `${dimensionId}-${index}` === active.id - ) - const newIndex = currentDimension.values.findIndex( - (_, index) => `${dimensionId}-${index}` === over.id - ) - - const newValues = arrayMove(currentDimension.values, oldIndex, newIndex) - onUpdateDimension(dimensionId, dimensionType, { values: newValues }) - } - } - } - - const handleEditStart = (dimensionId: string, valueIndex: number, currentValue: string) => { - setEditingItem({ dimensionId, valueIndex }) - setEditingValue(currentValue) - } - - const handleEditSave = ( - dimensionId: string, - dimensionType: 'horizontal' | 'vertical', - valueIndex: number - ) => { - if (editingValue.trim()) { - const currentDimension = getDimensionById(dimensionId, dimensionType) - if (currentDimension) { - const newValues = [...currentDimension.values] - newValues[valueIndex] = editingValue.trim() - onUpdateDimension(dimensionId, dimensionType, { values: newValues }) - message.success('修改成功') - } - } - setEditingItem(null) - setEditingValue('') - } - - const handleEditCancel = () => { - setEditingItem(null) - setEditingValue('') - } - - const handleDeleteDimensionValue = ( - dimensionId: string, - dimensionType: 'horizontal' | 'vertical', - valueIndex: number - ) => { - const currentDimension = getDimensionById(dimensionId, dimensionType) - if (currentDimension) { - const newValues = currentDimension.values.filter((_, index) => index !== valueIndex) - onUpdateDimension(dimensionId, dimensionType, { values: newValues }) - message.success('删除成功') - } - } - - const handleAddDimensionValue = ( - dimensionId: string, - dimensionType: 'horizontal' | 'vertical' - ) => { - const newValue = newValueInputs[dimensionId] - if (newValue && newValue.trim()) { - const currentDimension = getDimensionById(dimensionId, dimensionType) - if (currentDimension) { - const newValues = [...currentDimension.values, newValue.trim()] - onUpdateDimension(dimensionId, dimensionType, { values: newValues }) - setNewValueInputs((prev) => ({ ...prev, [dimensionId]: '' })) - message.success('添加成功') - } - } - } - - const getDimensionById = ( - dimensionId: string, - dimensionType: 'horizontal' | 'vertical' - ): CrosstabAxisDimension | null => { - if (!metadata) return null - const dimensions = - dimensionType === 'horizontal' ? metadata.horizontalDimensions : metadata.verticalDimensions - return dimensions.find((dim) => dim.id === dimensionId) || null - } - - const renderDimensionValues = ( - dimension: CrosstabAxisDimension, - dimensionType: 'horizontal' | 'vertical' - ) => { - const isGenerating = isGeneratingDimensionValues?.[dimension.id] || false - - return ( -
- {/* 值列表 */} - {dimension.values.length > 0 ? ( -
- handleDragEnd(event, dimension.id, dimensionType)} - > - `${dimension.id}-${index}`)} - strategy={verticalListSortingStrategy} - > - ( - handleEditStart(dimension.id, index, value)} - onSave={() => handleEditSave(dimension.id, dimensionType, index)} - onCancel={handleEditCancel} - onDelete={() => - handleDeleteDimensionValue(dimension.id, dimensionType, index) - } - editingValue={editingValue} - setEditingValue={setEditingValue} - /> - )} - /> - - -
- ) : ( -
- 暂无数据,请添加或生成数据 -
- )} - - {/* 添加新值 */} -
- - setNewValueInputs((prev) => ({ ...prev, [dimension.id]: e.target.value })) - } - onPressEnter={() => handleAddDimensionValue(dimension.id, dimensionType)} - /> - -
- - {/* 生成按钮 */} -
- -
-
- ) - } - - const renderDimensionCard = ( - dimension: CrosstabAxisDimension, - dimensionType: 'horizontal' | 'vertical' - ) => { - return ( - - {dimension.name} - {dimension.description && ( - - ({dimension.description}) - - )} - - } - style={{ marginBottom: 16 }} - > - {renderDimensionValues(dimension, dimensionType)} - - ) - } - - const renderAxisSection = ( - dimensions: CrosstabAxisDimension[], - dimensionType: 'horizontal' | 'vertical', - title: string - ) => { - if (dimensions.length === 0) { - return ( - -
- -
- 暂无{title}维度 -
- 请先在元数据中添加{title}维度 -
-
-
- ) - } - - return ( - -
- {dimensions.map((dimension) => renderDimensionCard(dimension, dimensionType))} -
-
- ) - } - - if (!metadata) { - return ( - -
- -
- 尚未生成多维度轴数据 -
- 请先完成主题分析,然后生成各维度的数据 -
-
-
- ) - } - - return ( -
- {/* 横轴数据 */} - {renderAxisSection(metadata.horizontalDimensions, 'horizontal', '横轴')} - - {/* 纵轴数据 */} - {renderAxisSection(metadata.verticalDimensions, 'vertical', '纵轴')} - - {/* 底部操作区 */} -
- - -
-
- ) -} diff --git a/src/renderer/src/components/pages/crosstab/CrosstabChat.tsx b/src/renderer/src/components/pages/crosstab/CrosstabChat.tsx deleted file mode 100644 index 283b5f4..0000000 --- a/src/renderer/src/components/pages/crosstab/CrosstabChat.tsx +++ /dev/null @@ -1,1081 +0,0 @@ -import React, { useState, useCallback, useMemo } from 'react' -import { Typography, Tabs, App, Space, Badge } from 'antd' -import { - FileTextOutlined, - BorderOutlined, - ColumnWidthOutlined, - TableOutlined, - CheckCircleOutlined -} from '@ant-design/icons' - -import { usePagesStore } from '../../../stores/pagesStore' -import { useSettingsStore } from '../../../stores/settingsStore' -import { useCrosstabStore } from '../../../stores/crosstabStore' -import { useAITasksStore } from '../../../stores/aiTasksStore' -import { createAIService } from '../../../services/aiService' -import { - PROMPT_TEMPLATES, - extractJsonContent, - generateAxisCombinations, - generateDimensionPath -} from './CrosstabUtils' -import { AITask } from '../../../types/type' -import { v4 as uuidv4 } from 'uuid' -import TopicInput from './TopicInput' -import MetadataDisplay from './MetadataDisplay' -import AxisDataManager from './AxisDataManager' -import CrosstabTable from './CrosstabTable' -import PageLineageDisplay from '../../common/PageLineageDisplay' -import ModelSelector from '../chat/ModelSelector' -import { useCrosstabWorkflow } from './hooks/useCrosstabWorkflow' -import './crosstab-page.css' - -const { Title } = Typography - -interface CrosstabChatProps { - chatId: string -} - -export default function CrosstabChat({ chatId }: CrosstabChatProps) { - const { pages, createAndOpenChat } = usePagesStore() - const { settings, getModelConfigForLLM } = useSettingsStore() - const { updateCrosstabData, updateCrosstabStep, completeCrosstabStep } = useCrosstabStore() - const { addTask, updateTask } = useAITasksStore() - const [userInput, setUserInput] = useState('') - const [activeTab, setActiveTab] = useState('0') - const [selectedModel, setSelectedModel] = useState(settings.defaultLLMId) - const [isGeneratingColumn, setIsGeneratingColumn] = useState(null) - const [isGeneratingRow, setIsGeneratingRow] = useState(null) - const [isGeneratingCell, setIsGeneratingCell] = useState(null) - const [isGeneratingTopicSuggestions, setIsGeneratingTopicSuggestions] = useState(false) - const [isGeneratingDimensionSuggestions, setIsGeneratingDimensionSuggestions] = useState<{ - [dimensionId: string]: boolean - }>({}) - const [isGeneratingDimensionValues, setIsGeneratingDimensionValues] = useState<{ - [dimensionId: string]: boolean - }>({}) - const { message } = App.useApp() - - // 使用工作流 hook - const { - chat, - workflowStatus, - generationState, - generateMetadata, - generateDimensionValues, - generateTableData, - stopGeneration - } = useCrosstabWorkflow(chatId, selectedModel) - - const getLLMConfig = useCallback(() => { - const targetModelId = selectedModel || settings.defaultLLMId - return settings.llmConfigs?.find((config) => config.id === targetModelId) || null - }, [selectedModel, settings.llmConfigs, settings.defaultLLMId]) - - const handleModelChange = useCallback((modelId: string) => { - setSelectedModel(modelId) - }, []) - - // 生成元数据并在完成后显示继续按钮 - const handleGenerateMetadata = useCallback(async () => { - const success = await generateMetadata(userInput) - if (success) { - // 生成成功后保持在当前 tab,用户可以点击继续按钮跳转 - } - }, [generateMetadata, userInput]) - - // 切换到下一个 tab - const handleGoToNextTab = useCallback( - (nextTab: string) => { - setActiveTab(nextTab) - }, - [] - ) - - const handleGenerateColumn = useCallback( - async (columnPath: string) => { - if (!chat || isGeneratingColumn) return - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - if (!chat.crosstabData.metadata) { - message.error('请先完成主题设置') - return - } - - const { verticalDimensions } = chat.crosstabData.metadata - const hasVerticalData = verticalDimensions.every((dim) => dim.values.length > 0) - - if (!hasVerticalData) { - message.error('请先完成纵轴维度数据生成') - return - } - - setIsGeneratingColumn(columnPath) - - try { - const verticalCombinations = generateAxisCombinations( - chat.crosstabData.metadata.verticalDimensions - ) - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - const aiService = createAIService(llmConfig, modelConfig) - - const updatedTableData = { ...chat.crosstabData.tableData } - - for (const vCombination of verticalCombinations) { - const rowPath = generateDimensionPath(vCombination) - const cellKey = `${columnPath}|${rowPath}` - - const taskId = uuidv4() - const task: AITask = { - id: taskId, - requestId: aiService.id, - type: 'crosstab_cell', - status: 'running', - title: '生成单元格数据', - description: `生成单元格 ${columnPath} × ${rowPath} 的数据`, - chatId, - modelId: llmConfig.id, - startTime: Date.now() - } - - addTask(task) - - try { - const prompt = PROMPT_TEMPLATES.cell_values - .replace('[METADATA_JSON]', JSON.stringify(chat.crosstabData.metadata, null, 2)) - .replace('[HORIZONTAL_PATH]', columnPath) - .replace('[VERTICAL_PATH]', rowPath) - .replace( - '[VALUE_DIMENSIONS]', - JSON.stringify(chat.crosstabData.metadata.valueDimensions, null, 2) - ) - - const response = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - const jsonContent = extractJsonContent(response) - const cellValues = JSON.parse(jsonContent) - - const processedCellValues: { [key: string]: string } = {} - if (chat.crosstabData.metadata.valueDimensions.length > 0) { - const valueDimensions = chat.crosstabData.metadata.valueDimensions - - const keys = Object.keys(cellValues) - const hasGenericKeys = keys.some((key) => key.match(/^value\d+$/)) - - if (hasGenericKeys) { - valueDimensions.forEach((dimension, index) => { - const genericKey = `value${index + 1}` - if (cellValues[genericKey]) { - processedCellValues[dimension.id] = cellValues[genericKey] - } - }) - } else { - valueDimensions.forEach((dimension) => { - if (cellValues[dimension.id]) { - processedCellValues[dimension.id] = cellValues[dimension.id] - } - }) - } - - if ( - Object.keys(processedCellValues).length === 0 && - Object.keys(cellValues).length > 0 - ) { - const firstDimension = valueDimensions[0] - const firstValue = Object.values(cellValues)[0] - processedCellValues[firstDimension.id] = firstValue as string - } - } - - updatedTableData[cellKey] = processedCellValues - - updateTask(taskId, { - status: 'completed', - endTime: Date.now() - }) - } catch (error) { - updateTask(taskId, { - status: 'failed', - endTime: Date.now(), - error: error instanceof Error ? error.message : 'Unknown error' - }) - console.error(`单元格 ${cellKey} 生成失败:`, error) - } - } - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - message.success(`列 "${columnPath}" 数据生成完成`) - } catch (error) { - console.error('列数据生成失败:', error) - message.error(`列数据生成失败: ${(error as Error).message}`) - } finally { - setIsGeneratingColumn(null) - } - }, - [ - chat, - isGeneratingColumn, - getLLMConfig, - message, - chatId, - addTask, - updateTask, - updateCrosstabData, - getModelConfigForLLM - ] - ) - - const handleGenerateRow = useCallback( - async (rowPath: string) => { - if (!chat || isGeneratingRow) return - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - if (!chat.crosstabData.metadata) { - message.error('请先完成主题设置') - return - } - - const { horizontalDimensions } = chat.crosstabData.metadata - const hasHorizontalData = horizontalDimensions.every((dim) => dim.values.length > 0) - - if (!hasHorizontalData) { - message.error('请先完成横轴维度数据生成') - return - } - - setIsGeneratingRow(rowPath) - - try { - const horizontalCombinations = generateAxisCombinations( - chat.crosstabData.metadata.horizontalDimensions - ) - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - const aiService = createAIService(llmConfig, modelConfig) - - const updatedTableData = { ...chat.crosstabData.tableData } - - for (const hCombination of horizontalCombinations) { - const columnPath = generateDimensionPath(hCombination) - const cellKey = `${columnPath}|${rowPath}` - - const taskId = uuidv4() - const task: AITask = { - id: taskId, - requestId: aiService.id, - type: 'crosstab_cell', - status: 'running', - title: '生成单元格数据', - description: `生成单元格 ${columnPath} × ${rowPath} 的数据`, - chatId, - modelId: llmConfig.id, - startTime: Date.now() - } - - addTask(task) - - try { - const prompt = PROMPT_TEMPLATES.cell_values - .replace('[METADATA_JSON]', JSON.stringify(chat.crosstabData.metadata, null, 2)) - .replace('[HORIZONTAL_PATH]', columnPath) - .replace('[VERTICAL_PATH]', rowPath) - .replace( - '[VALUE_DIMENSIONS]', - JSON.stringify(chat.crosstabData.metadata.valueDimensions, null, 2) - ) - - const response = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - const jsonContent = extractJsonContent(response) - const cellValues = JSON.parse(jsonContent) - - const processedCellValues: { [key: string]: string } = {} - if (chat.crosstabData.metadata.valueDimensions.length > 0) { - const valueDimensions = chat.crosstabData.metadata.valueDimensions - - const keys = Object.keys(cellValues) - const hasGenericKeys = keys.some((key) => key.match(/^value\d+$/)) - - if (hasGenericKeys) { - valueDimensions.forEach((dimension, index) => { - const genericKey = `value${index + 1}` - if (cellValues[genericKey]) { - processedCellValues[dimension.id] = cellValues[genericKey] - } - }) - } else { - valueDimensions.forEach((dimension) => { - if (cellValues[dimension.id]) { - processedCellValues[dimension.id] = cellValues[dimension.id] - } - }) - } - - if ( - Object.keys(processedCellValues).length === 0 && - Object.keys(cellValues).length > 0 - ) { - const firstDimension = valueDimensions[0] - const firstValue = Object.values(cellValues)[0] - processedCellValues[firstDimension.id] = firstValue as string - } - } - - updatedTableData[cellKey] = processedCellValues - - updateTask(taskId, { - status: 'completed', - endTime: Date.now() - }) - } catch (error) { - updateTask(taskId, { - status: 'failed', - endTime: Date.now(), - error: error instanceof Error ? error.message : 'Unknown error' - }) - console.error(`单元格 ${cellKey} 生成失败:`, error) - } - } - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - message.success(`行 "${rowPath}" 数据生成完成`) - } catch (error) { - console.error('行数据生成失败:', error) - message.error(`行数据生成失败: ${(error as Error).message}`) - } finally { - setIsGeneratingRow(null) - } - }, - [chat, isGeneratingRow, getLLMConfig, message, chatId, addTask, updateTask, updateCrosstabData, getModelConfigForLLM] - ) - - const handleGenerateCell = useCallback( - async (columnPath: string, rowPath: string) => { - if (!chat || isGeneratingCell) return - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - if (!chat.crosstabData.metadata) { - message.error('请先完成主题设置') - return - } - - const cellKey = `${columnPath}|${rowPath}` - setIsGeneratingCell(cellKey) - - const taskId = uuidv4() - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - const aiService = createAIService(llmConfig, modelConfig) - - const task: AITask = { - id: taskId, - requestId: aiService.id, - type: 'crosstab_cell', - status: 'running', - title: '生成单元格数据', - description: `生成单元格 ${columnPath} × ${rowPath} 的数据`, - chatId, - modelId: llmConfig.id, - startTime: Date.now() - } - - addTask(task) - - try { - const prompt = PROMPT_TEMPLATES.cell_values - .replace('[METADATA_JSON]', JSON.stringify(chat.crosstabData.metadata, null, 2)) - .replace('[HORIZONTAL_PATH]', columnPath) - .replace('[VERTICAL_PATH]', rowPath) - .replace( - '[VALUE_DIMENSIONS]', - JSON.stringify(chat.crosstabData.metadata.valueDimensions, null, 2) - ) - - const response = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - const jsonContent = extractJsonContent(response) - const cellValues = JSON.parse(jsonContent) - - const processedCellValues: { [key: string]: string } = {} - - if (chat.crosstabData.metadata.valueDimensions.length > 0) { - const valueDimensions = chat.crosstabData.metadata.valueDimensions - - const keys = Object.keys(cellValues) - const hasGenericKeys = keys.some((key) => key.match(/^value\d+$/)) - - if (hasGenericKeys) { - valueDimensions.forEach((dimension, index) => { - const genericKey = `value${index + 1}` - if (cellValues[genericKey]) { - processedCellValues[dimension.id] = cellValues[genericKey] - } - }) - } else { - valueDimensions.forEach((dimension) => { - if (cellValues[dimension.id]) { - processedCellValues[dimension.id] = cellValues[dimension.id] - } - }) - } - - if (Object.keys(processedCellValues).length === 0 && Object.keys(cellValues).length > 0) { - const firstDimension = valueDimensions[0] - const firstValue = Object.values(cellValues)[0] - processedCellValues[firstDimension.id] = firstValue as string - } - } - - const updatedTableData = { ...chat.crosstabData.tableData } - updatedTableData[cellKey] = processedCellValues - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - updateTask(taskId, { - status: 'completed', - endTime: Date.now() - }) - - message.success('单元格数据生成完成') - } catch (error) { - console.error('单元格生成失败:', error) - message.error(`单元格生成失败: ${(error as Error).message}`) - - updateTask(taskId, { - status: 'failed', - endTime: Date.now(), - error: error instanceof Error ? error.message : 'Unknown error' - }) - } finally { - setIsGeneratingCell(null) - } - }, - [chat, isGeneratingCell, getLLMConfig, addTask, updateTask, updateCrosstabData, chatId, message, getModelConfigForLLM] - ) - - const handleClearColumn = useCallback( - (columnPath: string) => { - if (!chat) return - - const updatedTableData = { ...chat.crosstabData.tableData } - - Object.keys(updatedTableData).forEach((cellKey) => { - if (cellKey.startsWith(columnPath + '|')) { - delete updatedTableData[cellKey] - } - }) - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - message.success(`列 "${columnPath}" 数据已清除`) - }, - [chat, updateCrosstabData, chatId, message] - ) - - const handleClearRow = useCallback( - (rowPath: string) => { - if (!chat) return - - const updatedTableData = { ...chat.crosstabData.tableData } - - Object.keys(updatedTableData).forEach((cellKey) => { - if (cellKey.endsWith('|' + rowPath)) { - delete updatedTableData[cellKey] - } - }) - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - message.success(`行 "${rowPath}" 数据已清除`) - }, - [chat, updateCrosstabData, chatId, message] - ) - - const handleClearCell = useCallback( - (columnPath: string, rowPath: string) => { - if (!chat) return - - const updatedTableData = { ...chat.crosstabData.tableData } - const cellKey = `${columnPath}|${rowPath}` - - if (updatedTableData[cellKey]) { - delete updatedTableData[cellKey] - } - - updateCrosstabData(chatId, { tableData: updatedTableData }) - - message.success(`单元格 "${columnPath} × ${rowPath}" 数据已清除`) - }, - [chat, updateCrosstabData, chatId, message] - ) - - const handleCreateChatFromCell = useCallback( - (columnPath: string, rowPath: string, cellContent: string, metadata: any) => { - if (!chat || !metadata) return - - const newChatId = usePagesStore.getState().createChatFromCell({ - folderId: chat.folderId, - horizontalItem: columnPath, - verticalItem: rowPath, - cellContent, - metadata, - sourcePageId: chat.id - }) - - message.success(`已创建新聊天窗口分析 "${columnPath} × ${rowPath}"`) - }, - [chat, message] - ) - - const handleUpdateMetadata = useCallback( - (metadata: any) => { - updateCrosstabData(chatId, { metadata }) - }, - [updateCrosstabData, chatId] - ) - - const handleUpdateDimension = useCallback( - (dimensionId: string, dimensionType: 'horizontal' | 'vertical', updates: any) => { - if (!chat || !chat.crosstabData.metadata) return - - const metadata = chat.crosstabData.metadata - const dimensionsKey = - dimensionType === 'horizontal' ? 'horizontalDimensions' : 'verticalDimensions' - const dimensions = metadata[dimensionsKey] - - const updatedDimensions = dimensions.map((dim) => - dim.id === dimensionId ? { ...dim, ...updates } : dim - ) - - const updatedMetadata = { - ...metadata, - [dimensionsKey]: updatedDimensions - } - - updateCrosstabData(chatId, { metadata: updatedMetadata }) - message.success('维度已更新') - }, - [chat, chatId, updateCrosstabData, message] - ) - - const handleGenerateDimensionValues = useCallback( - async (dimensionId: string, dimensionType: 'horizontal' | 'vertical') => { - if (!chat || !chat.crosstabData.metadata) { - message.error('请先完成主题分析') - return - } - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - setIsGeneratingDimensionValues((prev) => ({ ...prev, [dimensionId]: true })) - - try { - const dimensions = - dimensionType === 'horizontal' - ? chat.crosstabData.metadata.horizontalDimensions - : chat.crosstabData.metadata.verticalDimensions - - const dimension = dimensions.find((d) => d.id === dimensionId) - if (!dimension) { - message.error('找不到指定的维度') - return - } - - const prompt = PROMPT_TEMPLATES.dimension_values - .replace('[METADATA_JSON]', JSON.stringify(chat.crosstabData.metadata, null, 2)) - .replace('[DIMENSION_ID]', dimension.id) - .replace('[DIMENSION_NAME]', dimension.name) - .replace('[DIMENSION_DESCRIPTION]', dimension.description || '') - - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - - const aiService = createAIService(llmConfig, modelConfig) - const result = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - const jsonContent = extractJsonContent(result) - const values = JSON.parse(jsonContent) - - handleUpdateDimension(dimensionId, dimensionType, { values }) - message.success(`维度"${dimension.name}"的值生成完成`) - } catch (error) { - console.error('维度值生成失败:', error) - message.error(`维度值生成失败: ${(error as Error).message}`) - } finally { - setIsGeneratingDimensionValues((prev) => ({ ...prev, [dimensionId]: false })) - } - }, - [chat, getLLMConfig, handleUpdateDimension, message, getModelConfigForLLM] - ) - - const handleGenerateTopicSuggestions = useCallback(async () => { - if (!chat || isGeneratingTopicSuggestions) return - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - if (!chat.crosstabData.metadata) { - message.error('请先完成主题设置') - return - } - - setIsGeneratingTopicSuggestions(true) - - const taskId = uuidv4() - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - const aiService = createAIService(llmConfig, modelConfig) - - const task: AITask = { - id: taskId, - requestId: aiService.id, - type: 'crosstab_cell', - status: 'running', - title: '生成主题候选项', - description: `为主题 "${chat.crosstabData.metadata.topic}" 生成相关候选项`, - chatId, - modelId: llmConfig.id, - startTime: Date.now(), - context: { - crosstab: { - horizontalItem: 'suggestions', - verticalItem: 'topic', - metadata: chat.crosstabData.metadata - } - } - } - - addTask(task) - - try { - const prompt = PROMPT_TEMPLATES.topicSuggestions.replace( - '[CURRENT_TOPIC]', - chat.crosstabData.metadata.topic - ) - const response = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - try { - const parsedSuggestions = JSON.parse(extractJsonContent(response)) - if (Array.isArray(parsedSuggestions)) { - const newMetadata = { - ...chat.crosstabData.metadata!, - topicSuggestions: parsedSuggestions - } - updateCrosstabData(chatId, { metadata: newMetadata }) - - updateTask(taskId, { - status: 'completed', - endTime: Date.now() - }) - - message.success('主题候选项生成完成') - } else { - throw new Error('返回的不是数组格式') - } - } catch (e) { - message.error('解析主题候选项失败') - throw e - } - } catch (error) { - console.error('Topic suggestions generation error:', error) - message.error('主题候选项生成失败,请重试') - - updateTask(taskId, { - status: 'failed', - endTime: Date.now(), - error: error instanceof Error ? error.message : 'Unknown error' - }) - } finally { - setIsGeneratingTopicSuggestions(false) - } - }, [ - chat, - isGeneratingTopicSuggestions, - getLLMConfig, - addTask, - updateTask, - updateCrosstabData, - chatId, - message, - getModelConfigForLLM - ]) - - const handleGenerateDimensionSuggestions = useCallback( - async (dimensionId: string) => { - if (!chat || !chat.crosstabData.metadata) { - message.error('请先完成主题设置') - return - } - - if (isGeneratingDimensionSuggestions[dimensionId]) return - - const llmConfig = getLLMConfig() - if (!llmConfig) { - message.error('请先在设置中配置LLM') - return - } - - setIsGeneratingDimensionSuggestions((prev) => ({ ...prev, [dimensionId]: true })) - - const taskId = uuidv4() - const modelConfig = getModelConfigForLLM(llmConfig.id) - if (!modelConfig) { - message.error('请先在设置中配置模型参数') - return - } - const aiService = createAIService(llmConfig, modelConfig) - - const allDimensions = [ - ...chat.crosstabData.metadata.horizontalDimensions, - ...chat.crosstabData.metadata.verticalDimensions, - ...chat.crosstabData.metadata.valueDimensions - ] - const dimension = allDimensions.find((d) => d.id === dimensionId) - - if (!dimension) { - message.error('找不到指定的维度') - setIsGeneratingDimensionSuggestions((prev) => ({ ...prev, [dimensionId]: false })) - return - } - - const task: AITask = { - id: taskId, - requestId: aiService.id, - type: 'crosstab_cell', - status: 'running', - title: '生成维度候选项', - description: `为维度 "${dimension.name}" 生成候选项`, - chatId, - modelId: llmConfig.id, - startTime: Date.now(), - context: { - crosstab: { - horizontalItem: 'suggestions', - verticalItem: dimensionId, - metadata: chat.crosstabData.metadata - } - } - } - - addTask(task) - - try { - const prompt = PROMPT_TEMPLATES.dimensionSuggestions - .replace('[METADATA_JSON]', JSON.stringify(chat.crosstabData.metadata, null, 2)) - .replace( - '[DIMENSION_TYPE]', - dimension.id.startsWith('h') - ? 'horizontal' - : dimension.id.startsWith('v') - ? 'vertical' - : 'value' - ) - .replace('[DIMENSION_NAME]', dimension.name) - .replace('[DIMENSION_DESCRIPTION]', (dimension as any).description || '') - - const response = await new Promise((resolve, reject) => { - aiService.sendMessage( - [{ id: 'temp', role: 'user', content: prompt, timestamp: Date.now() }], - { - onChunk: () => {}, - onComplete: (response) => resolve(response), - onError: (error) => reject(error) - } - ) - }) - - try { - const parsedSuggestions = JSON.parse(extractJsonContent(response)) - if (Array.isArray(parsedSuggestions)) { - const metadata = chat.crosstabData.metadata - let updatedMetadata = { ...metadata } - - if (dimension.id.startsWith('h')) { - updatedMetadata.horizontalDimensions = updatedMetadata.horizontalDimensions.map( - (d) => (d.id === dimensionId ? { ...d, suggestions: parsedSuggestions } : d) - ) - } else if (dimension.id.startsWith('v')) { - updatedMetadata.verticalDimensions = updatedMetadata.verticalDimensions.map((d) => - d.id === dimensionId ? { ...d, suggestions: parsedSuggestions } : d - ) - } else { - updatedMetadata.valueDimensions = updatedMetadata.valueDimensions.map((d) => - d.id === dimensionId ? { ...d, suggestions: parsedSuggestions } : d - ) - } - - updateCrosstabData(chatId, { metadata: updatedMetadata }) - - updateTask(taskId, { - status: 'completed', - endTime: Date.now() - }) - - message.success('维度候选项生成完成') - } else { - throw new Error('返回的不是数组格式') - } - } catch (e) { - message.error('解析维度候选项失败') - throw e - } - } catch (error) { - console.error('Dimension suggestions generation error:', error) - message.error('维度候选项生成失败,请重试') - - updateTask(taskId, { - status: 'failed', - endTime: Date.now(), - error: error instanceof Error ? error.message : 'Unknown error' - }) - } finally { - setIsGeneratingDimensionSuggestions((prev) => ({ ...prev, [dimensionId]: false })) - } - }, - [ - chat, - isGeneratingDimensionSuggestions, - getLLMConfig, - addTask, - updateTask, - updateCrosstabData, - chatId, - message, - getModelConfigForLLM - ] - ) - - const handleSelectTopicSuggestion = useCallback( - (suggestion: string) => { - if (!chat) return - - const newMetadata = { - ...chat.crosstabData.metadata!, - topic: suggestion - } - - updateCrosstabData(chatId, { - metadata: newMetadata, - tableData: {} - }) - message.success('主题已更新') - }, - [chat, updateCrosstabData, chatId, message] - ) - - // 渲染 Tab 标签(带状态徽章) - const renderTabLabel = (icon: React.ReactNode, label: string, completed: boolean) => ( - - {icon} - {label} - {completed && ( - - )} - - ) - - if (!chat) { - return
交叉视图聊天不存在
- } - - const tabItems = [ - { - key: '0', - label: renderTabLabel(, '输入主题', workflowStatus.topicCompleted), - children: ( - handleGoToNextTab('1')} - /> - ) - }, - { - key: '1', - label: renderTabLabel(, '主题结构', workflowStatus.structureCompleted), - children: ( - handleGoToNextTab('2')} - /> - ) - }, - { - key: '2', - label: renderTabLabel(, '轴数据', workflowStatus.axisDataCompleted), - children: ( - handleGoToNextTab('3')} - /> - ) - }, - { - key: '3', - label: renderTabLabel(, '交叉分析表', workflowStatus.tableDataCompleted), - children: ( - - ) - } - ] - - return ( -
- {/* 页面溯源信息 */} - - -
-
- - <TableOutlined /> 交叉视图生成器 - - - {/* 模型选择器 */} - - 模型选择: - - -
-
- -
-
- -
-
-
- ) -} diff --git a/src/renderer/src/components/pages/crosstab/CrosstabTable.tsx b/src/renderer/src/components/pages/crosstab/CrosstabTable.tsx deleted file mode 100644 index ea1dbb0..0000000 --- a/src/renderer/src/components/pages/crosstab/CrosstabTable.tsx +++ /dev/null @@ -1,553 +0,0 @@ -import React, { useMemo, useState, useEffect } from 'react' -import { Card, Typography, Button, Tooltip, Space, Tag, Dropdown } from 'antd' -import { - TableOutlined, - FullscreenOutlined, - FullscreenExitOutlined, - PlayCircleOutlined, - LoadingOutlined, - DeleteOutlined, - CommentOutlined, - MoreOutlined -} from '@ant-design/icons' -import { CrosstabMetadata, CrosstabMultiDimensionData } from '../../../types/type' -import { generateAxisCombinations, generateDimensionPath } from './CrosstabUtils' -import './crosstab-table.css' - -const { Text } = Typography - -interface CrosstabTableProps { - metadata: CrosstabMetadata | null - tableData: CrosstabMultiDimensionData - onGenerateColumn?: (columnPath: string) => void - isGeneratingColumn?: string | null - onGenerateRow?: (rowPath: string) => void - isGeneratingRow?: string | null - onGenerateCell?: (columnPath: string, rowPath: string) => void - isGeneratingCell?: string | null - onClearColumn?: (columnPath: string) => void - onClearRow?: (rowPath: string) => void - onClearCell?: (columnPath: string, rowPath: string) => void - onCreateChatFromCell?: ( - columnPath: string, - rowPath: string, - cellContent: string, - metadata: CrosstabMetadata | null - ) => void -} - -export default function CrosstabTable({ - metadata, - tableData, - onGenerateColumn, - isGeneratingColumn, - onGenerateRow, - isGeneratingRow, - onGenerateCell, - isGeneratingCell, - onClearColumn, - onClearRow, - onClearCell, - onCreateChatFromCell -}: CrosstabTableProps) { - const [isFullscreen, setIsFullscreen] = useState(false) - const [selectedValueDimension, setSelectedValueDimension] = useState('') - - // 初始化选中的值维度 - useEffect(() => { - if (metadata && metadata.valueDimensions.length > 0) { - if ( - !selectedValueDimension || - !metadata.valueDimensions.find((d) => d.id === selectedValueDimension) - ) { - setSelectedValueDimension(metadata.valueDimensions[0].id) - } - } - }, [metadata, selectedValueDimension]) - - // 生成多维度网格数据 - const { horizontalCombinations, verticalCombinations, gridData } = useMemo(() => { - if (!metadata || !metadata.horizontalDimensions.length || !metadata.verticalDimensions.length) { - return { horizontalCombinations: [], verticalCombinations: [], gridData: {} } - } - - const horizontalCombinations = generateAxisCombinations(metadata.horizontalDimensions) - const verticalCombinations = generateAxisCombinations(metadata.verticalDimensions) - - // 生成网格数据 - const gridData: { [key: string]: string } = {} - - verticalCombinations.forEach((vCombination) => { - const vPath = generateDimensionPath(vCombination) - horizontalCombinations.forEach((hCombination) => { - const hPath = generateDimensionPath(hCombination) - const cellKey = `${hPath}|${vPath}` - const cellData = tableData[cellKey] - - // 数据处理逻辑 - - if (cellData && selectedValueDimension) { - gridData[cellKey] = cellData[selectedValueDimension] || '' - } else if (cellData && !selectedValueDimension && metadata.valueDimensions.length > 0) { - const firstValueDimension = metadata.valueDimensions[0].id - gridData[cellKey] = cellData[firstValueDimension] || '' - } else if (!gridData[cellKey] && cellData && Object.keys(cellData).length > 0) { - const firstAvailableKey = Object.keys(cellData)[0] - gridData[cellKey] = cellData[firstAvailableKey] || '' - } else { - gridData[cellKey] = '' - } - }) - }) - - return { horizontalCombinations, verticalCombinations, gridData } - }, [metadata, tableData, selectedValueDimension]) - - // 处理全屏切换 - const toggleFullscreen = () => { - setIsFullscreen(!isFullscreen) - } - - // 处理ESC键退出全屏 - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape' && isFullscreen) { - setIsFullscreen(false) - } - } - - if (isFullscreen) { - document.addEventListener('keydown', handleKeyDown) - return () => { - document.removeEventListener('keydown', handleKeyDown) - } - } - }, [isFullscreen]) - - if (!metadata || horizontalCombinations.length === 0 || verticalCombinations.length === 0) { - return ( - -
- -
- 尚未生成多维度交叉分析表 -
- 请先完成前面的步骤,然后生成表格数据 -
-
-
- ) - } - - // 创建列头菜单 - const createColumnMenu = (hPath: string, hasColumnData: boolean) => { - const menuItems: any[] = [ - { - key: 'generate', - icon: React.createElement( - isGeneratingColumn === hPath ? LoadingOutlined : PlayCircleOutlined - ), - label: hasColumnData ? '重新生成此列' : '生成此列', - onClick: () => onGenerateColumn && onGenerateColumn(hPath), - disabled: isGeneratingColumn !== null - } - ] - - if (hasColumnData && onClearColumn) { - menuItems.push({ - key: 'clear', - icon: React.createElement(DeleteOutlined), - label: '清除此列', - onClick: () => onClearColumn(hPath) - }) - } - - return menuItems - } - - // 创建行头菜单 - const createRowMenu = (vPath: string, hasRowData: boolean) => { - const menuItems: any[] = [ - { - key: 'generate', - icon: React.createElement(isGeneratingRow === vPath ? LoadingOutlined : PlayCircleOutlined), - label: hasRowData ? '重新生成此行' : '生成此行', - onClick: () => onGenerateRow && onGenerateRow(vPath), - disabled: isGeneratingRow !== null - } - ] - - if (hasRowData && onClearRow) { - menuItems.push({ - key: 'clear', - icon: React.createElement(DeleteOutlined), - label: '清除此行', - onClick: () => onClearRow(vPath) - }) - } - - return menuItems - } - - // 创建单元格菜单 - const createCellMenu = (hPath: string, vPath: string, cellContent: string) => { - const cellKey = `${hPath}|${vPath}` - const isGenerating = isGeneratingCell === cellKey - - const menuItems: any[] = [ - { - key: 'generate', - icon: React.createElement(isGenerating ? LoadingOutlined : PlayCircleOutlined), - label: cellContent ? '重新生成' : '生成内容', - onClick: () => onGenerateCell && onGenerateCell(hPath, vPath), - disabled: isGeneratingCell !== null - } - ] - - if (cellContent && onClearCell) { - menuItems.push({ - key: 'clear', - icon: React.createElement(DeleteOutlined), - label: '清除内容', - onClick: () => onClearCell(hPath, vPath) - }) - } - - if (cellContent && onCreateChatFromCell) { - menuItems.push({ - key: 'chat', - icon: React.createElement(CommentOutlined), - label: '创建对话', - onClick: () => onCreateChatFromCell(hPath, vPath, cellContent, metadata) - }) - } - - return menuItems - } - - // 创建extra内容(值维度选择器和全屏按钮) - const extraContent = ( - - {/* 值维度选择器 */} - {metadata.valueDimensions.length > 1 && ( -
- 值维度: - - {metadata.valueDimensions.map((dim) => ( - setSelectedValueDimension(dim.id)} - > - {dim.name} - - ))} - -
- )} - - {/* 全屏按钮 */} - -