@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.
- package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
- package/dist-renderer/assets/{TeamGraphOverlay-ZEDfZyHb.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CIhniz70.js → _basePickBy-B7kSYPxr.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-cKAW4Q8I.js → _baseUniq-CnjxqwAk.js} +1 -1
- package/dist-renderer/assets/{arc-YmNsoDXW.js → arc-CLeZuINP.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DHEls2sX.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-Bpwf1Sbg.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B0IaQ4w5.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
- package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLk-hcFc.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-1XRmX_Zm.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-1waH1DAD.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BqpZBtrN.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-Bly7vVym.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-Ci2QWBAs.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-YCqFW7d-.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-B0xGXInl.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DxcFNQKT.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DPo_RfZY.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-U3hQsFe4.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-OrwrAy0V.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CXATPWVw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-B0e8AfMF.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-CXfzA4jJ.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CMr08qVl.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vYFHpPmy.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
- package/dist-renderer/assets/{graph-DOe5j8dH.js → graph-CsoEwUhS.js} +1 -1
- package/dist-renderer/assets/{index-BySQS7AB.js → index-BWPWmJNo.js} +1 -1
- package/dist-renderer/assets/{index-V7dAKPqd.js → index-Bu2R-Se7.js} +587 -705
- package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
- package/dist-renderer/assets/{index-CzWxVCRL.js → index-D-3KgskL.js} +1 -1
- package/dist-renderer/assets/{index-VJ-MM9xa.js → index-DGEBzLNT.js} +1 -1
- package/dist-renderer/assets/{index-B2Dy7M2G.js → index-NhHNs2Oo.js} +1 -1
- package/dist-renderer/assets/{index-C_okzZXP.js → index-h17WuEyf.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D_WubR0B.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-w9ca-1TI.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-Jg9p6_pN.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
- package/dist-renderer/assets/{layout-B-z3y17c.js → layout-C3XWrpwo.js} +1 -1
- package/dist-renderer/assets/{linear-D-RTX5UW.js → linear-OEEcn8KN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-CDQmHOYP.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D_odsQL7.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BRsmYWSA.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-ChNE_BOV.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8FtpwKc.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DmLCzNcc.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
- package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-WJBm4bhu.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BXs_hOJs.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-o04MA0G9.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-Czj69XRd.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
- package/dist-renderer/index.html +20 -53
- package/package.json +25 -18
- package/src/main/ipc/extensions.ts +2 -1
- package/src/main/server.ts +873 -221
- package/src/main/services/extensions/ExtensionFacadeService.ts +2 -5
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
- package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
- package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
- package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
- package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
- package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +5 -6
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
- package/src/main/utils/teamProjectResolution.ts +15 -0
- package/src/renderer/App.tsx +8 -4
- package/src/renderer/api/httpClient.ts +68 -18
- package/src/renderer/api/providers.ts +23 -2
- package/src/renderer/assets/participant-avatars/01.svg +3 -0
- package/src/renderer/assets/participant-avatars/02.svg +3 -0
- package/src/renderer/assets/participant-avatars/03.svg +3 -0
- package/src/renderer/assets/participant-avatars/04.svg +3 -0
- package/src/renderer/assets/participant-avatars/05.svg +3 -0
- package/src/renderer/assets/participant-avatars/06.svg +3 -0
- package/src/renderer/assets/participant-avatars/07.svg +3 -0
- package/src/renderer/assets/participant-avatars/08.svg +3 -0
- package/src/renderer/assets/participant-avatars/09.svg +3 -0
- package/src/renderer/assets/participant-avatars/10.svg +3 -0
- package/src/renderer/assets/participant-avatars/11.svg +3 -0
- package/src/renderer/assets/participant-avatars/12.svg +3 -0
- package/src/renderer/assets/participant-avatars/13.svg +3 -0
- package/src/renderer/components/common/TerminalPane.tsx +213 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -125
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
- package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
- package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
- package/src/renderer/components/extensions/plugins/PluginCard.tsx +6 -6
- package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +34 -21
- package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
- package/src/renderer/components/layout/PaneContent.tsx +8 -1
- package/src/renderer/components/layout/Sidebar.tsx +11 -54
- package/src/renderer/components/layout/SortableTab.tsx +20 -31
- package/src/renderer/components/layout/TabBar.tsx +1 -1
- package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
- package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
- package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
- package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
- package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
- package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
- package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
- package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
- package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
- package/src/renderer/components/schedules/calendar/index.ts +2 -0
- package/src/renderer/components/schedules/calendar/types.ts +44 -0
- package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
- package/src/renderer/components/settings/SettingsView.tsx +30 -35
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
- package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
- package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
- package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
- package/src/renderer/components/settings/sections/index.ts +0 -1
- package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
- package/src/renderer/components/splash/splashScene.ts +121 -929
- package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
- package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
- package/src/renderer/components/tasks/TasksView.tsx +112 -134
- package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
- package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
- package/src/renderer/components/team/TeamDetailView.tsx +319 -123
- package/src/renderer/components/team/TeamListView.tsx +108 -123
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
- package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
- package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
- package/src/renderer/components/team/members/MemberCard.tsx +14 -47
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
- package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
- package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
- package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
- package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
- package/src/renderer/components/team/tools/AddMcpInline.tsx +27 -17
- package/src/renderer/components/team/tools/McpChip.tsx +6 -3
- package/src/renderer/components/team/tools/SkillChip.tsx +2 -2
- package/src/renderer/components/team/tools/ToolsSection.tsx +418 -70
- package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
- package/src/renderer/index.css +39 -22
- package/src/renderer/index.html +17 -50
- package/src/renderer/store/index.ts +2 -1
- package/src/renderer/store/slices/scheduleSlice.ts +1 -1
- package/src/renderer/store/slices/teamSlice.ts +45 -168
- package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
- package/src/renderer/utils/memberHelpers.ts +5 -17
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
- package/src/renderer/utils/providerSlashCommands.ts +0 -5
- package/src/renderer/utils/scheduleFormatters.ts +3 -1
- package/src/renderer/utils/teamMessageFiltering.ts +14 -1
- package/src/renderer/utils/teamModelAvailability.ts +18 -2
- package/src/shared/types/api.ts +121 -2
- package/src/shared/types/ccConnect.ts +2 -0
- package/src/shared/types/index.ts +3 -0
- package/src/shared/types/systemManager.ts +49 -0
- package/src/shared/types/team.ts +29 -0
- package/src/shared/types/terminal.ts +4 -2
- package/src/shared/utils/extensionNormalizers.ts +15 -8
- package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-lJZi-9Hp.js +0 -52
- package/dist-renderer/assets/channel-yIlSKy0e.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +0 -1
- package/dist-renderer/assets/clone-BTNuUva-.js +0 -1
- package/dist-renderer/assets/index-Bi6nrZ4z.css +0 -1
- package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-_m6iPPUR.js +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { Cron } from 'croner';
|
|
2
|
+
import {
|
|
3
|
+
addDays,
|
|
4
|
+
endOfDay,
|
|
5
|
+
endOfWeek,
|
|
6
|
+
isBefore,
|
|
7
|
+
startOfDay,
|
|
8
|
+
startOfWeek,
|
|
9
|
+
} from 'date-fns';
|
|
10
|
+
|
|
11
|
+
import type { Schedule } from '@shared/types';
|
|
12
|
+
|
|
13
|
+
import type { CalendarOccurrence, CalendarViewMode, WeekRange } from './types';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Config
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Max cron hits to enumerate per schedule per range */
|
|
20
|
+
const MAX_OCCURRENCES_PER_SCHEDULE = 200;
|
|
21
|
+
|
|
22
|
+
/** Default visual duration for each event block (minutes) */
|
|
23
|
+
const DEFAULT_DURATION_MINUTES = 30;
|
|
24
|
+
|
|
25
|
+
/** Week starts on Monday */
|
|
26
|
+
const WEEK_STARTS_ON = 1 as const;
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Date range computation
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
export function getViewRange(mode: CalendarViewMode, referenceDate: Date): { start: Date; end: Date } {
|
|
33
|
+
const base = startOfDay(referenceDate);
|
|
34
|
+
switch (mode) {
|
|
35
|
+
case 'day':
|
|
36
|
+
return { start: base, end: endOfDay(referenceDate) };
|
|
37
|
+
case 'week':
|
|
38
|
+
return {
|
|
39
|
+
start: startOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
|
|
40
|
+
end: endOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
|
|
41
|
+
};
|
|
42
|
+
case 'month': {
|
|
43
|
+
const monthStart = new Date(base.getFullYear(), base.getMonth(), 1);
|
|
44
|
+
const gridStart = startOfWeek(monthStart, { weekStartsOn: WEEK_STARTS_ON });
|
|
45
|
+
const nextMonth = new Date(base.getFullYear(), base.getMonth() + 1, 1);
|
|
46
|
+
const lastDay = addDays(nextMonth, -1);
|
|
47
|
+
const gridEnd = endOfWeek(lastDay, { weekStartsOn: WEEK_STARTS_ON });
|
|
48
|
+
return { start: gridStart, end: gridEnd };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getWeekRange(referenceDate: Date): WeekRange {
|
|
54
|
+
const base = startOfDay(referenceDate);
|
|
55
|
+
return {
|
|
56
|
+
start: startOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
|
|
57
|
+
end: endOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Core occurrence computation
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
interface TeamColorResolver {
|
|
66
|
+
getTeamColor: (teamName: string) => string;
|
|
67
|
+
getTeamDisplayName: (teamName: string) => string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function computeCalendarOccurrences(
|
|
71
|
+
schedules: Schedule[],
|
|
72
|
+
rangeStart: Date,
|
|
73
|
+
rangeEnd: Date,
|
|
74
|
+
resolvers: TeamColorResolver,
|
|
75
|
+
): CalendarOccurrence[] {
|
|
76
|
+
if (schedules.length === 0) return [];
|
|
77
|
+
|
|
78
|
+
const allOccurrences: CalendarOccurrence[] = [];
|
|
79
|
+
|
|
80
|
+
for (const schedule of schedules) {
|
|
81
|
+
if (schedule.status === 'disabled') continue;
|
|
82
|
+
|
|
83
|
+
const rawDates = enumerateCronInRange(
|
|
84
|
+
schedule.cronExpression,
|
|
85
|
+
schedule.timezone,
|
|
86
|
+
rangeStart,
|
|
87
|
+
rangeEnd,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const date of rawDates) {
|
|
91
|
+
allOccurrences.push({
|
|
92
|
+
scheduleId: schedule.id,
|
|
93
|
+
teamName: schedule.teamName,
|
|
94
|
+
label: schedule.label || '',
|
|
95
|
+
cronDescription: '',
|
|
96
|
+
status: schedule.status,
|
|
97
|
+
date,
|
|
98
|
+
hour: date.getHours(),
|
|
99
|
+
minute: date.getMinutes(),
|
|
100
|
+
durationMinutes: DEFAULT_DURATION_MINUTES,
|
|
101
|
+
color: resolvers.getTeamColor(schedule.teamName),
|
|
102
|
+
teamDisplayName: resolvers.getTeamDisplayName(schedule.teamName),
|
|
103
|
+
column: 0,
|
|
104
|
+
totalColumns: 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resolveOverlaps(allOccurrences);
|
|
110
|
+
return allOccurrences;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// Cron enumeration
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
function enumerateCronInRange(
|
|
118
|
+
cronExpression: string,
|
|
119
|
+
timezone: string,
|
|
120
|
+
rangeStart: Date,
|
|
121
|
+
rangeEnd: Date,
|
|
122
|
+
): Date[] {
|
|
123
|
+
try {
|
|
124
|
+
const job = new Cron(cronExpression.trim(), { timezone, paused: true });
|
|
125
|
+
const raw = job.nextRuns(MAX_OCCURRENCES_PER_SCHEDULE, rangeStart);
|
|
126
|
+
const results: Date[] = [];
|
|
127
|
+
for (const d of raw) {
|
|
128
|
+
const dt = d instanceof Date ? d : new Date(d);
|
|
129
|
+
if (isBefore(rangeEnd, dt)) break;
|
|
130
|
+
if (!isBefore(dt, rangeStart)) {
|
|
131
|
+
results.push(dt);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
} catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Overlap resolution — greedy column assignment within each day
|
|
142
|
+
// =============================================================================
|
|
143
|
+
|
|
144
|
+
function resolveOverlaps(occurrences: CalendarOccurrence[]): void {
|
|
145
|
+
// Group by day key (YYYY-MM-DD)
|
|
146
|
+
const byDay = new Map<string, CalendarOccurrence[]>();
|
|
147
|
+
for (const occ of occurrences) {
|
|
148
|
+
const key = dayKey(occ.date);
|
|
149
|
+
const list = byDay.get(key);
|
|
150
|
+
if (list) list.push(occ);
|
|
151
|
+
else byDay.set(key, [occ]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const dayOccurrences of byDay.values()) {
|
|
155
|
+
if (dayOccurrences.length > 1) {
|
|
156
|
+
resolveOverlapsInDay(dayOccurrences);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveOverlapsInDay(occurrences: CalendarOccurrence[]): void {
|
|
162
|
+
const sorted = [...occurrences].sort(
|
|
163
|
+
(a, b) => a.hour * 60 + a.minute - (b.hour * 60 + b.minute),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// --- Pass 1: greedy column assignment ---
|
|
167
|
+
// columnEndTimes[col] = minute-of-day when that column becomes free
|
|
168
|
+
const columnEndTimes: number[] = [];
|
|
169
|
+
|
|
170
|
+
for (const occ of sorted) {
|
|
171
|
+
const startMin = occ.hour * 60 + occ.minute;
|
|
172
|
+
const endMin = startMin + occ.durationMinutes;
|
|
173
|
+
|
|
174
|
+
let assignedCol = -1;
|
|
175
|
+
for (let col = 0; col < columnEndTimes.length; col++) {
|
|
176
|
+
if (columnEndTimes[col] <= startMin) {
|
|
177
|
+
assignedCol = col;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (assignedCol === -1) {
|
|
182
|
+
assignedCol = columnEndTimes.length;
|
|
183
|
+
columnEndTimes.push(0);
|
|
184
|
+
}
|
|
185
|
+
columnEndTimes[assignedCol] = endMin;
|
|
186
|
+
occ.column = assignedCol;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Pass 2: propagate max totalColumns to all events in each overlap group ---
|
|
190
|
+
// Build overlap graph, then for each connected component set totalColumns = max column + 1
|
|
191
|
+
const visited = new Set<CalendarOccurrence>();
|
|
192
|
+
|
|
193
|
+
for (const occ of sorted) {
|
|
194
|
+
if (visited.has(occ)) continue;
|
|
195
|
+
|
|
196
|
+
// BFS to find all transitively overlapping events
|
|
197
|
+
const component: CalendarOccurrence[] = [];
|
|
198
|
+
const queue = [occ];
|
|
199
|
+
visited.add(occ);
|
|
200
|
+
|
|
201
|
+
while (queue.length > 0) {
|
|
202
|
+
const current = queue.pop()!;
|
|
203
|
+
component.push(current);
|
|
204
|
+
const curStart = current.hour * 60 + current.minute;
|
|
205
|
+
const curEnd = curStart + current.durationMinutes;
|
|
206
|
+
|
|
207
|
+
for (const other of sorted) {
|
|
208
|
+
if (visited.has(other)) continue;
|
|
209
|
+
const otherStart = other.hour * 60 + other.minute;
|
|
210
|
+
const otherEnd = otherStart + other.durationMinutes;
|
|
211
|
+
if (otherStart < curEnd && otherEnd > curStart) {
|
|
212
|
+
visited.add(other);
|
|
213
|
+
queue.push(other);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const maxCol = Math.max(...component.map((o) => o.column));
|
|
219
|
+
for (const o of component) {
|
|
220
|
+
o.totalColumns = maxCol + 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// Helpers
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
function dayKey(date: Date): string {
|
|
230
|
+
const y = date.getFullYear();
|
|
231
|
+
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
232
|
+
const d = String(date.getDate()).padStart(2, '0');
|
|
233
|
+
return `${y}-${m}-${d}`;
|
|
234
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ScheduleStatus } from '@shared/types';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Calendar view mode
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
export type CalendarViewMode = 'day' | 'week' | 'month';
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Calendar occurrence — a single cron fire event on the calendar
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
export interface CalendarOccurrence {
|
|
14
|
+
scheduleId: string;
|
|
15
|
+
teamName: string;
|
|
16
|
+
label: string;
|
|
17
|
+
cronDescription: string;
|
|
18
|
+
status: ScheduleStatus;
|
|
19
|
+
/** The Date of this specific cron occurrence */
|
|
20
|
+
date: Date;
|
|
21
|
+
/** Hour of day (0-23) for positioning */
|
|
22
|
+
hour: number;
|
|
23
|
+
/** Minute within the hour (0-59) */
|
|
24
|
+
minute: number;
|
|
25
|
+
/** Visual block height in minutes — defaults to 30 */
|
|
26
|
+
durationMinutes: number;
|
|
27
|
+
/** Team color (CSS color string) */
|
|
28
|
+
color: string;
|
|
29
|
+
/** Team display name */
|
|
30
|
+
teamDisplayName: string;
|
|
31
|
+
/** Column index for side-by-side stacking (computed by overlap algorithm) */
|
|
32
|
+
column: number;
|
|
33
|
+
/** Total number of overlapping columns in this slot */
|
|
34
|
+
totalColumns: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Date range helpers
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
export interface WeekRange {
|
|
42
|
+
start: Date;
|
|
43
|
+
end: Date;
|
|
44
|
+
}
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
TooltipProvider,
|
|
7
7
|
TooltipTrigger,
|
|
8
8
|
} from '@renderer/components/ui/tooltip';
|
|
9
|
-
import { Bot, Info,
|
|
9
|
+
import { Bot, Info, Settings, Share2, Wrench } from 'lucide-react';
|
|
10
10
|
|
|
11
11
|
import type { LucideIcon } from 'lucide-react';
|
|
12
12
|
|
|
13
|
-
export type SettingsSection = 'general' | '
|
|
13
|
+
export type SettingsSection = 'general' | 'harness' | 'task-bus' | 'advanced';
|
|
14
14
|
|
|
15
15
|
interface SettingsTabsProps {
|
|
16
16
|
activeSection: SettingsSection;
|
|
@@ -31,30 +31,23 @@ const tabs: TabConfig[] = [
|
|
|
31
31
|
icon: Settings,
|
|
32
32
|
description: '主题、语言、显示密度和启动行为等核心应用偏好。',
|
|
33
33
|
},
|
|
34
|
-
{
|
|
35
|
-
id: 'channels',
|
|
36
|
-
label: '渠道',
|
|
37
|
-
icon: PlugZap,
|
|
38
|
-
description:
|
|
39
|
-
'管理飞书、微信、Telegram 等消息平台的接入配置。每个 cc-connect 项目可绑定一个或多个渠道。',
|
|
40
|
-
},
|
|
41
34
|
{
|
|
42
35
|
id: 'harness',
|
|
43
36
|
label: 'Harness',
|
|
44
37
|
icon: Bot,
|
|
45
|
-
description: '管理 AI Agent
|
|
38
|
+
description: '管理 AI Agent 运行时的 Provider 配置、API Key、端点和 CLI 安装状态。',
|
|
46
39
|
},
|
|
47
40
|
{
|
|
48
41
|
id: 'task-bus',
|
|
49
42
|
label: '团队总线',
|
|
50
43
|
icon: Share2,
|
|
51
|
-
description: '配置 Redis
|
|
44
|
+
description: '配置 Redis 消息总线实现跨主机团队协作,以及使用数据采集和上报。',
|
|
52
45
|
},
|
|
53
46
|
{
|
|
54
47
|
id: 'advanced',
|
|
55
48
|
label: '高级',
|
|
56
49
|
icon: Wrench,
|
|
57
|
-
description: '
|
|
50
|
+
description: '导出/导入配置、重置默认值和编辑原始配置。',
|
|
58
51
|
},
|
|
59
52
|
];
|
|
60
53
|
|
|
@@ -66,51 +59,53 @@ export const SettingsTabs = ({
|
|
|
66
59
|
|
|
67
60
|
return (
|
|
68
61
|
<TooltipProvider>
|
|
69
|
-
<div className="
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const isActive = activeSection === tab.id;
|
|
62
|
+
<div className="flex items-center" style={{ backgroundColor: 'var(--color-surface-raised)' }}>
|
|
63
|
+
{visibleTabs.map((tab, index) => {
|
|
64
|
+
const Icon = tab.icon;
|
|
65
|
+
const isActive = activeSection === tab.id;
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
return (
|
|
68
|
+
<button
|
|
69
|
+
key={tab.id}
|
|
70
|
+
onClick={() => onSectionChange(tab.id)}
|
|
71
|
+
className={`flex items-center gap-1.5 px-3 py-2 text-xs transition-colors ${
|
|
72
|
+
isActive
|
|
73
|
+
? 'font-medium'
|
|
74
|
+
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
|
|
75
|
+
}`}
|
|
76
|
+
style={{
|
|
77
|
+
borderLeft: index > 0 ? '1px solid var(--color-border-subtle)' : undefined,
|
|
78
|
+
color: isActive ? '#67e8f9' : undefined,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<Icon className={`size-3 ${isActive ? 'opacity-90' : 'opacity-40'}`} />
|
|
82
|
+
{tab.label}
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
</div>
|
|
84
|
+
<Tooltip>
|
|
85
|
+
<TooltipTrigger asChild>
|
|
86
|
+
<span
|
|
87
|
+
role="button"
|
|
88
|
+
tabIndex={0}
|
|
89
|
+
aria-label={`What is ${tab.label}?`}
|
|
90
|
+
onClick={(event) => event.stopPropagation()}
|
|
91
|
+
onMouseDown={(event) => event.stopPropagation()}
|
|
92
|
+
onKeyDown={(event) => {
|
|
93
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
94
|
+
event.stopPropagation();
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
className="ml-0.5 inline-flex items-center justify-center rounded-full text-[var(--color-text-muted)] opacity-0 transition-opacity hover:opacity-100"
|
|
98
|
+
>
|
|
99
|
+
<Info className="size-2.5" />
|
|
100
|
+
</span>
|
|
101
|
+
</TooltipTrigger>
|
|
102
|
+
<TooltipContent className="max-w-64 text-xs leading-relaxed">
|
|
103
|
+
{tab.description}
|
|
104
|
+
</TooltipContent>
|
|
105
|
+
</Tooltip>
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
114
109
|
</div>
|
|
115
110
|
</TooltipProvider>
|
|
116
111
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SettingsView - Main settings panel
|
|
3
|
-
*
|
|
2
|
+
* SettingsView - Main settings panel.
|
|
3
|
+
* Terminal-style layout matching the control console aesthetic.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
@@ -10,7 +10,7 @@ import { Loader2 } from 'lucide-react';
|
|
|
10
10
|
import { useShallow } from 'zustand/react/shallow';
|
|
11
11
|
|
|
12
12
|
import { useSettingsConfig, useSettingsHandlers } from './hooks';
|
|
13
|
-
import { AdvancedSection, GeneralSection, HarnessSection
|
|
13
|
+
import { AdvancedSection, GeneralSection, HarnessSection } from './sections';
|
|
14
14
|
import { TaskBusSection } from './sections/TaskBusSection';
|
|
15
15
|
import { type SettingsSection, SettingsTabs } from './SettingsTabs';
|
|
16
16
|
|
|
@@ -23,17 +23,15 @@ export const SettingsView = (): React.JSX.Element | null => {
|
|
|
23
23
|
}))
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
// Consume pending section (avoid setState during render)
|
|
27
26
|
useEffect(() => {
|
|
28
27
|
if (pendingSettingsSection) {
|
|
29
28
|
const nextSection: SettingsSection =
|
|
30
|
-
pendingSettingsSection === 'channels' ||
|
|
31
29
|
pendingSettingsSection === 'harness' ||
|
|
32
30
|
pendingSettingsSection === 'task-bus' ||
|
|
33
31
|
pendingSettingsSection === 'advanced'
|
|
34
32
|
? pendingSettingsSection
|
|
35
33
|
: 'general';
|
|
36
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
34
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
37
35
|
setActiveSection(nextSection);
|
|
38
36
|
clearPendingSettingsSection();
|
|
39
37
|
}
|
|
@@ -61,39 +59,37 @@ export const SettingsView = (): React.JSX.Element | null => {
|
|
|
61
59
|
updateConfig,
|
|
62
60
|
});
|
|
63
61
|
|
|
64
|
-
// Loading state
|
|
65
62
|
if (loading) {
|
|
66
63
|
return (
|
|
67
64
|
<div
|
|
68
|
-
className="flex flex-1 items-center justify-center"
|
|
65
|
+
className="flex flex-1 items-center justify-center font-mono"
|
|
69
66
|
style={{ backgroundColor: 'var(--color-surface)' }}
|
|
70
67
|
>
|
|
71
68
|
<div className="flex items-center gap-3" style={{ color: 'var(--color-text-muted)' }}>
|
|
72
|
-
<Loader2 className="size-
|
|
73
|
-
<span
|
|
69
|
+
<Loader2 className="size-4 animate-spin" />
|
|
70
|
+
<span className="text-xs">loading settings...</span>
|
|
74
71
|
</div>
|
|
75
72
|
</div>
|
|
76
73
|
);
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
// Error state
|
|
80
76
|
if (error && !config) {
|
|
81
77
|
return (
|
|
82
78
|
<div
|
|
83
|
-
className="flex flex-1 items-center justify-center"
|
|
79
|
+
className="flex flex-1 items-center justify-center font-mono"
|
|
84
80
|
style={{ backgroundColor: 'var(--color-surface)' }}
|
|
85
81
|
>
|
|
86
82
|
<div className="text-center">
|
|
87
|
-
<p className="mb-4 text-red-400">{error}</p>
|
|
83
|
+
<p className="mb-4 text-xs text-red-400">{error}</p>
|
|
88
84
|
<button
|
|
89
85
|
onClick={() => window.location.reload()}
|
|
90
|
-
className="rounded
|
|
86
|
+
className="rounded border px-3 py-1.5 text-xs transition-colors"
|
|
91
87
|
style={{
|
|
92
|
-
|
|
93
|
-
color: 'var(--color-text-
|
|
88
|
+
borderColor: 'var(--color-border)',
|
|
89
|
+
color: 'var(--color-text-muted)',
|
|
94
90
|
}}
|
|
95
91
|
>
|
|
96
|
-
|
|
92
|
+
retry
|
|
97
93
|
</button>
|
|
98
94
|
</div>
|
|
99
95
|
</div>
|
|
@@ -104,27 +100,28 @@ export const SettingsView = (): React.JSX.Element | null => {
|
|
|
104
100
|
|
|
105
101
|
return (
|
|
106
102
|
<div className="flex-1 overflow-auto" style={{ backgroundColor: 'var(--color-surface)' }}>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
{/* Clean container */}
|
|
104
|
+
<div
|
|
105
|
+
className="mx-auto flex min-h-full max-w-3xl flex-col"
|
|
106
|
+
style={{
|
|
107
|
+
backgroundColor: 'var(--color-surface)',
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{/* Tabs area */}
|
|
111
|
+
<div
|
|
112
|
+
className="border-b"
|
|
113
|
+
style={{
|
|
114
|
+
borderColor: 'var(--color-border-subtle)',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<SettingsTabs activeSection={activeSection} onSectionChange={setActiveSection} />
|
|
116
118
|
{error && (
|
|
117
|
-
<div className="
|
|
118
|
-
<p className="text-sm text-red-400">{error}</p>
|
|
119
|
-
</div>
|
|
119
|
+
<div className="px-4 pb-2 text-[10px] text-red-400">{error}</div>
|
|
120
120
|
)}
|
|
121
121
|
</div>
|
|
122
122
|
|
|
123
|
-
{/* Tabs */}
|
|
124
|
-
<SettingsTabs activeSection={activeSection} onSectionChange={setActiveSection} />
|
|
125
|
-
|
|
126
123
|
{/* Content */}
|
|
127
|
-
<div className="
|
|
124
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
128
125
|
{activeSection === 'general' && (
|
|
129
126
|
<GeneralSection
|
|
130
127
|
safeConfig={safeConfig}
|
|
@@ -135,8 +132,6 @@ export const SettingsView = (): React.JSX.Element | null => {
|
|
|
135
132
|
/>
|
|
136
133
|
)}
|
|
137
134
|
|
|
138
|
-
{activeSection === 'channels' && <PlatformsSection />}
|
|
139
|
-
|
|
140
135
|
{activeSection === 'harness' && <HarnessSection />}
|
|
141
136
|
|
|
142
137
|
{activeSection === 'task-bus' && <TaskBusSection />}
|
|
@@ -14,11 +14,15 @@ export const SettingsSectionHeader = ({
|
|
|
14
14
|
}: SettingsSectionHeaderProps): React.JSX.Element => {
|
|
15
15
|
return (
|
|
16
16
|
<h3
|
|
17
|
-
className="mb-
|
|
17
|
+
className="mb-3 mt-7 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.12em] first:mt-0"
|
|
18
18
|
style={{ color: 'var(--color-text-muted)' }}
|
|
19
19
|
>
|
|
20
20
|
{icon}
|
|
21
21
|
{title}
|
|
22
|
+
<div
|
|
23
|
+
className="ml-1 h-px flex-1"
|
|
24
|
+
style={{ backgroundColor: 'var(--color-border-subtle)' }}
|
|
25
|
+
/>
|
|
22
26
|
</h3>
|
|
23
27
|
);
|
|
24
28
|
};
|
|
@@ -57,7 +57,7 @@ export const SettingsSelect = <T extends string | number>({
|
|
|
57
57
|
type="button"
|
|
58
58
|
onClick={() => !disabled && setIsOpen(!isOpen)}
|
|
59
59
|
disabled={disabled}
|
|
60
|
-
className={`flex h-8 items-center justify-between gap-2 rounded-md border bg-transparent px-2 text-sm transition-all duration-150 focus:outline-none focus:ring-1 focus:ring-
|
|
60
|
+
className={`flex h-8 items-center justify-between gap-2 rounded-md border bg-transparent px-2 text-sm transition-all duration-150 focus:outline-none focus:ring-1 focus:ring-[var(--color-border-emphasis)] ${fullWidth ? 'w-full' : 'min-w-[140px]'} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} ${isOpen ? 'ring-1 ring-[var(--color-border-emphasis)]' : ''} `}
|
|
61
61
|
style={{
|
|
62
62
|
color: 'var(--color-text-secondary)',
|
|
63
63
|
borderColor: isOpen ? 'var(--color-border)' : 'var(--color-border-subtle)',
|
|
@@ -85,12 +85,14 @@ export const SettingsSelect = <T extends string | number>({
|
|
|
85
85
|
type="button"
|
|
86
86
|
onClick={() => handleSelect(option.value)}
|
|
87
87
|
className={`flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors duration-100 ${
|
|
88
|
-
value === option.value
|
|
88
|
+
value === option.value
|
|
89
|
+
? 'bg-cyan-500/10 text-cyan-300'
|
|
90
|
+
: 'hover:bg-white/5'
|
|
89
91
|
} `}
|
|
90
92
|
style={value !== option.value ? { color: 'var(--color-text-secondary)' } : undefined}
|
|
91
93
|
>
|
|
92
94
|
<span className="whitespace-nowrap">{option.label}</span>
|
|
93
|
-
{value === option.value && <Check className="size-4 shrink-0 text-
|
|
95
|
+
{value === option.value && <Check className="size-4 shrink-0 text-cyan-400" />}
|
|
94
96
|
</button>
|
|
95
97
|
))}
|
|
96
98
|
</div>
|
|
@@ -27,9 +27,9 @@ export const SettingsToggle = ({
|
|
|
27
27
|
aria-checked={enabled}
|
|
28
28
|
disabled={disabled}
|
|
29
29
|
onClick={handleClick}
|
|
30
|
-
className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-
|
|
30
|
+
className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-[var(--color-text)]/20 focus:ring-offset-2 focus:ring-offset-[var(--color-surface)]"
|
|
31
31
|
style={{
|
|
32
|
-
backgroundColor: enabled ? '#
|
|
32
|
+
backgroundColor: enabled ? '#0891b2' : '#3f3f46',
|
|
33
33
|
opacity: disabled ? 0.5 : 1,
|
|
34
34
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
35
35
|
}}
|