@yancyyu/openhermit 1.6.37 → 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 (205) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.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-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.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-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.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 +30 -50
  58. package/src/main/server.ts +890 -247
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  62. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  63. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  64. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  65. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  66. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  67. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  68. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  69. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
  70. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  71. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  72. package/src/main/utils/teamProjectResolution.ts +15 -0
  73. package/src/renderer/App.tsx +8 -4
  74. package/src/renderer/api/httpClient.ts +174 -38
  75. package/src/renderer/api/providers.ts +23 -2
  76. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  87. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  88. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  89. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  90. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  91. package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
  92. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  93. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  94. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  95. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  96. package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
  97. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
  98. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  99. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  100. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  101. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  102. package/src/renderer/components/layout/TabBar.tsx +1 -1
  103. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  104. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  105. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  106. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  107. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  108. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  109. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  110. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  111. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  112. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  113. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  114. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  115. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  116. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  117. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  118. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  119. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  120. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  121. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  122. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  123. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  124. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  126. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  127. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  128. package/src/renderer/components/settings/sections/index.ts +0 -1
  129. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  130. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  131. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  132. package/src/renderer/components/splash/splashScene.ts +121 -929
  133. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  134. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  135. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  136. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  137. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  138. package/src/renderer/components/team/TeamDetailView.tsx +325 -114
  139. package/src/renderer/components/team/TeamListView.tsx +108 -123
  140. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  141. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  142. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  143. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  144. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  145. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  146. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  147. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  148. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  149. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  150. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  151. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  152. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  153. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  154. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  155. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  156. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  157. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  158. package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
  159. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  160. package/src/renderer/components/team/tools/McpChip.tsx +45 -0
  161. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  162. package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
  163. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  164. package/src/renderer/index.css +39 -22
  165. package/src/renderer/index.html +17 -50
  166. package/src/renderer/store/index.ts +2 -1
  167. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  168. package/src/renderer/store/slices/teamSlice.ts +45 -168
  169. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  170. package/src/renderer/utils/memberHelpers.ts +5 -17
  171. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  172. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  173. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  174. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  175. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  176. package/src/shared/types/api.ts +121 -2
  177. package/src/shared/types/ccConnect.ts +2 -0
  178. package/src/shared/types/extensions/api.ts +9 -0
  179. package/src/shared/types/extensions/index.ts +4 -0
  180. package/src/shared/types/extensions/mcp.ts +41 -0
  181. package/src/shared/types/index.ts +3 -0
  182. package/src/shared/types/systemManager.ts +49 -0
  183. package/src/shared/types/team.ts +29 -0
  184. package/src/shared/types/terminal.ts +4 -2
  185. package/src/shared/utils/extensionNormalizers.ts +29 -0
  186. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  187. package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
  188. package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
  189. package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
  190. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
  191. package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
  192. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  193. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  194. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
  195. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  196. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  197. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  198. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  199. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  200. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  201. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  202. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  203. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  204. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  205. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -0,0 +1,173 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { format, isToday } from 'date-fns';
