@yancyyu/openhermit 1.6.38 → 1.6.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-ZEDfZyHb.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CIhniz70.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-cKAW4Q8I.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-YmNsoDXW.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DHEls2sX.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-Bpwf1Sbg.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B0IaQ4w5.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLk-hcFc.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-1XRmX_Zm.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-1waH1DAD.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-BqpZBtrN.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Bly7vVym.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-Ci2QWBAs.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-YCqFW7d-.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-B0xGXInl.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
  20. package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DxcFNQKT.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DPo_RfZY.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-U3hQsFe4.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-OrwrAy0V.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CXATPWVw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-B0e8AfMF.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CXfzA4jJ.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CMr08qVl.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vYFHpPmy.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-DOe5j8dH.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-BySQS7AB.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-V7dAKPqd.js → index-Bu2R-Se7.js} +587 -705
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-CzWxVCRL.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-VJ-MM9xa.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-B2Dy7M2G.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-C_okzZXP.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D_WubR0B.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-w9ca-1TI.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-Jg9p6_pN.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-B-z3y17c.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-D-RTX5UW.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-CDQmHOYP.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D_odsQL7.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BRsmYWSA.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-ChNE_BOV.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8FtpwKc.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DmLCzNcc.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-WJBm4bhu.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
  51. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
  52. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BXs_hOJs.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-o04MA0G9.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-Czj69XRd.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
  55. package/dist-renderer/index.html +20 -53
  56. package/package.json +25 -18
  57. package/src/main/ipc/extensions.ts +2 -1
  58. package/src/main/server.ts +873 -221
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +2 -5
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  62. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  63. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  64. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  65. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  66. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  67. package/src/main/services/teams-mvp/TaskDispatchService.ts +5 -6
  68. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  69. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  70. package/src/main/utils/teamProjectResolution.ts +15 -0
  71. package/src/renderer/App.tsx +8 -4
  72. package/src/renderer/api/httpClient.ts +68 -18
  73. package/src/renderer/api/providers.ts +23 -2
  74. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  75. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  76. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  87. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  88. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  89. package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -125
  90. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  91. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  92. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  93. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  94. package/src/renderer/components/extensions/plugins/PluginCard.tsx +6 -6
  95. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +34 -21
  96. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  97. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  98. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  99. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  100. package/src/renderer/components/layout/TabBar.tsx +1 -1
  101. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  102. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  103. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  104. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  105. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  106. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  107. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  108. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  109. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  110. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  111. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  112. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  113. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  114. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  115. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  116. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  117. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  118. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  119. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  120. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  121. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  122. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  123. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  124. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  125. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  126. package/src/renderer/components/settings/sections/index.ts +0 -1
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  128. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  129. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  130. package/src/renderer/components/splash/splashScene.ts +121 -929
  131. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  132. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  133. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  134. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  135. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  136. package/src/renderer/components/team/TeamDetailView.tsx +319 -123
  137. package/src/renderer/components/team/TeamListView.tsx +108 -123
  138. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  139. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  140. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  141. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  142. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  143. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  144. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  145. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  146. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  147. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  148. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  149. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  150. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  151. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  152. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  153. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  154. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  155. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  156. package/src/renderer/components/team/tools/AddMcpInline.tsx +27 -17
  157. package/src/renderer/components/team/tools/McpChip.tsx +6 -3
  158. package/src/renderer/components/team/tools/SkillChip.tsx +2 -2
  159. package/src/renderer/components/team/tools/ToolsSection.tsx +418 -70
  160. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  161. package/src/renderer/index.css +39 -22
  162. package/src/renderer/index.html +17 -50
  163. package/src/renderer/store/index.ts +2 -1
  164. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  165. package/src/renderer/store/slices/teamSlice.ts +45 -168
  166. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  167. package/src/renderer/utils/memberHelpers.ts +5 -17
  168. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  169. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  170. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  171. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  172. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  173. package/src/shared/types/api.ts +121 -2
  174. package/src/shared/types/ccConnect.ts +2 -0
  175. package/src/shared/types/index.ts +3 -0
  176. package/src/shared/types/systemManager.ts +49 -0
  177. package/src/shared/types/team.ts +29 -0
  178. package/src/shared/types/terminal.ts +4 -2
  179. package/src/shared/utils/extensionNormalizers.ts +15 -8
  180. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  181. package/dist-renderer/assets/ProjectEditorOverlay-lJZi-9Hp.js +0 -52
  182. package/dist-renderer/assets/channel-yIlSKy0e.js +0 -1
  183. package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +0 -1
  184. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +0 -1
  185. package/dist-renderer/assets/clone-BTNuUva-.js +0 -1
  186. package/dist-renderer/assets/index-Bi6nrZ4z.css +0 -1
  187. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  188. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-_m6iPPUR.js +0 -1