3
+
4
+ import { cn } from '@renderer/lib/utils';
5
+
6
+ import { CalendarEventBlock } from './CalendarEventBlock';
7
+ import type { CalendarOccurrence } from './types';
8
+
9
+ // =============================================================================
10
+ // Constants
11
+ // =============================================================================
12
+
13
+ const HOUR_ROW_HEIGHT = 80; // taller than week view for more detail
14
+
15
+ // =============================================================================
16
+ // CalendarDayView
17
+ // =============================================================================
18
+
19
+ interface CalendarDayViewProps {
20
+ occurrences: CalendarOccurrence[];
21
+ date: Date;
22
+ onEventClick: (occurrence: CalendarOccurrence) => void;
23
+ onSlotClick?: (date: Date) => void;
24
+ }
25
+
26
+ export const CalendarDayView = React.memo(function CalendarDayView({
27
+ occurrences,
28
+ date,
29
+ onEventClick,
30
+ onSlotClick,
31
+ }: CalendarDayViewProps): React.JSX.Element {
32
+ const scrollRef = useRef<HTMLDivElement>(null);
33
+ const [nowMinute, setNowMinute] = useState(() => Date.now());
34
+
35
+ // Update current-time indicator every 60s
36
+ useEffect(() => {
37
+ const timer = setInterval(() => setNowMinute(Date.now()), 60_000);
38
+ return () => clearInterval(timer);
39
+ }, []);
40
+
41
+ // Auto-scroll to current hour
42
+ useEffect(() => {
43
+ if (scrollRef.current) {
44
+ const currentHour = new Date().getHours();
45
+ const scrollTo = Math.max(0, (currentHour - 1) * HOUR_ROW_HEIGHT);
46
+ scrollRef.current.scrollTop = scrollTo;
47
+ }
48
+ }, []);
49
+
50
+ // Current time line
51
+ const nowDate = new Date(nowMinute);
52
+ const nowHour = nowDate.getHours();
53
+ const nowMin = nowDate.getMinutes();
54
+ const nowY = (nowHour * 60 + nowMin) / 60 * HOUR_ROW_HEIGHT;
55
+ const showNowLine = isToday(date);
56
+
57
+ const handleSlotClick = useCallback(
58
+ (hour: number) => {
59
+ if (!onSlotClick) return;
60
+ const d = new Date(date);
61
+ d.setHours(hour, 0, 0, 0);
62
+ onSlotClick(d);
63
+ },
64
+ [onSlotClick, date],
65
+ );
66
+
67
+ return (
68
+ <div className="flex flex-col">
69
+ {/* Day header */}
70
+ <div
71
+ className={cn(
72
+ 'flex items-center justify-center gap-2 border-b border-[var(--color-border-subtle)] py-2.5',
73
+ showNowLine && 'bg-[var(--color-accent)]/5',
74
+ )}
75
+ >
76
+ <span
77
+ className={cn(
78
+ 'text-xs',
79
+ showNowLine ? 'text-[var(--color-accent)]' : 'text-[var(--color-text-muted)]',
80
+ )}
81
+ >
82
+ {format(date, 'EEEE')}
83
+ </span>
84
+ <span
85
+ className={cn(
86
+ 'inline-flex size-7 items-center justify-center rounded-full text-sm font-medium',
87
+ showNowLine
88
+ ? 'bg-[var(--color-accent)] text-white'
89
+ : 'text-[var(--color-text)]',
90
+ )}
91
+ >
92
+ {format(date, 'd')}
93
+ </span>
94
+ </div>
95
+
96
+ {/* Scrollable grid */}
97
+ <div ref={scrollRef} className="relative overflow-y-auto" style={{ maxHeight: 520 }}>
98
+ <div className="relative" style={{ height: 24 * HOUR_ROW_HEIGHT }}>
99
+ {/* Hour rows */}
100
+ {Array.from({ length: 24 }, (_, hour) => {
101
+ const y = hour * HOUR_ROW_HEIGHT;
102
+ return (
103
+ <React.Fragment key={hour}>
104
+ {/* Hour label */}
105
+ <div
106
+ className="absolute right-auto flex items-start justify-end pr-3 pt-0 text-[10px] text-[var(--color-text-muted)]"
107
+ style={{ left: 0, top: y, width: 56, height: HOUR_ROW_HEIGHT }}
108
+ >
109
+ <span className="translate-y-[-6px]">
110
+ {String(hour).padStart(2, '0')}:00
111
+ </span>
112
+ </div>
113
+ {/* Grid line */}
114
+ <div
115
+ className="absolute border-t border-[var(--color-border-subtle)]"
116
+ style={{ left: 56, top: y, right: 0 }}
117
+ />
118
+ </React.Fragment>
119
+ );
120
+ })}
121
+
122
+ {/* Event column */}
123
+ <div className="absolute top-0 bottom-0" style={{ left: 56, right: 0 }}>
124
+ {/* Events */}
125
+ {occurrences.map((occ) => {
126
+ const topPx = (occ.hour * 60 + occ.minute) / 60 * HOUR_ROW_HEIGHT;
127
+ const heightPx = (occ.durationMinutes / 60) * HOUR_ROW_HEIGHT;
128
+ const widthPct = 100 / occ.totalColumns;
129
+ const leftPct = occ.column * widthPct;
130
+
131
+ return (
132
+ <CalendarEventBlock
133
+ key={`${occ.scheduleId}-${occ.date.toISOString()}`}
134
+ occurrence={occ}
135
+ variant="day"
136
+ className="absolute"
137
+ style={{
138
+ top: topPx,
139
+ height: Math.max(heightPx, 28),
140
+ width: `calc(${widthPct}% - 4px)`,
141
+ left: `calc(${leftPct}% + 2px)`,
142
+ }}
143
+ onClick={() => onEventClick(occ)}
144
+ />
145
+ );
146
+ })}
147
+
148
+ {/* Clickable slots */}
149
+ {onSlotClick &&
150
+ Array.from({ length: 24 }, (_, hour) => (
151
+ <div
152
+ key={`slot-${hour}`}
153
+ className="absolute left-0 right-0 cursor-pointer transition-colors hover:bg-white/[0.02]"
154
+ style={{ top: hour * HOUR_ROW_HEIGHT, height: HOUR_ROW_HEIGHT }}
155
+ onClick={() => handleSlotClick(hour)}
156
+ />
157
+ ))}
158
+ </div>
159
+
160
+ {/* Current time line */}
161
+ {showNowLine && (
162
+ <div className="pointer-events-none absolute z-20" style={{ top: nowY, left: 56, right: 0 }}>
163
+ <div className="flex items-center">
164
+ <div className="size-2.5 shrink-0 rounded-full bg-red-500" />
165
+ <div className="h-px flex-1 bg-red-500" />
166
+ </div>
167
+ </div>
168
+ )}
169
+ </div>
170
+ </div>
171
+ </div>
172
+ );
173
+ });
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+
3
+ import { cn } from '@renderer/lib/utils';
4
+
5
+ import type { CalendarOccurrence } from './types';
6
+
7
+ // =============================================================================
8
+ // CalendarEventBlock — solid colored block like Feishu
9
+ // =============================================================================
10
+
11
+ interface CalendarEventBlockProps {
12
+ occurrence: CalendarOccurrence;
13
+ variant: 'week' | 'day' | 'month';
14
+ className?: string;
15
+ style?: React.CSSProperties;
16
+ onClick: () => void;
17
+ }
18
+
19
+ export const CalendarEventBlock = React.memo(function CalendarEventBlock({
20
+ occurrence,
21
+ variant,
22
+ className,
23
+ style,
24
+ onClick,
25
+ }: CalendarEventBlockProps): React.JSX.Element {
26
+ const label = occurrence.label || '定时任务';
27
+ const timeStr = occurrence.date.toLocaleTimeString('zh-CN', {
28
+ hour: '2-digit',
29
+ minute: '2-digit',
30
+ hour12: false,
31
+ });
32
+
33
+ if (variant === 'month') {
34
+ // Month: tiny pill with colored dot
35
+ return (
36
+ <button
37
+ type="button"
38
+ className={cn(
39
+ 'flex w-full items-center gap-1 overflow-hidden rounded px-1 py-[1px] text-left transition-opacity hover:opacity-80',
40
+ className,
41
+ )}
42
+ style={{ backgroundColor: hexToRgba(occurrence.color, 0.12), ...style }}
43
+ onClick={onClick}
44
+ >
45
+ <span className="size-1.5 shrink-0 rounded-full" style={{ backgroundColor: occurrence.color }} />
46
+ <span className="truncate text-[10px] leading-tight" style={{ color: occurrence.color }}>
47
+ {label}
48
+ </span>
49
+ </button>
50
+ );
51
+ }
52
+
53
+ // Week / Day: solid colored block with white text (Feishu style)
54
+ return (
55
+ <button
56
+ type="button"
57
+ className={cn(
58
+ 'group relative flex w-full flex-col overflow-hidden rounded-[3px] px-1.5 py-0.5 text-left transition-opacity hover:opacity-90 focus:outline-none',
59
+ occurrence.status === 'paused' && 'opacity-50',
60
+ className,
61
+ )}
62
+ style={{
63
+ backgroundColor: occurrence.color,
64
+ borderLeft: `3px solid ${occurrence.color}`,
65
+ filter: `saturate(0.85) brightness(1.05)`,
66
+ ...style,
67
+ }}
68
+ onClick={onClick}
69
+ title={`${label} · ${occurrence.teamDisplayName}\n${timeStr}`}
70
+ >
71
+ <span className="truncate text-[11px] font-medium leading-tight text-white/95">
72
+ {label}
73
+ </span>
74
+ {variant === 'day' && (
75
+ <span className="truncate text-[10px] leading-tight text-white/70">
76
+ {occurrence.teamDisplayName}
77
+ </span>
78
+ )}
79
+ {variant === 'week' && (
80
+ <span className="truncate text-[9px] leading-tight text-white/60">
81
+ {timeStr} · {occurrence.teamDisplayName}
82
+ </span>
83
+ )}
84
+ </button>
85
+ );
86
+ });
87
+
88
+ // =============================================================================
89
+ // Helpers
90
+ // =============================================================================
91
+
92
+ function hexToRgba(color: string, alpha: number): string {
93
+ if (color.startsWith('hsl')) {
94
+ return color.replace(/^hsla?\(/, 'hsla(').replace(/\)$/, `, ${alpha})`);
95
+ }
96
+ if (color.startsWith('rgb')) {
97
+ return color.replace(/^rgba?\(/, 'rgba(').replace(/\)$/, `, ${alpha})`);
98
+ }
99
+ const hex = color.replace('#', '');
100
+ let r: number, g: number, b: number;
101
+ if (hex.length === 3) {
102
+ r = parseInt(hex[0] + hex[0], 16);
103
+ g = parseInt(hex[1] + hex[1], 16);
104
+ b = parseInt(hex[2] + hex[2], 16);
105
+ } else if (hex.length >= 6) {
106
+ r = parseInt(hex.slice(0, 2), 16);
107
+ g = parseInt(hex.slice(2, 4), 16);
108
+ b = parseInt(hex.slice(4, 6), 16);
109
+ } else {
110
+ return `rgba(128, 128, 128, ${alpha})`;
111
+ }
112
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
113
+ }
@@ -0,0 +1,148 @@
1
+ import React from 'react';
2
+ import { addMonths, addWeeks, addDays, format } from 'date-fns';
3
+
4
+ import { cn } from '@renderer/lib/utils';
5
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
6
+
7
+ import type { CalendarViewMode } from './types';
8
+
9
+ // =============================================================================
10
+ // CalendarHeader
11
+ // =============================================================================
12
+
13
+ interface CalendarHeaderProps {
14
+ viewMode: CalendarViewMode;
15
+ onViewModeChange: (mode: CalendarViewMode) => void;
16
+ currentDate: Date;
17
+ onNavigate: (date: Date) => void;
18
+ }
19
+
20
+ const VIEW_MODE_LABELS: { value: CalendarViewMode; label: string }[] = [
21
+ { value: 'day', label: '日' },
22
+ { value: 'week', label: '周' },
23
+ { value: 'month', label: '月' },
24
+ ];
25
+
26
+ export const CalendarHeader = React.memo(function CalendarHeader({
27
+ viewMode,
28
+ onViewModeChange,
29
+ currentDate,
30
+ onNavigate,
31
+ }: CalendarHeaderProps): React.JSX.Element {
32
+ const handlePrev = () => {
33
+ switch (viewMode) {
34
+ case 'day':
35
+ onNavigate(addDays(currentDate, -1));
36
+ break;
37
+ case 'week':
38
+ onNavigate(addWeeks(currentDate, -1));
39
+ break;
40
+ case 'month':
41
+ onNavigate(addMonths(currentDate, -1));
42
+ break;
43
+ }
44
+ };
45
+
46
+ const handleNext = () => {
47
+ switch (viewMode) {
48
+ case 'day':
49
+ onNavigate(addDays(currentDate, 1));
50
+ break;
51
+ case 'week':
52
+ onNavigate(addWeeks(currentDate, 1));
53
+ break;
54
+ case 'month':
55
+ onNavigate(addMonths(currentDate, 1));
56
+ break;
57
+ }
58
+ };
59
+
60
+ const handleToday = () => {
61
+ onNavigate(new Date());
62
+ };
63
+
64
+ const rangeLabel = getRangeLabel(viewMode, currentDate);
65
+
66
+ return (
67
+ <div className="flex items-center justify-between border-b border-[var(--color-border-subtle)] px-4 py-3">
68
+ {/* Navigation */}
69
+ <div className="flex items-center gap-2">
70
+ <button
71
+ type="button"
72
+ className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-white/[0.06] hover:text-[var(--color-text)]"
73
+ onClick={handlePrev}
74
+ >
75
+ <ChevronLeft className="size-4" />
76
+ </button>
77
+ <button
78
+ type="button"
79
+ className="flex size-7 items-center justify-center rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-white/[0.06] hover:text-[var(--color-text)]"
80
+ onClick={handleNext}
81
+ >
82
+ <ChevronRight className="size-4" />
83
+ </button>
84
+ <button
85
+ type="button"
86
+ className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-2.5 py-1 text-xs text-[var(--color-text-secondary)] transition-colors hover:text-[var(--color-text)]"
87
+ onClick={handleToday}
88
+ >
89
+ 今天
90
+ </button>
91
+ </div>
92
+
93
+ {/* Date range label */}
94
+ <h2 className="text-sm font-medium text-[var(--color-text)]">{rangeLabel}</h2>
95
+
96
+ {/* View mode toggle */}
97
+ <div className="inline-flex rounded-lg border border-white/10 bg-black/20 p-0.5">
98
+ {VIEW_MODE_LABELS.map(({ value, label }) => (
99
+ <button
100
+ key={value}
101
+ type="button"
102
+ className={cn(
103
+ 'rounded-md px-2.5 py-1 text-xs transition-colors',
104
+ viewMode === value
105
+ ? 'bg-white/10 text-[var(--color-text)]'
106
+ : 'text-[var(--color-text-muted)] hover:text-[var(--color-text)]',
107
+ )}
108
+ onClick={() => onViewModeChange(value)}
109
+ >
110
+ {label}
111
+ </button>
112
+ ))}
113
+ </div>
114
+ </div>
115
+ );
116
+ });
117
+
118
+ // =============================================================================
119
+ // Helpers
120
+ // =============================================================================
121
+
122
+ function getRangeLabel(mode: CalendarViewMode, date: Date): string {
123
+ switch (mode) {
124
+ case 'day':
125
+ return format(date, 'yyyy年M月d日 EEEE', { locale: undefined });
126
+ case 'week': {
127
+ // Show week range: "6月8日 - 6月14日"
128
+ const weekStart = startOfWeekCustom(date);
129
+ const weekEnd = new Date(weekStart);
130
+ weekEnd.setDate(weekEnd.getDate() + 6);
131
+ const startStr = format(weekStart, 'M月d日');
132
+ const endStr = format(weekEnd, 'M月d日');
133
+ return `${startStr} – ${endStr}`;
134
+ }
135
+ case 'month':
136
+ return format(date, 'yyyy年M月');
137
+ }
138
+ }
139
+
140
+ function startOfWeekCustom(date: Date): Date {
141
+ const d = new Date(date);
142
+ const day = d.getDay();
143
+ // Monday = 0 offset, Sunday = 6 offset
144
+ const diff = day === 0 ? 6 : day - 1;
145
+ d.setDate(d.getDate() - diff);
146
+ d.setHours(0, 0, 0, 0);
147
+ return d;
148
+ }
@@ -0,0 +1,142 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ addDays,
4
+ addMonths,
5
+ endOfMonth,
6
+ format,
7
+ getDate,
8
+ isSameDay,
9
+ isSameMonth,
10
+ isToday,
11
+ startOfMonth,
12
+ startOfWeek,
13
+ } from 'date-fns';
14
+
15
+ import { cn } from '@renderer/lib/utils';
16
+
17
+ import { CalendarEventBlock } from './CalendarEventBlock';
18
+ import type { CalendarOccurrence } from './types';
19
+
20
+ // =============================================================================
21
+ // Constants
22
+ // =============================================================================
23
+
24
+ const WEEK_STARTS_ON = 1; // Monday
25
+ const DAY_NAMES = ['一', '二', '三', '四', '五', '六', '日'];
26
+ const MAX_VISIBLE_EVENTS = 3;
27
+
28
+ // =============================================================================
29
+ // CalendarMonthView
30
+ // =============================================================================
31
+
32
+ interface CalendarMonthViewProps {
33
+ occurrences: CalendarOccurrence[];
34
+ currentDate: Date;
35
+ onEventClick: (occurrence: CalendarOccurrence) => void;
36
+ onDayClick?: (date: Date) => void;
37
+ }
38
+
39
+ export const CalendarMonthView = React.memo(function CalendarMonthView({
40
+ occurrences,
41
+ currentDate,
42
+ onEventClick,
43
+ onDayClick,
44
+ }: CalendarMonthViewProps): React.JSX.Element {
45
+ // Build 6×7 grid of days
46
+ const gridDays = useMemo(() => {
47
+ const monthStart = startOfMonth(currentDate);
48
+ const gridStart = startOfWeek(monthStart, { weekStartsOn: WEEK_STARTS_ON });
49
+ const monthEnd = endOfMonth(currentDate);
50
+
51
+ // How many weeks to show (5 or 6)
52
+ const totalDays = Math.ceil((monthEnd.getDate() + (gridStart.getDay() === 0 ? 6 : gridStart.getDay() - 1)) / 7) * 7;
53
+ const rows = Math.max(5, Math.ceil(totalDays / 7));
54
+ const count = rows * 7;
55
+
56
+ return Array.from({ length: count }, (_, i) => addDays(gridStart, i));
57
+ }, [currentDate]);
58
+
59
+ // Group occurrences by day
60
+ const occurrencesByDay = useMemo(() => {
61
+ const map = new Map<string, CalendarOccurrence[]>();
62
+ for (const occ of occurrences) {
63
+ const key = format(occ.date, 'yyyy-MM-dd');
64
+ const list = map.get(key);
65
+ if (list) list.push(occ);
66
+ else map.set(key, [occ]);
67
+ }
68
+ return map;
69
+ }, [occurrences]);
70
+
71
+ return (
72
+ <div className="flex flex-col">
73
+ {/* Day-of-week headers */}
74
+ <div className="grid grid-cols-7 border-b border-[var(--color-border-subtle)]">
75
+ {DAY_NAMES.map((name) => (
76
+ <div
77
+ key={name}
78
+ className="flex items-center justify-center py-2 text-xs text-[var(--color-text-muted)]"
79
+ >
80
+ {name}
81
+ </div>
82
+ ))}
83
+ </div>
84
+
85
+ {/* Day grid */}
86
+ <div className="grid grid-cols-7 auto-rows-fr">
87
+ {gridDays.map((day) => {
88
+ const key = format(day, 'yyyy-MM-dd');
89
+ const inMonth = isSameMonth(day, currentDate);
90
+ const today = isToday(day);
91
+ const dayOccurrences = occurrencesByDay.get(key) ?? [];
92
+ const visible = dayOccurrences.slice(0, MAX_VISIBLE_EVENTS);
93
+ const hiddenCount = dayOccurrences.length - MAX_VISIBLE_EVENTS;
94
+
95
+ return (
96
+ <div
97
+ key={key}
98
+ className={cn(
99
+ 'group relative flex min-h-[80px] flex-col border-b border-r border-[var(--color-border-subtle)] p-1.5 transition-colors',
100
+ !inMonth && 'bg-black/20',
101
+ today && 'bg-[var(--color-accent)]/5',
102
+ onDayClick && 'cursor-pointer hover:bg-white/[0.02]',
103
+ )}
104
+ onClick={() => onDayClick?.(day)}
105
+ >
106
+ {/* Date number */}
107
+ <span
108
+ className={cn(
109
+ 'mb-1 inline-flex size-6 items-center justify-center rounded-full text-xs font-medium',
110
+ today
111
+ ? 'bg-[var(--color-accent)] text-white'
112
+ : inMonth
113
+ ? 'text-[var(--color-text-secondary)]'
114
+ : 'text-[var(--color-text-muted)]',
115
+ )}
116
+ >
117
+ {getDate(day)}
118
+ </span>
119
+
120
+ {/* Event pills */}
121
+ <div className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-hidden">
122
+ {visible.map((occ) => (
123
+ <CalendarEventBlock
124
+ key={`${occ.scheduleId}-${occ.date.toISOString()}`}
125
+ occurrence={occ}
126
+ variant="month"
127
+ onClick={() => onEventClick(occ)}
128
+ />
129
+ ))}
130
+ {hiddenCount > 0 && (
131
+ <span className="px-1 text-[10px] text-[var(--color-text-muted)]">
132
+ +{hiddenCount} 更多
133
+ </span>
134
+ )}
135
+ </div>
136
+ </div>
137
+ );
138
+ })}
139
+ </div>
140
+ </div>
141
+ );
142
+ });