@@ -5,14 +5,18 @@
5
5
  *
6
6
  * 改进:
7
7
  * - 分组:live 会话在上,inactive 在下
8
- * - 分页:默认 8 条,"加载更多"
9
- * - 取消按钮:live 会话右侧红色 X(hover 可见)
8
+ * - 运行中和历史会话统一展示在团队详情中
9
+ * - 点击会话行可展开最近历史消息
10
10
  */
11
11
 
12
- import { useState } from 'react';
12
+ import { useCallback, useEffect, useState } from 'react';
13
+
14
+ import { api } from '@renderer/api';
13
15
  import { formatDistanceToNowStrict } from 'date-fns';
14
16
  import {
15
17
  AlertCircle,
18
+ ChevronDown,
19
+ ChevronRight,
16
20
  Loader2,
17
21
  MessageSquare,
18
22
  Monitor,
@@ -22,24 +26,46 @@ import {
22
26
  X,
23
27
  } from 'lucide-react';
24
28
 
25
- import type { CcSession } from '@shared/types';
29
+ import type { CcSession, CcSessionDetail } from '@shared/types';
26
30
 
27
- const PAGE_SIZE = 8;
31
+ const SESSION_DETAIL_PAGE_SIZE = 12;
32
+ const HISTORICAL_SESSION_PAGE_SIZE = 10;
28
33
 
29
34
  interface CcSessionsSectionProps {
35
+ teamName: string;
30
36
  sessions: CcSession[];
31
37
  loading: boolean;
32
38
  error: string | null;
33
- onCancelSession?: (sessionId: string) => void;
34
39
  }
35
40
 
36
41
  export function CcSessionsSection({
42
+ teamName,
37
43
  sessions,
38
44
  loading,
39
45
  error,
40
- onCancelSession,
41
46
  }: CcSessionsSectionProps): React.JSX.Element {
42
- const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
47
+ const [expandedSessionId, setExpandedSessionId] = useState<string | null>(null);
48
+ const [visibleHistoricalCount, setVisibleHistoricalCount] = useState(
49
+ HISTORICAL_SESSION_PAGE_SIZE
50
+ );
51
+
52
+ useEffect(() => {
53
+ setExpandedSessionId((current) =>
54
+ current && sessions.some((session) => session.id === current) ? current : null
55
+ );
56
+ }, [sessions]);
57
+
58
+ useEffect(() => {
59
+ setVisibleHistoricalCount(HISTORICAL_SESSION_PAGE_SIZE);
60
+ }, [teamName]);
61
+
62
+ const toggleExpandedSession = useCallback((sessionId: string) => {
63
+ setExpandedSessionId((current) => (current === sessionId ? null : sessionId));
64
+ }, []);
65
+
66
+ const loadMoreHistoricalSessions = useCallback(() => {
67
+ setVisibleHistoricalCount((current) => current + HISTORICAL_SESSION_PAGE_SIZE);
68
+ }, []);
43
69
 
44
70
  if (loading) {
45
71
  return (
@@ -73,131 +99,431 @@ export function CcSessionsSection({
73
99
  );
74
100
  const liveSessions = allSorted.filter((s) => s.live);
75
101
  const inactiveSessions = allSorted.filter((s) => !s.live);
76
- const displayed = allSorted.slice(0, visibleCount);
77
- const hasMore = visibleCount < allSorted.length;
78
-
102
+ const visibleInactiveSessions = inactiveSessions.slice(0, visibleHistoricalCount);
103
+ const hiddenHistoricalCount = Math.max(
104
+ inactiveSessions.length - visibleInactiveSessions.length,
105
+ 0
106
+ );
79
107
  return (
80
108
  <div className="space-y-1">
81
- {/* Live 分组标题 */}
82
109
  {liveSessions.length > 0 && (
83
110
  <div className="px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-emerald-400/70">
84
111
  运行中 ({liveSessions.length})
85
112
  </div>
86
113
  )}
87
- {displayed
88
- .filter((s) => s.live)
89
- .map((s) => (
90
- <CcSessionRow key={s.id} session={s} onCancel={onCancelSession} />
91
- ))}
114
+ {liveSessions.map((s) => (
115
+ <CcSessionRow
116
+ key={s.id}
117
+ teamName={teamName}
118
+ session={s}
119
+ isExpanded={expandedSessionId === s.id}
120
+ onToggleExpanded={toggleExpandedSession}
121
+ />
122
+ ))}
92
123
 
93
- {/* Inactive 分组标题 */}
94
124
  {inactiveSessions.length > 0 && (
95
- <div className="px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">
96
- 历史 ({inactiveSessions.length})
125
+ <div className="flex items-center justify-between px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-muted)]">
126
+ <span>历史 ({inactiveSessions.length})</span>
127
+ {hiddenHistoricalCount > 0 && <span>已显示 {visibleInactiveSessions.length}</span>}
97
128
  </div>
98
129
  )}
99
- {displayed
100
- .filter((s) => !s.live)
101
- .map((s) => (
102
- <CcSessionRow key={s.id} session={s} onCancel={onCancelSession} />
103
- ))}
104
-
105
- {/* 加载更多 */}
106
- {hasMore && (
130
+ {visibleInactiveSessions.map((s) => (
131
+ <CcSessionRow
132
+ key={s.id}
133
+ teamName={teamName}
134
+ session={s}
135
+ isExpanded={expandedSessionId === s.id}
136
+ onToggleExpanded={toggleExpandedSession}
137
+ />
138
+ ))}
139
+ {hiddenHistoricalCount > 0 && (
107
140
  <button
108
141
  type="button"
109
- className="mx-auto mt-2 block rounded-md px-3 py-1 text-xs text-[var(--color-text-muted)] hover:bg-[var(--color-surface-raised)] hover:text-[var(--color-text)]"
110
- onClick={() => setVisibleCount((c) => c + PAGE_SIZE)}
142
+ className="mx-auto mt-2 flex items-center justify-center rounded-full bg-[var(--color-surface-raised)] px-3 py-1.5 text-xs text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text)]"
143
+ onClick={loadMoreHistoricalSessions}
111
144
  >
112
- 加载更多 ({allSorted.length - visibleCount})
145
+ 加载更多历史会话 ({hiddenHistoricalCount})
113
146
  </button>
114
147
  )}
115
148
  </div>
116
149
  );
117
150
  }
118
151
 
152
+ export function isExportPayload(
153
+ value: unknown
154
+ ): value is { filename: string; mimeType: string; content: string } {
155
+ if (!value || typeof value !== 'object') return false;
156
+ const record = value as Record<string, unknown>;
157
+ return (
158
+ typeof record.filename === 'string' &&
159
+ typeof record.mimeType === 'string' &&
160
+ typeof record.content === 'string'
161
+ );
162
+ }
163
+
164
+ export function hasDataRows(csv: string): boolean {
165
+ return (
166
+ csv
167
+ .split('\n')
168
+ .map((line) => line.trim())
169
+ .filter(Boolean).length > 1
170
+ );
171
+ }
172
+
173
+ export function downloadTextFile(content: string, filename: string, mimeType: string): void {
174
+ const blob = new Blob([`${content}`], { type: mimeType });
175
+ const url = URL.createObjectURL(blob);
176
+ const link = document.createElement('a');
177
+ link.href = url;
178
+ link.download = filename;
179
+ document.body.appendChild(link);
180
+ link.click();
181
+ link.remove();
182
+ URL.revokeObjectURL(url);
183
+ }
184
+
185
+ export function buildAllSessionsCsv(
186
+ rows: { session: CcSession; detail: CcSessionDetail | null }[]
187
+ ): string {
188
+ const headers = [
189
+ 'sessionId',
190
+ 'sessionName',
191
+ 'sessionKey',
192
+ 'platform',
193
+ 'userName',
194
+ 'chatName',
195
+ 'messageRole',
196
+ 'messageTimestamp',
197
+ 'messageContent',
198
+ 'inputTokens',
199
+ 'outputTokens',
200
+ 'cacheReadTokens',
201
+ 'cacheCreationTokens',
202
+ 'totalTokens',
203
+ ];
204
+ const lines = [headers.map(csvEscape).join(',')];
205
+
206
+ for (const { session, detail } of rows) {
207
+ const messages = detail?.history?.length
208
+ ? detail.history
209
+ : session.lastMessage
210
+ ? [session.lastMessage]
211
+ : [];
212
+ const sessionName = session.chatName || session.userName || session.title || session.sessionKey;
213
+ for (const message of messages) {
214
+ lines.push(
215
+ [
216
+ session.id,
217
+ sessionName,
218
+ session.sessionKey,
219
+ session.platform,
220
+ session.userName,
221
+ session.chatName,
222
+ message.role,
223
+ message.timestamp,
224
+ message.content,
225
+ '',
226
+ '',
227
+ '',
228
+ '',
229
+ '',
230
+ ]
231
+ .map(csvEscape)
232
+ .join(',')
233
+ );
234
+ }
235
+ }
236
+
237
+ return `${lines.join('\n')}\n`;
238
+ }
239
+
240
+ export function buildAllSessionsCsvFilename(teamName: string): string {
241
+ return `${sanitizeFilename(teamName)}-all-sessions-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`;
242
+ }
243
+
244
+ function csvEscape(value: unknown): string {
245
+ let text = value == null ? '' : String(value);
246
+ if (/^[=+\-@]/.test(text)) text = `'${text}`;
247
+ return `"${text.replace(/"/g, '""')}"`;
248
+ }
249
+
119
250
  function CcSessionRow({
251
+ teamName,
120
252
  session,
121
- onCancel,
253
+ isExpanded,
254
+ onToggleExpanded,
122
255
  }: {
256
+ teamName: string;
123
257
  session: CcSession;
124
- onCancel?: (sessionId: string) => void;
258
+ isExpanded: boolean;
259
+ onToggleExpanded: (sessionId: string) => void;
125
260
  }): React.JSX.Element {
126
261
  const timeAgo = formatShortTime(new Date(session.updatedAt));
127
- const label = session.chatName || session.userName || session.sessionKey;
262
+ const label = session.chatName || session.userName || session.title || session.sessionKey;
128
263
  const platformLabel = session.platform === 'bridge' ? 'Bridge' : session.platform;
264
+ const [detail, setDetail] = useState<CcSessionDetail | null>(null);
265
+ const [loadingDetail, setLoadingDetail] = useState(false);
266
+ const [detailError, setDetailError] = useState<string | null>(null);
267
+ const [historyLimit, setHistoryLimit] = useState(SESSION_DETAIL_PAGE_SIZE);
268
+ const [loadingMoreHistory, setLoadingMoreHistory] = useState(false);
269
+ const hasMoreHistory =
270
+ detail != null &&
271
+ Array.isArray(detail.history) &&
272
+ detail.history.length < Math.max(detail.historyCount ?? 0, detail.history.length);
273
+
274
+ useEffect(() => {
275
+ if (!isExpanded) {
276
+ setDetail(null);
277
+ setDetailError(null);
278
+ setLoadingDetail(false);
279
+ setLoadingMoreHistory(false);
280
+ setHistoryLimit(SESSION_DETAIL_PAGE_SIZE);
281
+ return;
282
+ }
283
+
284
+ let cancelled = false;
285
+ const isIncrementalLoad = historyLimit > SESSION_DETAIL_PAGE_SIZE;
286
+ setDetailError(null);
287
+ setLoadingDetail(true);
288
+ setLoadingMoreHistory(isIncrementalLoad);
289
+ void api.teams
290
+ .getSessionDetail(teamName, session.id, historyLimit)
291
+ .then((nextDetail) => {
292
+ if (!cancelled) setDetail(nextDetail);
293
+ })
294
+ .catch((err) => {
295
+ if (!cancelled) {
296
+ setDetail(null);
297
+ setDetailError(err instanceof Error ? err.message : '加载会话历史失败');
298
+ }
299
+ })
300
+ .finally(() => {
301
+ if (!cancelled) {
302
+ setLoadingDetail(false);
303
+ setLoadingMoreHistory(false);
304
+ }
305
+ });
306
+
307
+ return () => {
308
+ cancelled = true;
309
+ };
310
+ }, [historyLimit, isExpanded, session.id, teamName]);
311
+
312
+ const handleLoadMoreHistory = useCallback(() => {
313
+ if (loadingDetail || loadingMoreHistory || !hasMoreHistory) {
314
+ return;
315
+ }
316
+ setHistoryLimit((prev) => prev + SESSION_DETAIL_PAGE_SIZE);
317
+ }, [hasMoreHistory, loadingDetail, loadingMoreHistory]);
129
318
 
130
319
  return (
131
320
  <div
132
- className={`group relative flex w-full items-start gap-2 rounded-md px-2.5 py-2 text-xs transition-colors ${
133
- session.live
134
- ? 'border border-emerald-500/20 bg-emerald-500/5'
135
- : 'hover:bg-[var(--color-surface-raised)]'
321
+ className={`group relative rounded-xl border transition-colors ${
322
+ isExpanded
323
+ ? 'border-blue-500/20 bg-blue-500/[0.04]'
324
+ : session.live
325
+ ? 'border-emerald-500/20 bg-emerald-500/[0.04] hover:bg-emerald-500/[0.08]'
326
+ : 'border-transparent bg-[var(--color-surface)] hover:border-[var(--color-border)] hover:bg-[var(--color-surface-raised)]'
136
327
  }`}
137
328
  >
138
- {/* 状态指示 */}
139
- <div className="mt-0.5 shrink-0">
140
- {session.live ? (
141
- <Radio size={12} className="animate-pulse text-emerald-400" />
142
- ) : session.active ? (
143
- <Wifi size={12} className="text-blue-400" />
144
- ) : (
145
- <WifiOff size={12} className="text-[var(--color-text-muted)] opacity-50" />
146
- )}
147
- </div>
329
+ <button
330
+ type="button"
331
+ className="group relative flex w-full items-start gap-2 rounded-xl px-3 py-2.5 text-left text-xs"
332
+ onClick={() => onToggleExpanded(session.id)}
333
+ aria-expanded={isExpanded}
334
+ >
335
+ <div className="mt-0.5 shrink-0 rounded-full p-0.5 text-[var(--color-text-muted)] transition-colors group-hover:bg-[var(--color-surface-raised)] group-hover:text-[var(--color-text)]">
336
+ {isExpanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
337
+ </div>
148
338
 
149
- <div className="min-w-0 flex-1">
150
- <div className="flex items-center gap-1.5">
151
- {session.live && (
152
- <span className="size-1.5 shrink-0 animate-pulse rounded-full bg-emerald-400" />
339
+ <div className="mt-0.5 shrink-0">
340
+ {session.live ? (
341
+ <Radio size={12} className="animate-pulse text-emerald-400" />
342
+ ) : session.active ? (
343
+ <Wifi size={12} className="text-blue-400" />
344
+ ) : (
345
+ <WifiOff size={12} className="text-[var(--color-text-muted)] opacity-50" />
153
346
  )}
154
- <span className="truncate font-medium text-[var(--color-text)]">{label}</span>
155
347
  </div>
156
348
 
157
- <div className="mt-0.5 flex items-center gap-2 text-[10px] text-[var(--color-text-muted)]">
158
- <span className="rounded bg-[var(--color-surface-raised)] px-1 py-0.5">
159
- {platformLabel}
160
- </span>
161
- <span className="flex items-center gap-0.5">
162
- <MessageSquare size={9} />
163
- {session.historyCount}
164
- </span>
165
- <span className="tabular-nums">{timeAgo}</span>
166
- {session.live && <span className="text-emerald-500 dark:text-emerald-400">进行中</span>}
167
- </div>
349
+ <div className="min-w-0 flex-1">
350
+ <div className="flex items-center gap-1.5">
351
+ {session.live && (
352
+ <span className="size-1.5 shrink-0 animate-pulse rounded-full bg-emerald-400" />
353
+ )}
354
+ <span className="truncate font-medium text-[var(--color-text)]">{label}</span>
355
+ </div>
168
356
 
169
- {session.lastMessage && (
170
- <p className="mt-1 truncate text-[10px] text-[var(--color-text-muted)]">
171
- <span
172
- className={
173
- session.lastMessage.role === 'user'
174
- ? 'text-blue-400'
175
- : 'text-[var(--color-text-muted)]'
176
- }
177
- >
178
- {session.lastMessage.role === 'user' ? '用户' : 'Agent'}:
357
+ <div className="mt-1 flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
358
+ <span className="rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0.5">
359
+ {platformLabel}
179
360
  </span>
180
- {session.lastMessage.content.slice(0, 80)}
181
- {session.lastMessage.content.length > 80 ? '…' : ''}
182
- </p>
183
- )}
184
- </div>
361
+ <span className="inline-flex items-center gap-0.5 rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0.5">
362
+ <MessageSquare size={9} />
363
+ {session.historyCount}
364
+ </span>
365
+ <span className="tabular-nums">{timeAgo}</span>
366
+ {session.live && <span className="text-emerald-500 dark:text-emerald-400">进行中</span>}
367
+ </div>
185
368
 
186
- {/* 取消按钮 — 仅 live 会话,hover 可见 */}
187
- {session.live && onCancel && (
188
- <button
189
- type="button"
190
- className="absolute right-2 top-2 shrink-0 rounded p-1 opacity-0 transition-opacity hover:bg-red-500/10 hover:text-red-400 group-hover:opacity-100"
191
- onClick={() => onCancel(session.id)}
192
- title="终止会话"
193
- >
194
- <X size={12} />
195
- </button>
369
+ {session.lastMessage && (
370
+ <p className="mt-1.5 truncate text-[10px] leading-relaxed text-[var(--color-text-muted)]">
371
+ <span
372
+ className={
373
+ session.lastMessage.role === 'user'
374
+ ? 'text-blue-400'
375
+ : 'text-[var(--color-text-muted)]'
376
+ }
377
+ >
378
+ {session.lastMessage.role === 'user' ? '用户' : 'Agent'}:
379
+ </span>
380
+ {session.lastMessage.content.slice(0, 96)}
381
+ {session.lastMessage.content.length > 96 ? '…' : ''}
382
+ </p>
383
+ )}
384
+ </div>
385
+ </button>
386
+
387
+
388
+ {isExpanded && (
389
+ <div className="px-3 pb-3">
390
+ {loadingDetail && !detail && (
391
+ <div className="rounded-lg bg-[var(--color-surface)] px-3 py-3">
392
+ <div className="space-y-2">
393
+ {[1, 2, 3].map((i) => (
394
+ <div
395
+ key={i}
396
+ className="h-3 animate-pulse rounded bg-[var(--color-surface-raised)]"
397
+ />
398
+ ))}
399
+ </div>
400
+ </div>
401
+ )}
402
+ {detailError && !loadingDetail && (
403
+ <div className="flex items-center gap-2 rounded-lg bg-red-500/10 px-3 py-2 text-xs text-red-400">
404
+ <AlertCircle size={13} className="shrink-0" />
405
+ <span>{detailError}</span>
406
+ </div>
407
+ )}
408
+ {detail && (
409
+ <div className="rounded-lg bg-[var(--color-surface)] p-2">
410
+ {detail.history.length === 0 ? (
411
+ <div className="px-2 py-3 text-xs text-[var(--color-text-muted)]">暂无消息</div>
412
+ ) : (
413
+ <>
414
+ <div className="mb-2 flex items-center justify-between px-1 text-[10px] text-[var(--color-text-muted)]">
415
+ <span>会话历史</span>
416
+ <span>
417
+ 已显示 {detail.history.length} / {detail.historyCount}
418
+ </span>
419
+ </div>
420
+ <div className="max-h-64 space-y-2 overflow-y-auto pr-1">
421
+ {[...detail.history].reverse().map((msg, i) => {
422
+ const isUserMessage = msg.role === 'user';
423
+ return (
424
+ <div
425
+ key={`${msg.timestamp}-${i}`}
426
+ className={`rounded-lg px-3 py-2 text-[11px] leading-relaxed ${
427
+ isUserMessage
428
+ ? 'bg-blue-500/10 text-[var(--color-text)]'
429
+ : 'bg-[var(--color-surface-raised)] text-[var(--color-text)]'
430
+ }`}
431
+ >
432
+ <div className="mb-1 flex items-center gap-2">
433
+ <span
434
+ className={`shrink-0 text-[10px] font-medium ${
435
+ isUserMessage ? 'text-blue-400' : 'text-[var(--color-text-muted)]'
436
+ }`}
437
+ >
438
+ {isUserMessage ? '用户' : 'Agent'}
439
+ </span>
440
+ <span className="text-[10px] text-[var(--color-text-muted)] opacity-60">
441
+ {formatMessageTime(msg.timestamp)}
442
+ </span>
443
+ </div>
444
+ <div className="whitespace-pre-wrap break-words">
445
+ {msg.content.slice(0, 500)}
446
+ {msg.content.length > 500 ? '…' : ''}
447
+ </div>
448
+ </div>
449
+ );
450
+ })}
451
+ </div>
452
+ {hasMoreHistory && (
453
+ <div className="mt-2 flex items-center justify-between gap-2 px-1">
454
+ <span className="text-[10px] text-[var(--color-text-muted)]">
455
+ 每次加载 {SESSION_DETAIL_PAGE_SIZE} 条
456
+ </span>
457
+ <button
458
+ type="button"
459
+ className="inline-flex items-center gap-1 rounded-full bg-[var(--color-surface-raised)] px-2.5 py-1 text-xs text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-50"
460
+ onClick={handleLoadMoreHistory}
461
+ disabled={loadingDetail || loadingMoreHistory}
462
+ >
463
+ {loadingMoreHistory ? (
464
+ <>
465
+ <Loader2 size={12} className="animate-spin" />
466
+ 加载中...
467
+ </>
468
+ ) : (
469
+ <>加载更早 ({Math.max(detail.historyCount - detail.history.length, 0)})</>
470
+ )}
471
+ </button>
472
+ </div>
473
+ )}
474
+ </>
475
+ )}
476
+ </div>
477
+ )}
478
+ </div>
196
479
  )}
197
480
  </div>
198
481
  );
199
482
  }
200
483
 
484
+ function buildSessionRecordMarkdown(session: CcSession, detail: CcSessionDetail): string {
485
+ const title = session.chatName || session.userName || session.title || session.sessionKey;
486
+ const lines = [
487
+ `# ${title}`,
488
+ '',
489
+ `- Team: ${detail.name || session.projectId}`,
490
+ `- Session key: ${detail.sessionKey || session.sessionKey}`,
491
+ `- Platform: ${detail.platform || session.platform}`,
492
+ `- Status: ${detail.live ? 'live' : detail.active ? 'active' : 'inactive'}`,
493
+ `- Created: ${detail.createdAt || session.createdAt}`,
494
+ `- Updated: ${detail.updatedAt || session.updatedAt}`,
495
+ `- Messages: ${detail.historyCount}`,
496
+ '',
497
+ '## Messages',
498
+ '',
499
+ ];
500
+
501
+ if (detail.history.length === 0) {
502
+ lines.push('_No messages._', '');
503
+ return lines.join('\n');
504
+ }
505
+
506
+ for (const msg of [...detail.history].reverse()) {
507
+ lines.push(
508
+ `### ${msg.role === 'user' ? 'User' : 'Agent'} · ${msg.timestamp}`,
509
+ '',
510
+ msg.content,
511
+ ''
512
+ );
513
+ }
514
+
515
+ return lines.join('\n');
516
+ }
517
+
518
+ function buildSessionRecordFilename(teamName: string, session: CcSession): string {
519
+ const updatedAt = session.updatedAt.replace(/[:.]/g, '-');
520
+ return `${sanitizeFilename(teamName)}-${sanitizeFilename(session.sessionKey)}-${updatedAt}.md`;
521
+ }
522
+
523
+ function sanitizeFilename(value: string): string {
524
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'session';
525
+ }
526
+
201
527
  function formatShortTime(date: Date): string {
202
528
  try {
203
529
  const distance = formatDistanceToNowStrict(date, { addSuffix: false });
@@ -220,3 +546,19 @@ function formatShortTime(date: Date): string {
220
546
  return '';
221
547
  }
222
548
  }
549
+
550
+ function formatMessageTime(timestamp: string): string {
551
+ try {
552
+ const date = new Date(timestamp);
553
+ const now = new Date();
554
+ const diffMs = now.getTime() - date.getTime();
555
+ const diffSec = Math.floor(diffMs / 1000);
556
+
557
+ if (diffSec < 60) return '刚刚';
558
+ if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m 前`;
559
+ if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h 前`;
560
+ return `${Math.floor(diffSec / 86400)}d 前`;
561
+ } catch {
562
+ return '';
563
+ }
564
+ }