@v587d/capital-generation 2.13.1 → 2.14.0
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/CHANGELOG.md +51 -0
- package/README.md +25 -26
- package/automation/builtins.js +9 -69
- package/automation/client.js +88 -32
- package/automation/client.src.cjs +924 -321
- package/automation/package.json +1 -1
- package/automation/runner.js +22 -13
- package/automation/schema.js +49 -20
- package/automation/service.js +2 -2
- package/automation/store.js +58 -6
- package/automation/time.js +30 -2
- package/chart-ui/package.json +1 -1
- package/config/package.json +1 -1
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +1 -1
- package/watchlist/package.json +1 -1
|
@@ -7,6 +7,7 @@ const { createElement: h, useEffect, useRef, useState } = require('react')
|
|
|
7
7
|
const {
|
|
8
8
|
IconAlarmClockOutlineRegular,
|
|
9
9
|
IconChecklistOutlineRegular,
|
|
10
|
+
IconChevronDownOutlineRegular,
|
|
10
11
|
IconChevronLeftOutlineRegular,
|
|
11
12
|
IconChevronRightOutlineRegular,
|
|
12
13
|
IconClockOutlineRegular,
|
|
@@ -23,6 +24,7 @@ const {
|
|
|
23
24
|
IconPlayOutlineRegular,
|
|
24
25
|
IconPlusOutlineRegular,
|
|
25
26
|
IconRefreshOutlineRegular,
|
|
27
|
+
IconRightUpOutlineRegular,
|
|
26
28
|
IconSearchOutlineRegular,
|
|
27
29
|
IconSparkleRegular,
|
|
28
30
|
IconTrashOutlineRegular,
|
|
@@ -39,6 +41,7 @@ const PANEL_ID = 'capital-automation'
|
|
|
39
41
|
const STYLE_TAG = 'capital-automation-css'
|
|
40
42
|
const ACTIVE_RUN_STATUSES = ['queued', 'starting', 'running']
|
|
41
43
|
const FAILURE_RUN_STATUSES = ['failed', 'timed_out', 'blocked_requires_user', 'unknown_after_restart']
|
|
44
|
+
const SKIPPED_RUN_STATUSES = ['skipped_overlap', 'skipped_calendar_unavailable', 'missed']
|
|
42
45
|
const DEFAULT_LIMITS = { runTimeoutMinutes: 30, idleTimeoutMinutes: 5, maxSteps: 300, maxToolCalls: 1200 }
|
|
43
46
|
const WEEKDAYS = [
|
|
44
47
|
{ value: 1, label: '一' },
|
|
@@ -49,8 +52,28 @@ const WEEKDAYS = [
|
|
|
49
52
|
{ value: 6, label: '六' },
|
|
50
53
|
{ value: 7, label: '日' },
|
|
51
54
|
]
|
|
52
|
-
const TIMEZONES = [
|
|
55
|
+
const TIMEZONES = [
|
|
56
|
+
'Asia/Shanghai',
|
|
57
|
+
'Asia/Hong_Kong',
|
|
58
|
+
'Asia/Taipei',
|
|
59
|
+
'Asia/Singapore',
|
|
60
|
+
'Asia/Tokyo',
|
|
61
|
+
'Asia/Seoul',
|
|
62
|
+
'UTC',
|
|
63
|
+
'Europe/London',
|
|
64
|
+
'Europe/Paris',
|
|
65
|
+
'Europe/Berlin',
|
|
66
|
+
'America/New_York',
|
|
67
|
+
'America/Chicago',
|
|
68
|
+
'America/Los_Angeles',
|
|
69
|
+
'America/Toronto',
|
|
70
|
+
'America/Sao_Paulo',
|
|
71
|
+
'Australia/Sydney',
|
|
72
|
+
]
|
|
53
73
|
const HISTORY_DAYS = 30
|
|
74
|
+
const DETAIL_RECENT_RUN_LIMIT = 3
|
|
75
|
+
const TITLE_MAX_LENGTH = 12
|
|
76
|
+
const DESCRIPTION_MAX_LENGTH = 100
|
|
54
77
|
|
|
55
78
|
const zh = {
|
|
56
79
|
panel: 'CapGen 投研自动化',
|
|
@@ -102,11 +125,8 @@ const zh = {
|
|
|
102
125
|
filterLabel: '筛选任务',
|
|
103
126
|
category: { news: '资讯', market: '行情', review: '复盘', research: '研究' },
|
|
104
127
|
categoryField: '任务分类',
|
|
105
|
-
categoryFixed: '
|
|
106
|
-
|
|
107
|
-
modeHint: '只影响输出结构,不改变 Agent preset;所有运行固定使用 Capital 自动模式。',
|
|
108
|
-
mode: { news: '资讯', market: '行情', review: '复盘', research: '研究' },
|
|
109
|
-
cadence: { daily: '每天', weekly: '每周', monthly: '每月' },
|
|
128
|
+
categoryFixed: `'复制为我的任务'后,可调整任务分类。`,
|
|
129
|
+
cadence: { daily: '每天', weekly: '每周', monthly: '每月', once: '单次' },
|
|
110
130
|
stateLabel: '状态',
|
|
111
131
|
state: { dormant: '未添加', active: '已启用', paused: '已暂停' },
|
|
112
132
|
origin: { builtin: '内置模板', custom: '我的任务' },
|
|
@@ -133,11 +153,17 @@ const zh = {
|
|
|
133
153
|
cancel: '取消',
|
|
134
154
|
create: '创建任务',
|
|
135
155
|
creating: '创建中…',
|
|
136
|
-
|
|
156
|
+
titleRequired: '标题不能为空。',
|
|
157
|
+
instructionRequired: '任务指令不能为空。',
|
|
158
|
+
invalidLimit: '请输入有效数字。',
|
|
137
159
|
dirty: '有未保存的修改',
|
|
138
160
|
noDirty: '配置已同步',
|
|
139
161
|
nextRun: '下次运行',
|
|
162
|
+
nextRunDormant: '添加后开始调度',
|
|
163
|
+
nextRunPaused: '启用后恢复调度',
|
|
164
|
+
notScheduled: '当前不会自动运行',
|
|
140
165
|
lastResult: '上次结果',
|
|
166
|
+
lastRunEmpty: '运行后可在这里查看结果',
|
|
141
167
|
never: '暂无',
|
|
142
168
|
calendarUnavailable: '日历不可用',
|
|
143
169
|
unread: '未读',
|
|
@@ -145,25 +171,47 @@ const zh = {
|
|
|
145
171
|
config: '配置',
|
|
146
172
|
runs: '运行记录',
|
|
147
173
|
notifications: '通知',
|
|
148
|
-
descriptionField: '
|
|
149
|
-
titleField: '
|
|
174
|
+
descriptionField: '任务说明',
|
|
175
|
+
titleField: '任务标题',
|
|
150
176
|
instructionField: '任务指令',
|
|
151
177
|
instructionPlaceholder: '例如:读取今日自选股行情、公告与相关新闻,按「结论—证据—风险」整理成一份可复核的盘后复盘。',
|
|
152
178
|
titlePlaceholder: '给这个任务起个名字',
|
|
179
|
+
charCount: '{n}/{max}',
|
|
153
180
|
descriptionPlaceholder: '一句话说明任务目标(可选)',
|
|
154
181
|
triggerField: '触发器',
|
|
155
|
-
|
|
156
|
-
|
|
182
|
+
cadenceField: '频率',
|
|
183
|
+
triggerHint: '按设定时区的本地墙钟时间计算;仅交易日任务由交易日历判断周末与节假日。',
|
|
184
|
+
triggerHintNatural: '按设定时区的本地墙钟时间计算:每天都触发,周末与节假日也运行。',
|
|
185
|
+
triggerHintOnce: '单次任务只在指定日期运行一次;该日期不是交易日时,仅交易日设置会跳过本次运行。',
|
|
157
186
|
timeField: '时间',
|
|
158
187
|
timezoneField: '时区',
|
|
159
188
|
weekdayField: '星期',
|
|
160
189
|
dayField: '日期',
|
|
190
|
+
monthlyField: '每月日期',
|
|
191
|
+
monthlyFirst: '1 号',
|
|
192
|
+
monthlyMid: '月中',
|
|
193
|
+
monthlyLast: '月末',
|
|
194
|
+
monthlyCustom: '自定义',
|
|
195
|
+
customDayLabel: '第 {n} 日',
|
|
196
|
+
dailyNatural: '每个自然日运行',
|
|
197
|
+
dailyTrading: '每个交易日运行',
|
|
198
|
+
onceHint: '只在这天运行一次',
|
|
199
|
+
onceEnded: '单次任务已结束',
|
|
200
|
+
onceEndedHint: '指定日期已过;修改日期或改成重复计划后可再次运行。',
|
|
201
|
+
onceSkipped: '单次任务不会运行',
|
|
202
|
+
onceSkippedHint: '指定日期不是交易日;关闭仅交易日或改选日期后再启用。',
|
|
203
|
+
weekdayWorkdayQuick: '工作日',
|
|
204
|
+
weekdayWeekendQuick: '周末',
|
|
205
|
+
weekdayNone: '未选择',
|
|
206
|
+
weekdayEvery: '每天',
|
|
207
|
+
done: '完成',
|
|
161
208
|
tradingDaysField: '仅交易日',
|
|
162
209
|
weekdayRequired: '每周至少选择一天。',
|
|
210
|
+
dateRequired: '请选择运行日期。',
|
|
211
|
+
invalidDate: '请选择有效的日期。',
|
|
163
212
|
invalidTime: '运行时间格式不正确,请选择 HH:mm。',
|
|
164
213
|
invalidTimezone: '时区不能为空,请填写 IANA 时区。',
|
|
165
214
|
invalidTimezoneValue: '时区不被识别,请填写有效的 IANA 时区。',
|
|
166
|
-
invalidLimits: '运行上限必须是有效数字。',
|
|
167
215
|
filterNoRuns: '没有符合筛选条件的运行记录。',
|
|
168
216
|
schedulePrefix: '下次',
|
|
169
217
|
workspaceField: '工作区',
|
|
@@ -182,16 +230,14 @@ const zh = {
|
|
|
182
230
|
notifyFailure: '失败时通知',
|
|
183
231
|
notifySuccess: '成功时通知',
|
|
184
232
|
notifyHint: '通知只记录在当前 profile;不会发送邮件或外部消息。',
|
|
185
|
-
presetLabel: '
|
|
186
|
-
presetValue: '
|
|
187
|
-
|
|
188
|
-
policyText: '固定 preset:capital-generation(Capital 自动模式)。工作区来自已注册列表;未选择时使用专属自动化工作区。无人值守运行禁止提问、写文件与 shell 提权。',
|
|
233
|
+
presetLabel: '运行模式',
|
|
234
|
+
presetValue: '自动模式',
|
|
235
|
+
presetHint: '所有运行固定使用自动模式。',
|
|
189
236
|
budgetHint: '工具调用与步骤预算覆盖整棵运行子树:子 Agent 与 PTC 派发都计入。',
|
|
190
237
|
environment: '执行环境',
|
|
191
238
|
policyField: '无人值守策略',
|
|
192
239
|
cwdKind: '工作区来源',
|
|
193
240
|
techInfo: '技术信息',
|
|
194
|
-
version: '版本',
|
|
195
241
|
profileKey: 'profileKey',
|
|
196
242
|
runHistory: '运行历史',
|
|
197
243
|
last30Days: '最近 30 天',
|
|
@@ -257,10 +303,9 @@ const zh = {
|
|
|
257
303
|
schedulerBlocked: '调度器未取得运行权,暂时不能手动运行。',
|
|
258
304
|
runBlocked: '任务正在运行,结束或取消后才能再次运行。',
|
|
259
305
|
configWhileRunning: '任务正在运行;修改会在下一次运行时生效。',
|
|
260
|
-
|
|
261
|
-
detailRecentRuns: '最近运行',
|
|
306
|
+
detailRecentRuns: '最近 {n} 次运行',
|
|
262
307
|
detailNoRuns: '还没有运行记录。',
|
|
263
|
-
detailAllRuns: '
|
|
308
|
+
detailAllRuns: '完整记录',
|
|
264
309
|
detailTaskNotifications: '任务通知',
|
|
265
310
|
taskRunsLimit: '这里显示当前任务最近 20 次运行;全部运行见「运行」页。',
|
|
266
311
|
runErrors: '{n} 条错误',
|
|
@@ -276,7 +321,6 @@ const zh = {
|
|
|
276
321
|
lastRun: '上次运行',
|
|
277
322
|
neverRun: '尚未运行',
|
|
278
323
|
relativeNow: '刚刚',
|
|
279
|
-
timezoneHint: 'IANA 时区,例如 Asia/Shanghai。',
|
|
280
324
|
nextRunUnavailable: '交易日历不可用,无法计算下一次运行。',
|
|
281
325
|
copyHint: '复制后会生成一个暂停状态的自定义任务。',
|
|
282
326
|
notificationTaskGone: '任务已不存在',
|
|
@@ -288,7 +332,9 @@ const zh = {
|
|
|
288
332
|
everyPrefix: '每',
|
|
289
333
|
weeklyPrefix: '每周',
|
|
290
334
|
monthlyPrefix: '每月',
|
|
335
|
+
monthlyLastDay: '最后一天',
|
|
291
336
|
dailyPrefix: '每天',
|
|
337
|
+
oncePrefix: '单次',
|
|
292
338
|
tradingSuffix: '(仅交易日)',
|
|
293
339
|
sessionMissing: '没有可打开的 session',
|
|
294
340
|
}
|
|
@@ -467,8 +513,6 @@ function installStyles() {
|
|
|
467
513
|
.capital-automation-herotitle { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
468
514
|
.capital-automation-herotitle h2 { margin: 0; font-size: 22px; line-height: 30px; font-weight: 600; letter-spacing: .1px; overflow-wrap: anywhere; }
|
|
469
515
|
.capital-automation-herodesc { max-width: 760px; margin: 5px 0 0; color: var(--ca-muted); font-size: 12.5px; line-height: 19px; }
|
|
470
|
-
.capital-automation-herometa { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 14px; margin-top: 10px; color: var(--ca-muted); font-size: 11.5px; line-height: 18px; }
|
|
471
|
-
.capital-automation-herometa > span { display: inline-flex; align-items: center; gap: 5px; }
|
|
472
516
|
.capital-automation-heroactions { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; flex: none; padding-top: 2px; }
|
|
473
517
|
.capital-automation-detailtabs { display: flex; align-items: center; gap: 26px; overflow-x: auto; overflow-y: hidden; border-bottom: .5px solid var(--ca-line); }
|
|
474
518
|
.capital-automation-detailtab { position: relative; flex: none; height: 42px; padding: 0 1px; border: 0; background: transparent; color: var(--ca-muted); font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer; }
|
|
@@ -487,11 +531,13 @@ function installStyles() {
|
|
|
487
531
|
.capital-automation-statvalue[data-tone="ok"] { color: var(--ca-success-text); }
|
|
488
532
|
.capital-automation-statvalue[data-tone="error"] { color: var(--ca-error-text); }
|
|
489
533
|
.capital-automation-statvalue[data-tone="running"] { color: var(--ca-business-text); }
|
|
534
|
+
.capital-automation-statvalue[data-tone="warn"] { color: var(--ca-warn-text); }
|
|
490
535
|
.capital-automation-statsub { color: var(--ca-caption); font-size: 11px; line-height: 16px; }
|
|
491
536
|
.capital-automation-panel { padding: 16px 18px; }
|
|
492
537
|
.capital-automation-panelhead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 12px; margin-bottom: 12px; }
|
|
493
538
|
.capital-automation-panelhead h3 { margin: 0; font-size: 13.5px; line-height: 20px; font-weight: 600; }
|
|
494
539
|
.capital-automation-panelhead .capital-automation-btn { margin-left: auto; }
|
|
540
|
+
.capital-automation-panelhead .capital-automation-btn[data-variant="ghost"] { padding-right: 0; }
|
|
495
541
|
.capital-automation-kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px 18px; }
|
|
496
542
|
.capital-automation-kvitem { min-width: 0; }
|
|
497
543
|
.capital-automation-kvlabel { display: block; color: var(--ca-caption); font-size: 11px; line-height: 16px; }
|
|
@@ -505,32 +551,73 @@ function installStyles() {
|
|
|
505
551
|
.capital-automation-error { margin: 0; color: var(--ca-error-text); font-size: 12px; line-height: 18px; }
|
|
506
552
|
.capital-automation-toast { position: fixed; bottom: 26px; left: 50%; z-index: 1300; max-width: min(520px, calc(100vw - 40px)); transform: translateX(-50%); padding: 8px 15px; border: .5px solid var(--ca-line-soft); border-radius: 999px; background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-base); box-shadow: 0 8px 26px rgba(0,0,0,.22); font-size: 12px; line-height: 18px; }
|
|
507
553
|
.capital-automation-form { display: flex; flex-direction: column; gap: 16px; }
|
|
554
|
+
.capital-automation-instructionrow { display: grid; grid-template-columns: minmax(320px, 1fr) minmax(420px, 520px); align-items: stretch; gap: 16px; }
|
|
555
|
+
.capital-automation-instructionside { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
508
556
|
.capital-automation-formgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
|
|
557
|
+
.capital-automation-titlerow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(150px, 210px); align-items: start; gap: 12px; }
|
|
509
558
|
.capital-automation-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
|
510
|
-
.capital-automation-
|
|
511
|
-
.capital-automation-
|
|
512
|
-
.capital-automation-
|
|
513
|
-
.capital-automation-field
|
|
514
|
-
.capital-automation-field input
|
|
515
|
-
.capital-automation-
|
|
516
|
-
.capital-automation-field
|
|
559
|
+
.capital-automation-fieldhead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; min-width: 0; }
|
|
560
|
+
.capital-automation-fieldlabel { color: var(--ca-muted); font-size: 11.5px; line-height: 17px; }
|
|
561
|
+
.capital-automation-fieldhint { color: var(--ca-caption); font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums; }
|
|
562
|
+
.capital-automation-field input { box-sizing: border-box; width: 100%; padding: 0 10px; border: .5px solid var(--ca-line-soft); border-radius: 10px; background: var(--ca-field); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12.5px; line-height: 32px; }
|
|
563
|
+
.capital-automation-field input { height: 34px; }
|
|
564
|
+
.capital-automation-texteditor { color: var(--dsw-alias-label-primary); font: inherit; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: auto; outline: 0; cursor: text; }
|
|
565
|
+
.capital-automation-field .capital-automation-texteditor { box-sizing: border-box; flex: 1 1 auto; min-height: 110px; padding: 9px 10px; border: .5px solid var(--ca-line-soft); border-radius: 10px; background: var(--ca-field); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12.5px; line-height: 19px; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: auto; outline: 0; cursor: text; }
|
|
566
|
+
.capital-automation-field .capital-automation-texteditor.capital-automation-descriptioninput { flex: none; height: 58px; min-height: 58px; }
|
|
567
|
+
.capital-automation-texteditor:empty:before { content: attr(data-placeholder); color: var(--ca-caption); pointer-events: none; }
|
|
568
|
+
.capital-automation-field .capital-automation-texteditor:hover { border-color: var(--ca-line); }
|
|
569
|
+
.capital-automation-field .capital-automation-texteditor:focus-visible { border-color: var(--ca-business); outline: 2px solid color-mix(in srgb, var(--ca-business) 22%, transparent); outline-offset: 0; }
|
|
570
|
+
.capital-automation-field[data-invalid="true"] .capital-automation-texteditor { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
571
|
+
.capital-automation-field[data-invalid="true"] .capital-automation-texteditor:focus-visible { border-color: var(--ca-error); outline-color: color-mix(in srgb, var(--ca-error) 22%, transparent); }
|
|
572
|
+
.capital-automation-field .capital-automation-texteditor[contenteditable="false"] { color: var(--ca-muted); opacity: .8; cursor: not-allowed; }
|
|
573
|
+
.capital-automation-field input:hover { border-color: var(--ca-line); }
|
|
574
|
+
.capital-automation-field input:focus-visible { border-color: var(--ca-business); outline: 2px solid color-mix(in srgb, var(--ca-business) 22%, transparent); outline-offset: 0; }
|
|
575
|
+
.capital-automation-field[data-invalid="true"] input { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
576
|
+
.capital-automation-field[data-invalid="true"] input:focus-visible { border-color: var(--ca-error); outline-color: color-mix(in srgb, var(--ca-error) 22%, transparent); }
|
|
577
|
+
.capital-automation-field input:disabled { color: var(--ca-muted); opacity: .8; }
|
|
578
|
+
.capital-automation-fielderror { color: var(--ca-error-text); font-size: 11px; line-height: 16px; }
|
|
579
|
+
.capital-automation-select { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; height: 34px; padding: 0 10px 0 12px; border: 0; border-radius: 10px; background: var(--ca-field); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12.5px; line-height: 20px; text-align: left; cursor: pointer; transition: background .15s, opacity .15s; }
|
|
580
|
+
.capital-automation-select:hover:not(:disabled) { background: var(--ca-hover); }
|
|
581
|
+
.capital-automation-select[data-block="true"] { width: 100%; }
|
|
582
|
+
.capital-automation-select[data-size="sm"] { height: 28px; padding: 0 8px 0 9px; border-radius: 8px; font-size: 11.5px; }
|
|
583
|
+
.capital-automation-select:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--ca-business)); outline-offset: 0; }
|
|
584
|
+
.capital-automation-select:disabled { opacity: .5; cursor: not-allowed; }
|
|
585
|
+
.capital-automation-selectvalue { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
586
|
+
.capital-automation-select > svg { flex: none; color: var(--ca-muted); }
|
|
517
587
|
.capital-automation-formactions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; padding-top: 14px; border-top: .5px solid var(--ca-line); }
|
|
518
588
|
.capital-automation-formactions[data-sticky="true"] { position: sticky; bottom: -1px; z-index: 2; margin-top: 2px; padding: 12px 0 4px; background: var(--dsw-alias-bg-base); }
|
|
519
589
|
.capital-automation-formstatus { margin-right: auto; color: var(--ca-caption); font-size: 11.5px; line-height: 18px; }
|
|
520
|
-
.capital-automation-triggerbox { display: flex; flex-direction: column; gap: 12px; padding:
|
|
521
|
-
.capital-automation-
|
|
522
|
-
.capital-automation-
|
|
523
|
-
.capital-automation-
|
|
524
|
-
.capital-automation-
|
|
525
|
-
.capital-automation-
|
|
526
|
-
.capital-automation-
|
|
527
|
-
.capital-automation-
|
|
528
|
-
.capital-automation-
|
|
529
|
-
.capital-automation-
|
|
530
|
-
.capital-automation-
|
|
531
|
-
.capital-automation-
|
|
590
|
+
.capital-automation-triggerbox { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px; border: .5px solid var(--ca-line); border-radius: 14px; background: var(--ca-field); }
|
|
591
|
+
.capital-automation-triggerbox[data-invalid="true"] { border-color: color-mix(in srgb, var(--ca-error) 52%, var(--ca-line)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ca-error) 9%, transparent); }
|
|
592
|
+
.capital-automation-triggerhead { display: flex; flex-wrap: nowrap; align-items: center; justify-content: space-between; gap: 12px; min-width: 0; }
|
|
593
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:first-child { flex: 1 1 auto; min-width: 0; }
|
|
594
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:last-child { flex: none; }
|
|
595
|
+
.capital-automation-triggerfield { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
|
|
596
|
+
.capital-automation-triggerfield .capital-automation-select { flex: none; width: 96px; height: 32px; padding: 0 8px 0 10px; border: .5px solid var(--ca-line-soft); border-radius: 9px; background: var(--ca-card); font-size: 12.5px; }
|
|
597
|
+
.capital-automation-triggerfield .capital-automation-select:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
598
|
+
.capital-automation-triggerlabel { color: var(--ca-muted); font-size: 11.5px; line-height: 16px; white-space: nowrap; }
|
|
599
|
+
.capital-automation-triggerbox input.capital-automation-triggertime, .capital-automation-triggerbox input.capital-automation-triggerdate { box-sizing: border-box; flex: none; width: 112px; height: 32px; padding: 0 8px; border: .5px solid var(--ca-line-soft); border-radius: 10px; background: var(--ca-card); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12.5px; font-variant-numeric: tabular-nums; line-height: normal; transition: border-color .15s, box-shadow .15s; }
|
|
600
|
+
.capital-automation-triggerbox input.capital-automation-triggerdate { flex: 0 1 146px; width: 146px; min-width: 120px; }
|
|
601
|
+
.capital-automation-triggerbox input.capital-automation-triggertime:hover:not(:disabled), .capital-automation-triggerbox input.capital-automation-triggerdate:hover:not(:disabled) { border-color: var(--ca-line); }
|
|
602
|
+
.capital-automation-triggerbox input.capital-automation-triggertime:focus-visible, .capital-automation-triggerbox input.capital-automation-triggerdate:focus-visible { border-color: var(--ca-business); outline: 2px solid color-mix(in srgb, var(--ca-business) 22%, transparent); outline-offset: 0; }
|
|
603
|
+
.capital-automation-triggerbox input.capital-automation-triggertime[data-invalid="true"], .capital-automation-triggerbox input.capital-automation-triggerdate[data-invalid="true"] { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
604
|
+
.capital-automation-triggerbox input.capital-automation-triggertime[data-invalid="true"]:focus-visible, .capital-automation-triggerbox input.capital-automation-triggerdate[data-invalid="true"]:focus-visible { border-color: var(--ca-error); outline-color: color-mix(in srgb, var(--ca-error) 22%, transparent); }
|
|
605
|
+
.capital-automation-triggerbox input.capital-automation-triggertime:disabled, .capital-automation-triggerbox input.capital-automation-triggerdate:disabled { opacity: .5; cursor: not-allowed; }
|
|
606
|
+
body[data-ds-dark-theme] .capital-automation-triggertime, body[data-ds-dark-theme] .capital-automation-triggerdate { color-scheme: dark; }
|
|
607
|
+
body:not([data-ds-dark-theme]) .capital-automation-triggertime, body:not([data-ds-dark-theme]) .capital-automation-triggerdate { color-scheme: light; }
|
|
608
|
+
.capital-automation-triggerdetail { display: flex; flex: 1 1 auto; align-items: center; gap: 6px; min-width: 0; min-height: 32px; }
|
|
609
|
+
.capital-automation-triggerdetail > .capital-automation-triggerfield { flex: 1 1 auto; min-width: 0; }
|
|
610
|
+
.capital-automation-triggerdetail .capital-automation-triggerpick { flex: 1 1 auto; width: auto; min-width: 0; max-width: 190px; height: 32px; padding: 0 8px 0 10px; border: .5px solid var(--ca-line-soft); border-radius: 9px; background: var(--ca-card); font-size: 12px; }
|
|
611
|
+
.capital-automation-triggerdetail .capital-automation-triggerpick:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
612
|
+
.capital-automation-monthpick [role="menu"] [role="menu"] { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2px; width: 196px; min-width: 0; max-height: 252px; overflow-y: auto; padding: 6px; }
|
|
613
|
+
.capital-automation-monthpick [role="menu"] [role="menu"] button { justify-content: center; min-width: 0; min-height: 26px; padding: 3px 4px; font-size: 12px; line-height: 18px; }
|
|
614
|
+
.capital-automation-triggersettings { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding-top: 12px; border-top: .5px solid var(--ca-line); }
|
|
615
|
+
.capital-automation-triggerzone { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
616
|
+
.capital-automation-triggersettings .capital-automation-select { max-width: 190px; height: 28px; padding: 0 8px 0 10px; border: .5px solid var(--ca-line-soft); border-radius: 9px; background: var(--ca-card); font-size: 11.5px; }
|
|
617
|
+
.capital-automation-triggersettings .capital-automation-select:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
618
|
+
.capital-automation-triggerswitch { display: inline-flex; flex: none; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; user-select: none; }
|
|
619
|
+
.capital-automation-triggererrors { display: flex; flex-direction: column; gap: 4px; }
|
|
532
620
|
.capital-automation-switch { display: inline-flex; align-items: center; gap: 7px; color: var(--ca-muted); font-size: 12px; }
|
|
533
|
-
.capital-automation-triggersummary { margin: 0; color: var(--ca-caption); font-size: 11.5px; line-height: 18px; }
|
|
534
621
|
.capital-automation-advanced { border-top: .5px solid var(--ca-line); padding-top: 12px; }
|
|
535
622
|
.capital-automation-advanced summary { color: var(--ca-muted); font-size: 12px; line-height: 18px; cursor: pointer; user-select: none; }
|
|
536
623
|
.capital-automation-advanced summary:hover { color: var(--dsw-alias-label-primary); }
|
|
@@ -589,19 +676,22 @@ function installStyles() {
|
|
|
589
676
|
.capital-automation-modalheadline { display: flex; align-items: center; gap: 10px; }
|
|
590
677
|
.capital-automation-modaltitleinput { flex: 1 1 auto; min-width: 0; height: 42px; padding: 0 12px; border: .5px solid var(--ca-line-soft); border-radius: 12px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font: inherit; font-size: 15px; font-weight: 500; }
|
|
591
678
|
.capital-automation-modaltitleinput:focus-visible { border-color: var(--ca-business); outline: 2px solid color-mix(in srgb, var(--ca-business) 22%, transparent); outline-offset: 0; }
|
|
679
|
+
.capital-automation-modalheadline[data-invalid="true"] .capital-automation-modaltitleinput { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
592
680
|
.capital-automation-formlabel { display: block; margin-bottom: 6px; color: var(--ca-muted); font-size: 11.5px; line-height: 17px; }
|
|
681
|
+
.capital-automation-promptfield { display: flex; flex-direction: column; gap: 5px; }
|
|
682
|
+
.capital-automation-promptfield[data-invalid="true"] .capital-automation-promptbox { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line)); }
|
|
593
683
|
.capital-automation-promptbox { overflow: hidden; border: .5px solid var(--ca-line); border-radius: 16px; background: var(--dsw-alias-bg-module-platform); }
|
|
594
|
-
.capital-automation-promptbox
|
|
595
|
-
.capital-automation-promptbox
|
|
684
|
+
.capital-automation-promptbox .capital-automation-texteditor { box-sizing: border-box; width: 100%; height: 120px; min-height: 120px; padding: 13px 14px; border: 0; border-radius: 0; background: transparent; font-size: 13px; line-height: 21px; }
|
|
685
|
+
.capital-automation-promptbox .capital-automation-texteditor:focus-visible { outline: 0; }
|
|
596
686
|
.capital-automation-prompttoolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; border-top: .5px solid var(--ca-line); }
|
|
597
|
-
.capital-automation-prompttoolbar label { display: inline-flex; align-items: center; gap: 5px; color: var(--ca-muted); font-size: 11.5px; }
|
|
598
|
-
.capital-automation-prompttoolbar select { max-width: 168px;
|
|
687
|
+
.capital-automation-prompttoolbar label, .capital-automation-toolbarfield { display: inline-flex; align-items: center; gap: 5px; color: var(--ca-muted); font-size: 11.5px; }
|
|
688
|
+
.capital-automation-prompttoolbar .capital-automation-select { max-width: 168px; background: var(--ca-card); }
|
|
599
689
|
.capital-automation-prompttoolbar .capital-automation-presetchip { margin-left: auto; }
|
|
600
690
|
.capital-automation-presetchip { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 9px; border-radius: 999px; background: color-mix(in srgb, var(--ca-business) 12%, var(--ca-card)); color: var(--ca-business-text); font-size: 11px; font-weight: 500; white-space: nowrap; }
|
|
601
691
|
.capital-automation-checklist { display: flex; flex-direction: column; gap: 2px; }
|
|
602
|
-
.capital-automation-technical { margin-top:
|
|
603
|
-
.capital-automation-
|
|
604
|
-
.capital-automation-technical dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin:
|
|
692
|
+
.capital-automation-technical { margin-top: 14px; padding-top: 12px; border-top: .5px solid var(--ca-line); color: var(--ca-caption); font-size: 11px; line-height: 17px; }
|
|
693
|
+
.capital-automation-technicaltitle { margin: 0 0 7px; color: var(--ca-muted); font-size: 11.5px; line-height: 17px; font-weight: 500; }
|
|
694
|
+
.capital-automation-technical dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; }
|
|
605
695
|
.capital-automation-technical dt { color: var(--ca-caption); }
|
|
606
696
|
.capital-automation-technical dd { min-width: 0; margin: 0; overflow-wrap: anywhere; color: var(--ca-muted); }
|
|
607
697
|
.capital-automation-page :focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--ca-business)); outline-offset: 2px; }
|
|
@@ -611,6 +701,9 @@ function installStyles() {
|
|
|
611
701
|
.capital-automation-theadmain > :nth-child(3), .capital-automation-cell[data-cell="next"] { display: none; }
|
|
612
702
|
.capital-automation-statgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
613
703
|
}
|
|
704
|
+
@media (max-width: 960px) {
|
|
705
|
+
.capital-automation-instructionrow { grid-template-columns: minmax(0, 1fr); }
|
|
706
|
+
}
|
|
614
707
|
@media (max-width: 760px) {
|
|
615
708
|
.capital-automation-head { padding-top: 14px; }
|
|
616
709
|
.capital-automation-topbar { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
@@ -628,8 +721,16 @@ function installStyles() {
|
|
|
628
721
|
.capital-automation-templates { grid-template-columns: 1fr; }
|
|
629
722
|
.capital-automation-sectiontools { width: 100%; margin-left: 0; }
|
|
630
723
|
}
|
|
724
|
+
@media (max-width: 560px) {
|
|
725
|
+
.capital-automation-triggerhead { flex-wrap: wrap; gap: 10px 12px; }
|
|
726
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:first-child { flex: 1 1 100%; flex-wrap: wrap; }
|
|
727
|
+
.capital-automation-triggerdetail { flex: 1 1 100%; }
|
|
728
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:last-child { flex: 1 1 100%; justify-content: flex-start; }
|
|
729
|
+
.capital-automation-monthpick [role="menu"] [role="menu"] { right: auto; left: 0; }
|
|
730
|
+
}
|
|
631
731
|
@media (max-width: 520px) {
|
|
632
732
|
.capital-automation-brand h1 { font-size: 16px; }
|
|
733
|
+
.capital-automation-titlerow { grid-template-columns: minmax(0, 1fr); }
|
|
633
734
|
.capital-automation-topactions .capital-automation-btn:not([data-variant="contrast"]) { width: auto; min-width: 32px; padding: 0 8px; }
|
|
634
735
|
.capital-automation-topactions .capital-automation-btn[data-variant="contrast"] { width: 32px; padding: 0; }
|
|
635
736
|
.capital-automation-topactions .capital-automation-btn .capital-automation-btnlabel { display: none; }
|
|
@@ -719,10 +820,6 @@ function clipTagLabel(label, limit = 12) {
|
|
|
719
820
|
return chars.length > limit ? `${chars.slice(0, limit).join('')}…` : text
|
|
720
821
|
}
|
|
721
822
|
|
|
722
|
-
function modeLabel(mode) {
|
|
723
|
-
return zh.mode[mode] ?? mode
|
|
724
|
-
}
|
|
725
|
-
|
|
726
823
|
function calendarLabel(calendar) {
|
|
727
824
|
if (calendar === null || calendar === undefined || calendar.available === false) return zh.calendarSourceNone
|
|
728
825
|
const source = calendar.source === 'szse'
|
|
@@ -824,26 +921,61 @@ function formatRangeLabel(date) {
|
|
|
824
921
|
return new Intl.DateTimeFormat('zh-CN', { month: 'short', day: 'numeric' }).format(date)
|
|
825
922
|
}
|
|
826
923
|
|
|
924
|
+
function weekdayText(trigger) {
|
|
925
|
+
return (trigger.weekdays ?? []).slice().sort((left, right) => left - right)
|
|
926
|
+
.map((day) => WEEKDAYS.find((item) => item.value === day)?.label ?? '')
|
|
927
|
+
.join('、')
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function monthlySummary(trigger) {
|
|
931
|
+
if (trigger.dayOfMonth === 'last') return '每月最后一天'
|
|
932
|
+
return `每月 ${trigger.dayOfMonth ?? 1} 日`
|
|
933
|
+
}
|
|
934
|
+
|
|
827
935
|
function scheduleSummary(trigger) {
|
|
828
936
|
if (trigger === null || trigger === undefined) return zh.never
|
|
829
937
|
const suffix = trigger.tradingDaysOnly === true ? zh.tradingSuffix : ''
|
|
830
|
-
if (trigger.cadence === 'weekly') {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
if (trigger.cadence === 'monthly') {
|
|
835
|
-
return `${zh.monthlyPrefix} ${trigger.dayOfMonth ?? 1} 日 ${trigger.time}${suffix}`
|
|
836
|
-
}
|
|
938
|
+
if (trigger.cadence === 'weekly') return `${zh.weeklyPrefix}${weekdayText(trigger)} ${trigger.time}${suffix}`
|
|
939
|
+
if (trigger.cadence === 'monthly') return `${monthlySummary(trigger)} ${trigger.time}${suffix}`
|
|
940
|
+
if (trigger.cadence === 'once') return `${formatDateLabel(trigger.date)} ${trigger.time}${suffix}`
|
|
837
941
|
return `${zh.dailyPrefix} ${trigger.time}${suffix}`
|
|
838
942
|
}
|
|
839
943
|
|
|
840
944
|
function scheduleRangeSummary(trigger) {
|
|
841
|
-
if (trigger?.cadence === 'weekly') {
|
|
842
|
-
|
|
843
|
-
|
|
945
|
+
if (trigger?.cadence === 'weekly') return `${zh.weeklyPrefix}${weekdayText(trigger)}`
|
|
946
|
+
if (trigger?.cadence === 'monthly') return monthlySummary(trigger)
|
|
947
|
+
if (trigger?.cadence === 'once') return formatDateLabel(trigger.date)
|
|
948
|
+
return zh.dailyPrefix
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Dormant/paused tasks still carry a computed nextRunLocal, but that instant will not fire until
|
|
952
|
+
// the task is activated; don't present it as a pending schedule.
|
|
953
|
+
function nextRunView(task) {
|
|
954
|
+
if (task?.state === 'dormant') return { value: zh.nextRunDormant, sub: zh.notScheduled }
|
|
955
|
+
if (task?.state === 'paused') return { value: zh.nextRunPaused, sub: zh.notScheduled }
|
|
956
|
+
if (task?.trigger?.cadence === 'once' && !task.nextRunAt && task.nextRunUnavailable !== true) {
|
|
957
|
+
return onceHasPassed(task.trigger)
|
|
958
|
+
? { value: zh.onceEnded, sub: zh.onceEndedHint }
|
|
959
|
+
: { value: zh.onceSkipped, sub: zh.onceSkippedHint }
|
|
844
960
|
}
|
|
845
|
-
|
|
846
|
-
|
|
961
|
+
const value = task?.nextRunLocal ?? (task?.nextRunUnavailable ? zh.calendarUnavailable : zh.never)
|
|
962
|
+
const sub = task?.nextRunAt ? formatRelative(task.nextRunAt) : (task?.nextRunUnavailable ? zh.nextRunUnavailable : zh.never)
|
|
963
|
+
return { value, sub }
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function lastRunView(run, timeZone) {
|
|
967
|
+
if (run === null || run === undefined) return { value: zh.neverRun, sub: zh.lastRunEmpty }
|
|
968
|
+
const active = isActiveRun(run)
|
|
969
|
+
const parts = [formatShortTime(run.queuedAt, timeZone)]
|
|
970
|
+
if (!active && typeof run.durationMs === 'number') parts.push(formatDuration(run.durationMs))
|
|
971
|
+
const errors = Array.isArray(run.errors) ? run.errors.length : 0
|
|
972
|
+
if (errors > 0) parts.push(zh.runErrors.replace('{n}', String(errors)))
|
|
973
|
+
let tone
|
|
974
|
+
if (run.status === 'succeeded') tone = 'ok'
|
|
975
|
+
else if (isFailureStatus(run.status)) tone = 'error'
|
|
976
|
+
else if (active) tone = 'running'
|
|
977
|
+
else if (SKIPPED_RUN_STATUSES.includes(run.status)) tone = 'warn'
|
|
978
|
+
return { value: statusLabel(run.status), sub: parts.filter(Boolean).join(' · '), tone }
|
|
847
979
|
}
|
|
848
980
|
|
|
849
981
|
function workspaceOptions(meta, current) {
|
|
@@ -864,16 +996,120 @@ function workspaceTitle(meta, id) {
|
|
|
864
996
|
return workspaceOptions(meta, id).find((item) => item.id === id)?.title ?? zh.workspaceMissing
|
|
865
997
|
}
|
|
866
998
|
|
|
999
|
+
function timezoneOptions(current) {
|
|
1000
|
+
const zone = String(current ?? '').trim()
|
|
1001
|
+
const zones = zone.length > 0 && !TIMEZONES.includes(zone) ? [zone, ...TIMEZONES] : TIMEZONES
|
|
1002
|
+
return zones.map((value) => ({ value, label: value }))
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function dateStringFromParts(year, month, day) {
|
|
1006
|
+
const date = new Date(Date.UTC(year, month - 1, day))
|
|
1007
|
+
return date.toISOString().slice(0, 10)
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function zonedToday(timeZone) {
|
|
1011
|
+
try {
|
|
1012
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
1013
|
+
timeZone,
|
|
1014
|
+
year: 'numeric',
|
|
1015
|
+
month: '2-digit',
|
|
1016
|
+
day: '2-digit',
|
|
1017
|
+
}).formatToParts(new Date())
|
|
1018
|
+
const read = (type) => Number(parts.find((part) => part.type === type)?.value)
|
|
1019
|
+
return dateStringFromParts(read('year'), read('month'), read('day'))
|
|
1020
|
+
} catch {
|
|
1021
|
+
return new Date().toISOString().slice(0, 10)
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function tomorrowInZone(timeZone) {
|
|
1026
|
+
const [year, month, day] = zonedToday(timeZone).split('-').map(Number)
|
|
1027
|
+
return dateStringFromParts(year, month, day + 1)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function nextWeekdayOnOrAfter(value) {
|
|
1031
|
+
let current = value
|
|
1032
|
+
for (let attempt = 0; attempt < 7; attempt += 1) {
|
|
1033
|
+
const weekday = new Date(`${current}T00:00:00Z`).getUTCDay()
|
|
1034
|
+
if (weekday !== 0 && weekday !== 6) return current
|
|
1035
|
+
const [year, month, day] = current.split('-').map(Number)
|
|
1036
|
+
current = dateStringFromParts(year, month, day + 1)
|
|
1037
|
+
}
|
|
1038
|
+
return current
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/** A default single-run date must not land on a weekend while trading-only is on. */
|
|
1042
|
+
function defaultOnceDate(timeZone, tradingDaysOnly) {
|
|
1043
|
+
const tomorrow = tomorrowInZone(timeZone)
|
|
1044
|
+
return tradingDaysOnly === true ? nextWeekdayOnOrAfter(tomorrow) : tomorrow
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function zonedNowParts(timeZone) {
|
|
1048
|
+
try {
|
|
1049
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
1050
|
+
timeZone,
|
|
1051
|
+
year: 'numeric',
|
|
1052
|
+
month: '2-digit',
|
|
1053
|
+
day: '2-digit',
|
|
1054
|
+
hour: '2-digit',
|
|
1055
|
+
minute: '2-digit',
|
|
1056
|
+
hourCycle: 'h23',
|
|
1057
|
+
}).formatToParts(new Date())
|
|
1058
|
+
const read = (type) => Number(parts.find((part) => part.type === type)?.value)
|
|
1059
|
+
return {
|
|
1060
|
+
date: dateStringFromParts(read('year'), read('month'), read('day')),
|
|
1061
|
+
minutes: read('hour') * 60 + read('minute'),
|
|
1062
|
+
}
|
|
1063
|
+
} catch {
|
|
1064
|
+
const now = new Date()
|
|
1065
|
+
return { date: now.toISOString().slice(0, 10), minutes: now.getUTCHours() * 60 + now.getUTCMinutes() }
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/** Distinguishes an expired single run from a future date that the calendar will skip. */
|
|
1070
|
+
function onceHasPassed(trigger) {
|
|
1071
|
+
if (trigger?.cadence !== 'once' || !isValidDateInput(trigger.date)) return false
|
|
1072
|
+
const now = zonedNowParts(trigger.timezone)
|
|
1073
|
+
if (trigger.date !== now.date) return trigger.date < now.date
|
|
1074
|
+
const [hour, minute] = String(trigger.time ?? '').split(':').map(Number)
|
|
1075
|
+
if (!Number.isInteger(hour) || !Number.isInteger(minute)) return false
|
|
1076
|
+
return now.minutes >= hour * 60 + minute
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function isValidDateInput(value) {
|
|
1080
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value ?? ''))
|
|
1081
|
+
if (match === null) return false
|
|
1082
|
+
const year = Number(match[1])
|
|
1083
|
+
const month = Number(match[2])
|
|
1084
|
+
const day = Number(match[3])
|
|
1085
|
+
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31) return false
|
|
1086
|
+
const date = new Date(Date.UTC(year, month - 1, day))
|
|
1087
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function formatDateLabel(value) {
|
|
1091
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value ?? ''))
|
|
1092
|
+
if (match === null) return String(value ?? '')
|
|
1093
|
+
return `${Number(match[1])} 年 ${Number(match[2])} 月 ${Number(match[3])} 日`
|
|
1094
|
+
}
|
|
1095
|
+
|
|
867
1096
|
function normalizeTrigger(trigger) {
|
|
1097
|
+
const cadence = Object.prototype.hasOwnProperty.call(zh.cadence, trigger?.cadence) ? trigger.cadence : 'daily'
|
|
868
1098
|
const next = {
|
|
869
1099
|
kind: 'schedule',
|
|
870
|
-
cadence
|
|
1100
|
+
cadence,
|
|
871
1101
|
time: trigger?.time ?? '09:00',
|
|
872
1102
|
timezone: trigger?.timezone ?? 'Asia/Shanghai',
|
|
873
1103
|
tradingDaysOnly: trigger?.tradingDaysOnly === true,
|
|
874
1104
|
}
|
|
875
1105
|
if (next.cadence === 'weekly') next.weekdays = (trigger?.weekdays ?? [1, 3, 5]).slice().sort((left, right) => left - right)
|
|
876
|
-
if (next.cadence === 'monthly')
|
|
1106
|
+
if (next.cadence === 'monthly') {
|
|
1107
|
+
const day = trigger?.dayOfMonth
|
|
1108
|
+
next.dayOfMonth = day === 'last' || (Number.isInteger(day) && day >= 1 && day <= 31) ? day : 1
|
|
1109
|
+
}
|
|
1110
|
+
if (next.cadence === 'once') {
|
|
1111
|
+
next.date = typeof trigger?.date === 'string' ? trigger.date : defaultOnceDate(next.timezone, next.tradingDaysOnly)
|
|
1112
|
+
}
|
|
877
1113
|
return next
|
|
878
1114
|
}
|
|
879
1115
|
|
|
@@ -890,7 +1126,6 @@ function defaultDraft() {
|
|
|
890
1126
|
instruction: '',
|
|
891
1127
|
category: 'research',
|
|
892
1128
|
trigger: { kind: 'schedule', cadence: 'daily', time: '09:00', timezone: 'Asia/Shanghai', tradingDaysOnly: true },
|
|
893
|
-
mode: 'research',
|
|
894
1129
|
workspaceId: '',
|
|
895
1130
|
limits: { ...DEFAULT_LIMITS },
|
|
896
1131
|
notifications: { inApp: true, onFailure: true, onSuccess: false },
|
|
@@ -904,7 +1139,6 @@ function draftPayload(task) {
|
|
|
904
1139
|
instruction: task.instruction ?? '',
|
|
905
1140
|
category: task.category,
|
|
906
1141
|
trigger: normalizeTrigger(task.trigger),
|
|
907
|
-
mode: task.mode,
|
|
908
1142
|
workspaceId: task.workspaceId ?? '',
|
|
909
1143
|
limits: task.limits,
|
|
910
1144
|
notifications: task.notifications,
|
|
@@ -974,19 +1208,50 @@ function draftChanged(draft, task) {
|
|
|
974
1208
|
return !samePayload(draft, task)
|
|
975
1209
|
}
|
|
976
1210
|
|
|
977
|
-
function
|
|
978
|
-
|
|
979
|
-
if (
|
|
980
|
-
if (String(task.
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1211
|
+
function draftIssues(task) {
|
|
1212
|
+
const issues = {}
|
|
1213
|
+
if (String(task.title ?? '').trim().length === 0) issues.title = zh.titleRequired
|
|
1214
|
+
if (String(task.instruction ?? '').trim().length === 0) issues.instruction = zh.instructionRequired
|
|
1215
|
+
const trigger = task.trigger ?? {}
|
|
1216
|
+
if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(trigger.time ?? '')) issues.time = zh.invalidTime
|
|
1217
|
+
const timezone = String(trigger.timezone ?? '').trim()
|
|
1218
|
+
if (timezone.length === 0) issues.timezone = zh.invalidTimezone
|
|
1219
|
+
else {
|
|
1220
|
+
try {
|
|
1221
|
+
new Intl.DateTimeFormat('zh-CN', { timeZone: timezone })
|
|
1222
|
+
} catch {
|
|
1223
|
+
issues.timezone = zh.invalidTimezoneValue
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (trigger.cadence === 'weekly' && (trigger.weekdays ?? []).length === 0) issues.weekdays = zh.weekdayRequired
|
|
1227
|
+
if (trigger.cadence === 'once') {
|
|
1228
|
+
if (typeof trigger.date !== 'string' || trigger.date.length === 0) issues.date = zh.dateRequired
|
|
1229
|
+
else if (!isValidDateInput(trigger.date)) issues.date = zh.invalidDate
|
|
985
1230
|
}
|
|
986
|
-
if (task.trigger?.cadence === 'weekly' && (task.trigger.weekdays ?? []).length === 0) return zh.weekdayRequired
|
|
987
1231
|
const limits = task.limits ?? {}
|
|
988
|
-
|
|
989
|
-
|
|
1232
|
+
for (const key of ['runTimeoutMinutes', 'idleTimeoutMinutes', 'maxSteps', 'maxToolCalls']) {
|
|
1233
|
+
if (!Number.isFinite(limits[key])) issues[key] = zh.invalidLimit
|
|
1234
|
+
}
|
|
1235
|
+
return issues
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Tracks which fields the user has left so errors appear on blur, then update live.
|
|
1240
|
+
* `resetKey` clears that state when the edited task or modal session changes.
|
|
1241
|
+
*/
|
|
1242
|
+
function useDraftIssues(task, resetKey) {
|
|
1243
|
+
const [touched, setTouched] = useState({})
|
|
1244
|
+
const issues = draftIssues(task)
|
|
1245
|
+
useEffect(() => { setTouched({}) }, [resetKey])
|
|
1246
|
+
const errorFor = (field) => (touched[field] === true ? issues[field] : undefined)
|
|
1247
|
+
const touch = (field) => setTouched((current) => (current[field] === true ? current : { ...current, [field]: true }))
|
|
1248
|
+
const touchAll = () => setTouched((current) => {
|
|
1249
|
+
const next = { ...current }
|
|
1250
|
+
for (const field of Object.keys(issues)) next[field] = true
|
|
1251
|
+
return next
|
|
1252
|
+
})
|
|
1253
|
+
const clear = () => setTouched({})
|
|
1254
|
+
return { issues, errorFor, touch, touchAll, clear }
|
|
990
1255
|
}
|
|
991
1256
|
|
|
992
1257
|
function limitValue(meta, key, fallback) {
|
|
@@ -1039,6 +1304,21 @@ function RunRow(props) {
|
|
|
1039
1304
|
const errorCount = Array.isArray(run.errors) ? run.errors.length : 0
|
|
1040
1305
|
const [errorsOpen, setErrorsOpen] = useState(false)
|
|
1041
1306
|
const errorPanelId = `capital-automation-run-errors-${run.id}`
|
|
1307
|
+
const errorBadge = errorCount > 0
|
|
1308
|
+
? h('button', {
|
|
1309
|
+
type: 'button',
|
|
1310
|
+
className: 'capital-automation-runerrorbadge',
|
|
1311
|
+
'data-tone': isFailureStatus(run.status) ? 'error' : 'warn',
|
|
1312
|
+
'aria-expanded': errorsOpen ? 'true' : 'false',
|
|
1313
|
+
'aria-controls': errorPanelId,
|
|
1314
|
+
'aria-label': `${zh.runErrors.replace('{n}', String(errorCount))},${errorsOpen ? zh.hideErrorDetails : zh.showErrorDetails}`,
|
|
1315
|
+
title: errorsOpen ? zh.hideErrorDetails : zh.showErrorDetails,
|
|
1316
|
+
onClick: () => setErrorsOpen((current) => !current),
|
|
1317
|
+
},
|
|
1318
|
+
h(IconWarningOutlineRegular, { size: 12 }),
|
|
1319
|
+
zh.runErrors.replace('{n}', String(errorCount)),
|
|
1320
|
+
h(IconChevronRightOutlineRegular, { size: 12 }))
|
|
1321
|
+
: null
|
|
1042
1322
|
return h('div', { className: 'capital-automation-run' },
|
|
1043
1323
|
h('div', { className: 'capital-automation-runhead' },
|
|
1044
1324
|
h('div', { className: 'capital-automation-runmain' },
|
|
@@ -1050,27 +1330,15 @@ function RunRow(props) {
|
|
|
1050
1330
|
h('div', { className: 'capital-automation-runmetaline' },
|
|
1051
1331
|
h('span', { className: 'capital-automation-runmeta' }, `${formatShortTime(run.queuedAt, task?.trigger?.timezone)} · ${run.trigger === 'manual' ? zh.triggerManual : zh.triggerScheduled}`),
|
|
1052
1332
|
typeof run.durationMs === 'number' ? h('span', { className: 'capital-automation-runmeta' }, `${zh.duration} ${formatDuration(run.durationMs)}`) : null,
|
|
1053
|
-
h('span', { className: 'capital-automation-runmeta' }, `${zh.steps} ${run.steps} · ${zh.tools} ${run.toolCalls}`)
|
|
1333
|
+
h('span', { className: 'capital-automation-runmeta' }, `${zh.steps} ${run.steps} · ${zh.tools} ${run.toolCalls}`),
|
|
1334
|
+
errorBadge)),
|
|
1054
1335
|
h('div', { className: 'capital-automation-runactions' },
|
|
1055
|
-
errorCount > 0
|
|
1056
|
-
? h('button', {
|
|
1057
|
-
type: 'button',
|
|
1058
|
-
className: 'capital-automation-runerrorbadge',
|
|
1059
|
-
'data-tone': isFailureStatus(run.status) ? 'error' : 'warn',
|
|
1060
|
-
'aria-expanded': errorsOpen ? 'true' : 'false',
|
|
1061
|
-
'aria-controls': errorPanelId,
|
|
1062
|
-
'aria-label': `${zh.runErrors.replace('{n}', String(errorCount))},${errorsOpen ? zh.hideErrorDetails : zh.showErrorDetails}`,
|
|
1063
|
-
title: errorsOpen ? zh.hideErrorDetails : zh.showErrorDetails,
|
|
1064
|
-
onClick: () => setErrorsOpen((current) => !current),
|
|
1065
|
-
},
|
|
1066
|
-
h(IconWarningOutlineRegular, { size: 12 }),
|
|
1067
|
-
zh.runErrors.replace('{n}', String(errorCount)),
|
|
1068
|
-
h(IconChevronRightOutlineRegular, { size: 12 }))
|
|
1069
|
-
: null,
|
|
1070
1336
|
active && typeof onCancel === 'function'
|
|
1071
1337
|
? h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', 'data-variant': 'danger', disabled: busy, onClick: () => onCancel(run) }, zh.cancelRun)
|
|
1072
1338
|
: run.sessionId && onOpenSession
|
|
1073
|
-
? h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', onClick: () => onOpenSession(run.sessionId) },
|
|
1339
|
+
? h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', 'data-variant': 'primary', onClick: () => onOpenSession(run.sessionId) },
|
|
1340
|
+
h(IconRightUpOutlineRegular, { size: 13 }),
|
|
1341
|
+
zh.openSession)
|
|
1074
1342
|
: null)),
|
|
1075
1343
|
errorsOpen && errorCount > 0
|
|
1076
1344
|
? h('div', { id: errorPanelId, className: 'capital-automation-runerrors', role: 'region', 'aria-label': zh.errorDetailsLabel },
|
|
@@ -1266,7 +1534,7 @@ function TaskRow(props) {
|
|
|
1266
1534
|
const { task, onOpen, menuOpen, onMenuToggle, onMenuClose, onEdit, onAction, onRequestConfirm, canRun, busy, mutationsDisabled } = props
|
|
1267
1535
|
const run = task.lastRun
|
|
1268
1536
|
const running = isActiveRun(run)
|
|
1269
|
-
const
|
|
1537
|
+
const next = nextRunView(task)
|
|
1270
1538
|
return h('div', { className: 'capital-automation-trow' },
|
|
1271
1539
|
h('button', { type: 'button', className: 'capital-automation-trowmain', onClick: () => onOpen(task.id) },
|
|
1272
1540
|
h('span', { className: 'capital-automation-cell capital-automation-identity' },
|
|
@@ -1281,8 +1549,8 @@ function TaskRow(props) {
|
|
|
1281
1549
|
h('span', { className: 'capital-automation-schedule' }, scheduleSummary(task.trigger)),
|
|
1282
1550
|
h('span', { className: 'capital-automation-schedulesub' }, task.trigger?.timezone ?? '')),
|
|
1283
1551
|
h('span', { className: 'capital-automation-cell', 'data-cell': 'next' },
|
|
1284
|
-
h('span', { className: 'capital-automation-nextmain' },
|
|
1285
|
-
h('span', { className: 'capital-automation-nextsub' },
|
|
1552
|
+
h('span', { className: 'capital-automation-nextmain' }, next.value),
|
|
1553
|
+
h('span', { className: 'capital-automation-nextsub' }, next.sub)),
|
|
1286
1554
|
h('span', { className: 'capital-automation-cell' },
|
|
1287
1555
|
h(StateBadge, { state: task.state, running }))),
|
|
1288
1556
|
h('div', { className: 'capital-automation-trowmenu' },
|
|
@@ -1347,7 +1615,9 @@ function TemplateCard(props) {
|
|
|
1347
1615
|
h('span', { className: 'capital-automation-templatemeta' },
|
|
1348
1616
|
h(IconClockOutlineRegular, { size: 12 }),
|
|
1349
1617
|
`${zh.templateMeta} · ${scheduleSummary(task.trigger)}`),
|
|
1350
|
-
h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', 'data-variant': 'primary', onClick: () => onEnable(task), disabled: busy || disabled },
|
|
1618
|
+
h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', 'data-variant': 'primary', onClick: () => onEnable(task), disabled: busy || disabled },
|
|
1619
|
+
h(IconPlusOutlineRegular, { size: 12 }),
|
|
1620
|
+
busy ? zh.templateAdding : zh.templateAdd)))
|
|
1351
1621
|
}
|
|
1352
1622
|
|
|
1353
1623
|
function EmptyState(props) {
|
|
@@ -1597,75 +1867,233 @@ function TaskDashboard(props) {
|
|
|
1597
1867
|
: null)
|
|
1598
1868
|
}
|
|
1599
1869
|
|
|
1600
|
-
function
|
|
1601
|
-
const {
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1870
|
+
function WeekdaySelect(props) {
|
|
1871
|
+
const { value, onChange, disabled } = props
|
|
1872
|
+
const [open, setOpen] = useState(false)
|
|
1873
|
+
const selected = Array.isArray(value) ? value : []
|
|
1874
|
+
const has = (day) => selected.includes(day)
|
|
1875
|
+
const exact = (days) => selected.length === days.length && days.every(has)
|
|
1876
|
+
const workday = exact([1, 2, 3, 4, 5])
|
|
1877
|
+
const weekend = exact([6, 7])
|
|
1878
|
+
const everyDay = exact(WEEKDAYS.map((day) => day.value))
|
|
1879
|
+
const summary = selected.length === 0
|
|
1880
|
+
? zh.weekdayNone
|
|
1881
|
+
: everyDay
|
|
1882
|
+
? zh.weekdayEvery
|
|
1883
|
+
: workday
|
|
1884
|
+
? zh.weekdayWorkdayQuick
|
|
1885
|
+
: weekend
|
|
1886
|
+
? zh.weekdayWeekendQuick
|
|
1887
|
+
: WEEKDAYS.filter((day) => has(day.value)).map((day) => day.label).join('、')
|
|
1888
|
+
useEffect(() => {
|
|
1889
|
+
if (disabled === true && open) setOpen(false)
|
|
1890
|
+
}, [disabled, open])
|
|
1891
|
+
const items = [
|
|
1892
|
+
{ id: 'workday', label: zh.weekdayWorkdayQuick },
|
|
1893
|
+
{ id: 'weekend', label: zh.weekdayWeekendQuick },
|
|
1894
|
+
{ type: 'separator', id: 'weekday-separator' },
|
|
1895
|
+
...WEEKDAYS.map((day) => ({ id: `weekday-${day.value}`, label: `周${day.label}` })),
|
|
1896
|
+
]
|
|
1897
|
+
const selectedIds = [
|
|
1898
|
+
...selected.map((day) => `weekday-${day}`),
|
|
1899
|
+
...(workday ? ['workday'] : []),
|
|
1900
|
+
...(weekend ? ['weekend'] : []),
|
|
1901
|
+
]
|
|
1902
|
+
const pick = (id) => {
|
|
1903
|
+
if (id === 'weekday-done') { setOpen(false); return }
|
|
1904
|
+
if (id === 'workday') { onChange([1, 2, 3, 4, 5]); return }
|
|
1905
|
+
if (id === 'weekend') { onChange([6, 7]); return }
|
|
1906
|
+
const day = Number(id.slice('weekday-'.length))
|
|
1907
|
+
if (!WEEKDAYS.some((item) => item.value === day)) return
|
|
1908
|
+
const next = has(day) ? selected.filter((item) => item !== day) : [...selected, day].sort((left, right) => left - right)
|
|
1909
|
+
if (next.length > 0) onChange(next)
|
|
1606
1910
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1911
|
+
return h(Menu, {
|
|
1912
|
+
open,
|
|
1913
|
+
anchor: h('button', {
|
|
1914
|
+
type: 'button',
|
|
1915
|
+
className: 'capital-automation-select capital-automation-triggerpick',
|
|
1916
|
+
disabled,
|
|
1917
|
+
'aria-label': `${zh.weekdayField}:${summary}`,
|
|
1918
|
+
'aria-haspopup': 'menu',
|
|
1919
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1920
|
+
title: `${zh.weekdayField}:${summary}`,
|
|
1921
|
+
onClick: () => setOpen((current) => !current),
|
|
1922
|
+
},
|
|
1923
|
+
h('span', { className: 'capital-automation-selectvalue' }, summary),
|
|
1924
|
+
h(IconChevronDownOutlineRegular, { size: 14 })),
|
|
1925
|
+
items,
|
|
1926
|
+
selectedIds,
|
|
1927
|
+
onSelect: pick,
|
|
1928
|
+
onClose: () => setOpen(false),
|
|
1929
|
+
footer: [{ id: 'weekday-done', label: zh.done }],
|
|
1930
|
+
align: 'start',
|
|
1931
|
+
portal: true,
|
|
1932
|
+
})
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function MonthlySelect(props) {
|
|
1936
|
+
const { value, onChange, disabled } = props
|
|
1937
|
+
const [open, setOpen] = useState(false)
|
|
1938
|
+
const day = value === 'last' ? 'last' : Number(value ?? 1)
|
|
1939
|
+
const summary = day === 'last'
|
|
1940
|
+
? zh.monthlyLast
|
|
1941
|
+
: day === 1
|
|
1942
|
+
? zh.monthlyFirst
|
|
1943
|
+
: day === 15
|
|
1944
|
+
? zh.monthlyMid
|
|
1945
|
+
: zh.dayOfMonth.replace('{n}', String(day))
|
|
1946
|
+
const selectedId = day === 'last' ? 'last' : day === 1 ? 'first' : day === 15 ? 'mid' : 'custom'
|
|
1947
|
+
useEffect(() => {
|
|
1948
|
+
if (disabled === true && open) setOpen(false)
|
|
1949
|
+
}, [disabled, open])
|
|
1950
|
+
const items = [
|
|
1951
|
+
{ id: 'first', label: zh.monthlyFirst },
|
|
1952
|
+
{ id: 'mid', label: zh.monthlyMid },
|
|
1953
|
+
{ id: 'last', label: zh.monthlyLast },
|
|
1954
|
+
{
|
|
1955
|
+
id: 'custom',
|
|
1956
|
+
label: zh.monthlyCustom,
|
|
1957
|
+
submenu: Array.from({ length: 31 }, (_, index) => ({
|
|
1958
|
+
id: `monthly-day-${index + 1}`,
|
|
1959
|
+
label: zh.dayOfMonth.replace('{n}', String(index + 1)),
|
|
1960
|
+
})),
|
|
1961
|
+
},
|
|
1962
|
+
]
|
|
1963
|
+
const pick = (id) => {
|
|
1964
|
+
if (id === 'first') { onChange(1); setOpen(false); return }
|
|
1965
|
+
if (id === 'mid') { onChange(15); setOpen(false); return }
|
|
1966
|
+
if (id === 'last') { onChange('last'); setOpen(false); return }
|
|
1967
|
+
if (id.startsWith('monthly-day-')) {
|
|
1968
|
+
onChange(Number(id.slice('monthly-day-'.length)))
|
|
1969
|
+
setOpen(false)
|
|
1614
1970
|
}
|
|
1615
|
-
update({ weekdays: [...selected].sort((left, right) => left - right) })
|
|
1616
1971
|
}
|
|
1617
|
-
return h(
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1972
|
+
return h(Menu, {
|
|
1973
|
+
open,
|
|
1974
|
+
className: 'capital-automation-monthpick',
|
|
1975
|
+
anchor: h('button', {
|
|
1976
|
+
type: 'button',
|
|
1977
|
+
className: 'capital-automation-select capital-automation-triggerpick',
|
|
1978
|
+
disabled,
|
|
1979
|
+
'aria-label': `${zh.monthlyField}:${summary}`,
|
|
1980
|
+
'aria-haspopup': 'menu',
|
|
1981
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1982
|
+
title: `${zh.monthlyField}:${summary}`,
|
|
1983
|
+
onClick: () => setOpen((current) => !current),
|
|
1984
|
+
},
|
|
1985
|
+
h('span', { className: 'capital-automation-selectvalue' }, summary),
|
|
1986
|
+
h(IconChevronDownOutlineRegular, { size: 14 })),
|
|
1987
|
+
items,
|
|
1988
|
+
selectedId,
|
|
1989
|
+
onSelect: pick,
|
|
1990
|
+
onClose: () => setOpen(false),
|
|
1991
|
+
align: 'start',
|
|
1992
|
+
portal: false,
|
|
1993
|
+
})
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function TriggerFields(props) {
|
|
1997
|
+
const { trigger, onChange, disabled, meta, errorFor, touch } = props
|
|
1998
|
+
const update = (patch) => onChange(normalizeTrigger({ ...trigger, ...patch }))
|
|
1999
|
+
const setCadence = (cadence) => onChange(triggerChanged(cadence, trigger))
|
|
2000
|
+
|
|
2001
|
+
const issue = (field) => (typeof errorFor === 'function' ? errorFor(field) : undefined)
|
|
2002
|
+
const touchField = (field) => { if (typeof touch === 'function') touch(field) }
|
|
2003
|
+
const timeError = issue('time')
|
|
2004
|
+
const timezoneError = issue('timezone')
|
|
2005
|
+
const weekdayError = issue('weekdays')
|
|
2006
|
+
const dateError = issue('date')
|
|
2007
|
+
const errors = [timeError, timezoneError, weekdayError, dateError]
|
|
2008
|
+
.filter((message) => typeof message === 'string' && message.length > 0)
|
|
2009
|
+
const cadenceOptions = Object.entries(zh.cadence).map(([value, label]) => ({ value, label }))
|
|
2010
|
+
|
|
2011
|
+
return h('div', {
|
|
2012
|
+
className: 'capital-automation-triggerbox',
|
|
2013
|
+
'data-cadence': trigger.cadence,
|
|
2014
|
+
'data-invalid': errors.length > 0 ? 'true' : undefined,
|
|
2015
|
+
},
|
|
2016
|
+
h('div', { className: 'capital-automation-triggerhead' },
|
|
2017
|
+
h('div', { className: 'capital-automation-triggerfield' },
|
|
2018
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.cadenceField),
|
|
2019
|
+
h(Select, {
|
|
2020
|
+
value: trigger.cadence,
|
|
2021
|
+
options: cadenceOptions,
|
|
2022
|
+
onChange: setCadence,
|
|
2023
|
+
disabled,
|
|
2024
|
+
label: zh.cadenceField,
|
|
2025
|
+
}),
|
|
2026
|
+
h('div', { className: 'capital-automation-triggerdetail', 'data-cadence': trigger.cadence },
|
|
2027
|
+
trigger.cadence === 'weekly'
|
|
2028
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2029
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.weekdayField),
|
|
2030
|
+
h(WeekdaySelect, {
|
|
2031
|
+
value: trigger.weekdays ?? [],
|
|
2032
|
+
onChange: (weekdays) => update({ weekdays }),
|
|
2033
|
+
disabled,
|
|
2034
|
+
}))
|
|
2035
|
+
: trigger.cadence === 'monthly'
|
|
2036
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2037
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.dayField),
|
|
2038
|
+
h(MonthlySelect, {
|
|
2039
|
+
value: trigger.dayOfMonth ?? 1,
|
|
2040
|
+
onChange: (dayOfMonth) => update({ dayOfMonth }),
|
|
2041
|
+
disabled,
|
|
2042
|
+
}))
|
|
2043
|
+
: trigger.cadence === 'once'
|
|
2044
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2045
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.dayField),
|
|
2046
|
+
h('input', {
|
|
2047
|
+
type: 'date',
|
|
2048
|
+
className: 'capital-automation-triggerdate',
|
|
2049
|
+
value: trigger.date ?? '',
|
|
2050
|
+
disabled,
|
|
2051
|
+
title: zh.onceHint,
|
|
2052
|
+
'aria-label': zh.dayField,
|
|
2053
|
+
'aria-invalid': dateError ? 'true' : undefined,
|
|
2054
|
+
'data-invalid': dateError ? 'true' : undefined,
|
|
2055
|
+
onChange: (event) => update({ date: event.target.value }),
|
|
2056
|
+
onBlur: () => touchField('date'),
|
|
2057
|
+
}))
|
|
2058
|
+
: null)),
|
|
2059
|
+
h('div', { className: 'capital-automation-triggerfield' },
|
|
2060
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.timeField),
|
|
2061
|
+
h('input', {
|
|
2062
|
+
type: 'time',
|
|
2063
|
+
className: 'capital-automation-triggertime',
|
|
2064
|
+
value: trigger.time,
|
|
2065
|
+
disabled,
|
|
2066
|
+
'aria-label': zh.timeField,
|
|
2067
|
+
'aria-invalid': timeError ? 'true' : undefined,
|
|
2068
|
+
'data-invalid': timeError ? 'true' : undefined,
|
|
2069
|
+
onChange: (event) => update({ time: event.target.value }),
|
|
2070
|
+
onBlur: () => touchField('time'),
|
|
2071
|
+
}))),
|
|
2072
|
+
h('div', { className: 'capital-automation-triggersettings' },
|
|
2073
|
+
h('span', { className: 'capital-automation-triggerzone' },
|
|
2074
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.timezoneField),
|
|
2075
|
+
h(Select, {
|
|
2076
|
+
value: trigger.timezone,
|
|
2077
|
+
options: timezoneOptions(trigger.timezone),
|
|
2078
|
+
onChange: (timezone) => update({ timezone }),
|
|
2079
|
+
disabled,
|
|
2080
|
+
label: zh.timezoneField,
|
|
2081
|
+
size: 'sm',
|
|
2082
|
+
align: 'end',
|
|
2083
|
+
})),
|
|
2084
|
+
h('label', { className: 'capital-automation-triggerswitch' },
|
|
2085
|
+
h(Switch, {
|
|
2086
|
+
checked: trigger.tradingDaysOnly === true,
|
|
2087
|
+
onChange: (value) => update({ tradingDaysOnly: value }),
|
|
2088
|
+
label: zh.tradingDaysField,
|
|
2089
|
+
disabled,
|
|
2090
|
+
}),
|
|
2091
|
+
h('span', null, zh.tradingDaysField))),
|
|
2092
|
+
errors.length > 0
|
|
2093
|
+
? h('div', { className: 'capital-automation-triggererrors' },
|
|
2094
|
+
errors.map((message) => h('span', { key: message, className: 'capital-automation-fielderror', role: 'alert' }, message)))
|
|
2095
|
+
: null,
|
|
2096
|
+
meta?.calendar?.available === false ? h('p', { className: 'capital-automation-hint' }, zh.nextRunUnavailable) : null)
|
|
1669
2097
|
}
|
|
1670
2098
|
|
|
1671
2099
|
function SwitchRow(props) {
|
|
@@ -1677,24 +2105,163 @@ function SwitchRow(props) {
|
|
|
1677
2105
|
h(Switch, { checked: checked === true, onChange, label, disabled }))
|
|
1678
2106
|
}
|
|
1679
2107
|
|
|
2108
|
+
function FieldHead(props) {
|
|
2109
|
+
const { label, note, error } = props
|
|
2110
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2111
|
+
return h('span', { className: 'capital-automation-fieldhead' },
|
|
2112
|
+
h('span', { className: 'capital-automation-fieldlabel' }, label),
|
|
2113
|
+
invalid
|
|
2114
|
+
? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, error)
|
|
2115
|
+
: note ? h('span', { className: 'capital-automation-fieldhint' }, note) : null)
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
/**
|
|
2119
|
+
* Fixed-height plain-text box. A native textarea brings a resize handle and browser chrome that
|
|
2120
|
+
* break the two-column height contract, so the editable surface is a contenteditable div; paste is
|
|
2121
|
+
* forced to plain text and maxLength is applied on input.
|
|
2122
|
+
*/
|
|
2123
|
+
function PlainTextEditor(props) {
|
|
2124
|
+
const { value, onChange, onBlur, placeholder, disabled, ariaLabel, invalid, className, maxLength } = props
|
|
2125
|
+
const ref = useRef(null)
|
|
2126
|
+
const limit = typeof maxLength === 'number' ? maxLength : undefined
|
|
2127
|
+
useEffect(() => {
|
|
2128
|
+
const node = ref.current
|
|
2129
|
+
if (node === null || node === undefined) return
|
|
2130
|
+
const next = value ?? ''
|
|
2131
|
+
if (document.activeElement !== node && node.innerText !== next) node.innerText = next
|
|
2132
|
+
}, [value])
|
|
2133
|
+
const read = (node) => (node.innerText ?? '').replace(/\r\n?/g, '\n')
|
|
2134
|
+
const commit = (node) => {
|
|
2135
|
+
let text = read(node)
|
|
2136
|
+
if (limit !== undefined && Array.from(text).length > limit) {
|
|
2137
|
+
text = Array.from(text).slice(0, limit).join('')
|
|
2138
|
+
node.innerText = text
|
|
2139
|
+
const range = document.createRange()
|
|
2140
|
+
range.selectNodeContents(node)
|
|
2141
|
+
range.collapse(false)
|
|
2142
|
+
const selection = window.getSelection()
|
|
2143
|
+
selection?.removeAllRanges()
|
|
2144
|
+
selection?.addRange(range)
|
|
2145
|
+
}
|
|
2146
|
+
if (text.length === 0 && node.innerHTML !== '') node.innerHTML = ''
|
|
2147
|
+
onChange(text)
|
|
2148
|
+
}
|
|
2149
|
+
return h('div', {
|
|
2150
|
+
ref,
|
|
2151
|
+
className,
|
|
2152
|
+
contentEditable: disabled === true ? 'false' : 'true',
|
|
2153
|
+
role: 'textbox',
|
|
2154
|
+
'aria-multiline': 'true',
|
|
2155
|
+
'aria-label': ariaLabel,
|
|
2156
|
+
'aria-invalid': invalid ? 'true' : undefined,
|
|
2157
|
+
'data-placeholder': placeholder,
|
|
2158
|
+
spellCheck: false,
|
|
2159
|
+
suppressContentEditableWarning: true,
|
|
2160
|
+
onInput: (event) => commit(event.currentTarget),
|
|
2161
|
+
onBlur: (event) => { commit(event.currentTarget); if (typeof onBlur === 'function') onBlur() },
|
|
2162
|
+
onPaste: (event) => {
|
|
2163
|
+
const text = event.clipboardData?.getData('text/plain')
|
|
2164
|
+
if (typeof text !== 'string') return
|
|
2165
|
+
event.preventDefault()
|
|
2166
|
+
const selection = window.getSelection()
|
|
2167
|
+
if (selection === null || selection.rangeCount === 0) return
|
|
2168
|
+
selection.deleteFromDocument()
|
|
2169
|
+
const range = selection.getRangeAt(0)
|
|
2170
|
+
const node = document.createTextNode(text)
|
|
2171
|
+
range.insertNode(node)
|
|
2172
|
+
range.setStartAfter(node)
|
|
2173
|
+
range.collapse(true)
|
|
2174
|
+
selection.removeAllRanges()
|
|
2175
|
+
selection.addRange(range)
|
|
2176
|
+
commit(event.currentTarget)
|
|
2177
|
+
},
|
|
2178
|
+
})
|
|
2179
|
+
}
|
|
2180
|
+
|
|
1680
2181
|
function Field(props) {
|
|
1681
|
-
const { label, hint, children } = props
|
|
1682
|
-
|
|
1683
|
-
|
|
2182
|
+
const { label, hint, note, error, children } = props
|
|
2183
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2184
|
+
return h('label', { className: 'capital-automation-field', 'data-invalid': invalid ? 'true' : undefined },
|
|
2185
|
+
h(FieldHead, { label, note, error }),
|
|
1684
2186
|
children,
|
|
1685
2187
|
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
1686
2188
|
}
|
|
1687
2189
|
|
|
2190
|
+
/**
|
|
2191
|
+
* DSH General-settings style selector: a filled pill plus the shared Menu surface.
|
|
2192
|
+
* Native <select> is intentionally avoided so light/dark themes follow host tokens.
|
|
2193
|
+
*/
|
|
2194
|
+
function Select(props) {
|
|
2195
|
+
const { value, options, onChange, disabled, label, block, size, align = 'start', title } = props
|
|
2196
|
+
const [open, setOpen] = useState(false)
|
|
2197
|
+
const triggerRef = useRef(null)
|
|
2198
|
+
const items = options.map((option) => ({
|
|
2199
|
+
id: String(option.value),
|
|
2200
|
+
label: option.label,
|
|
2201
|
+
disabled: option.disabled === true,
|
|
2202
|
+
}))
|
|
2203
|
+
const currentValue = String(value ?? '')
|
|
2204
|
+
const current = items.find((item) => item.id === currentValue) ?? items[0]
|
|
2205
|
+
useEffect(() => {
|
|
2206
|
+
if (disabled === true && open) setOpen(false)
|
|
2207
|
+
}, [disabled, open])
|
|
2208
|
+
const closeMenu = () => setOpen(false)
|
|
2209
|
+
const selectValue = (id) => {
|
|
2210
|
+
setOpen(false)
|
|
2211
|
+
triggerRef.current?.focus?.({ preventScroll: true })
|
|
2212
|
+
if (id !== currentValue) onChange(id)
|
|
2213
|
+
}
|
|
2214
|
+
const trigger = h('button', {
|
|
2215
|
+
ref: triggerRef,
|
|
2216
|
+
type: 'button',
|
|
2217
|
+
className: 'capital-automation-select',
|
|
2218
|
+
'data-block': block === true ? 'true' : undefined,
|
|
2219
|
+
'data-size': size,
|
|
2220
|
+
disabled,
|
|
2221
|
+
'aria-label': current?.label ? `${label}:${current.label}` : label,
|
|
2222
|
+
'aria-haspopup': 'menu',
|
|
2223
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
2224
|
+
title: title ?? current?.label,
|
|
2225
|
+
onClick: () => setOpen((currentOpen) => !currentOpen),
|
|
2226
|
+
},
|
|
2227
|
+
h('span', {
|
|
2228
|
+
className: 'capital-automation-selectvalue',
|
|
2229
|
+
title: title ?? current?.label,
|
|
2230
|
+
}, current?.label ?? ''),
|
|
2231
|
+
h(IconChevronDownOutlineRegular, { size: size === 'sm' ? 12 : 14 }))
|
|
2232
|
+
return h(Menu, {
|
|
2233
|
+
open,
|
|
2234
|
+
anchor: trigger,
|
|
2235
|
+
items,
|
|
2236
|
+
selectedId: currentValue,
|
|
2237
|
+
onSelect: selectValue,
|
|
2238
|
+
onClose: closeMenu,
|
|
2239
|
+
align,
|
|
2240
|
+
portal: true,
|
|
2241
|
+
})
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function SelectField(props) {
|
|
2245
|
+
const { label, hint, note, error, ariaLabel, block = true, title, ...rest } = props
|
|
2246
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2247
|
+
return h('div', { className: 'capital-automation-field', title, 'data-invalid': invalid ? 'true' : undefined },
|
|
2248
|
+
h(FieldHead, { label, note, error }),
|
|
2249
|
+
h(Select, { ...rest, label: ariaLabel ?? label, block, title }),
|
|
2250
|
+
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
2251
|
+
}
|
|
2252
|
+
|
|
1688
2253
|
function NumberField(props) {
|
|
1689
|
-
const { label, value, min, max, disabled, onChange } = props
|
|
1690
|
-
return h(Field, { label },
|
|
2254
|
+
const { label, value, min, max, disabled, onChange, error, onBlur } = props
|
|
2255
|
+
return h(Field, { label, error },
|
|
1691
2256
|
h('input', {
|
|
1692
2257
|
type: 'number',
|
|
1693
2258
|
min,
|
|
1694
2259
|
max,
|
|
1695
2260
|
value,
|
|
1696
2261
|
disabled,
|
|
2262
|
+
'aria-invalid': error ? 'true' : undefined,
|
|
1697
2263
|
onChange: (event) => onChange(Number(event.target.value)),
|
|
2264
|
+
onBlur,
|
|
1698
2265
|
}))
|
|
1699
2266
|
}
|
|
1700
2267
|
|
|
@@ -1755,53 +2322,48 @@ function RunsPage(props) {
|
|
|
1755
2322
|
}
|
|
1756
2323
|
|
|
1757
2324
|
function OverviewTab(props) {
|
|
1758
|
-
const { detail, meta, onGoRuns } = props
|
|
2325
|
+
const { detail, meta, onGoRuns, onCancelRun, onOpenSession } = props
|
|
1759
2326
|
const latest = detail.runs[0]
|
|
1760
|
-
const next =
|
|
2327
|
+
const next = nextRunView(detail.task)
|
|
2328
|
+
const last = lastRunView(latest, detail.task.trigger.timezone)
|
|
1761
2329
|
const anyRunning = detail.runs.some((run) => isActiveRun(run))
|
|
1762
2330
|
const stateText = anyRunning ? zh.status.running : stateLabel(detail.task.state)
|
|
2331
|
+
const originLabel = detail.task.origin === 'builtin' ? zh.origin.builtin : zh.origin.custom
|
|
2332
|
+
const scheduleText = `${scheduleRangeSummary(detail.task.trigger)}${detail.task.trigger.tradingDaysOnly === true ? ` · ${zh.tradingDaysField}` : ''}`
|
|
2333
|
+
const scheduleHint = detail.task.trigger.cadence === 'once'
|
|
2334
|
+
? zh.triggerHintOnce
|
|
2335
|
+
: detail.task.trigger.tradingDaysOnly === true ? zh.triggerHint : zh.triggerHintNatural
|
|
1763
2336
|
return h('div', { className: 'capital-automation-overview' },
|
|
1764
2337
|
h('div', { className: 'capital-automation-statgrid' },
|
|
1765
2338
|
h('div', { className: 'capital-automation-stat' },
|
|
1766
2339
|
h('span', { className: 'capital-automation-statlabel' }, zh.stateLabel),
|
|
1767
2340
|
h('span', { className: 'capital-automation-statvalue', 'data-tone': anyRunning ? 'running' : undefined }, stateText),
|
|
1768
|
-
h('span', { className: 'capital-automation-statsub' },
|
|
2341
|
+
h('span', { className: 'capital-automation-statsub' }, originLabel)),
|
|
1769
2342
|
h('div', { className: 'capital-automation-stat' },
|
|
1770
2343
|
h('span', { className: 'capital-automation-statlabel' }, zh.nextRun),
|
|
1771
|
-
h('span', { className: 'capital-automation-statvalue' }, next),
|
|
1772
|
-
h('span', { className: 'capital-automation-statsub' },
|
|
2344
|
+
h('span', { className: 'capital-automation-statvalue' }, next.value),
|
|
2345
|
+
h('span', { className: 'capital-automation-statsub' }, next.sub)),
|
|
1773
2346
|
h('div', { className: 'capital-automation-stat' },
|
|
1774
2347
|
h('span', { className: 'capital-automation-statlabel' }, zh.lastResult),
|
|
1775
|
-
h('span', { className: 'capital-automation-statvalue', 'data-tone':
|
|
1776
|
-
h('span', { className: 'capital-automation-statsub' },
|
|
1777
|
-
h('div', { className: 'capital-automation-stat' },
|
|
2348
|
+
h('span', { className: 'capital-automation-statvalue', 'data-tone': last.tone }, last.value),
|
|
2349
|
+
h('span', { className: 'capital-automation-statsub' }, last.sub)),
|
|
2350
|
+
h('div', { className: 'capital-automation-stat', title: scheduleHint },
|
|
1778
2351
|
h('span', { className: 'capital-automation-statlabel' }, zh.triggerSummary),
|
|
1779
|
-
h('span', { className: 'capital-automation-statvalue' },
|
|
2352
|
+
h('span', { className: 'capital-automation-statvalue' }, scheduleText),
|
|
1780
2353
|
h('span', { className: 'capital-automation-statsub' }, `${detail.task.trigger.time} · ${detail.task.trigger.timezone}`))),
|
|
1781
2354
|
h('section', { className: 'capital-automation-card capital-automation-panel' },
|
|
1782
2355
|
h('div', { className: 'capital-automation-panelhead' },
|
|
1783
|
-
h('h3', null, zh.
|
|
1784
|
-
h('span', { className: 'capital-automation-hint' }, detail.task.trigger.tradingDaysOnly === true ? zh.triggerHint : zh.triggerHintNatural)),
|
|
1785
|
-
h('div', { className: 'capital-automation-kv' },
|
|
1786
|
-
h('div', { className: 'capital-automation-kvitem' },
|
|
1787
|
-
h('span', { className: 'capital-automation-kvlabel' }, zh.triggerField),
|
|
1788
|
-
h('span', { className: 'capital-automation-kvvalue' }, scheduleSummary(detail.task.trigger))),
|
|
1789
|
-
h('div', { className: 'capital-automation-kvitem' },
|
|
1790
|
-
h('span', { className: 'capital-automation-kvlabel' }, zh.timezoneField),
|
|
1791
|
-
h('span', { className: 'capital-automation-kvvalue' }, detail.task.trigger.timezone)),
|
|
1792
|
-
h('div', { className: 'capital-automation-kvitem' },
|
|
1793
|
-
h('span', { className: 'capital-automation-kvlabel' }, zh.nextRun),
|
|
1794
|
-
h('span', { className: 'capital-automation-kvvalue' }, `${next}${detail.task.nextRunAt ? ` · ${formatRelative(detail.task.nextRunAt)}` : ''}`)),
|
|
1795
|
-
h('div', { className: 'capital-automation-kvitem' },
|
|
1796
|
-
h('span', { className: 'capital-automation-kvlabel' }, zh.stateLabel),
|
|
1797
|
-
h('span', { className: 'capital-automation-kvvalue' }, `${stateText}${detail.task.trigger.tradingDaysOnly ? ` · ${zh.tradingDaysField}` : ''}`)))),
|
|
1798
|
-
h('section', { className: 'capital-automation-card capital-automation-panel' },
|
|
1799
|
-
h('div', { className: 'capital-automation-panelhead' },
|
|
1800
|
-
h('h3', null, zh.detailRecentRuns),
|
|
2356
|
+
h('h3', null, zh.detailRecentRuns.replace('{n}', String(DETAIL_RECENT_RUN_LIMIT))),
|
|
1801
2357
|
h('button', { type: 'button', className: 'capital-automation-btn', 'data-size': 'sm', 'data-variant': 'ghost', onClick: onGoRuns },
|
|
1802
2358
|
zh.detailAllRuns,
|
|
1803
2359
|
h(IconChevronRightOutlineRegular, { size: 13 }))),
|
|
1804
|
-
h(RunList, {
|
|
2360
|
+
h(RunList, {
|
|
2361
|
+
runs: detail.runs.slice(0, DETAIL_RECENT_RUN_LIMIT),
|
|
2362
|
+
tasks: [detail.task],
|
|
2363
|
+
onCancel: onCancelRun,
|
|
2364
|
+
onOpenSession,
|
|
2365
|
+
compact: true,
|
|
2366
|
+
})),
|
|
1805
2367
|
h('section', { className: 'capital-automation-card capital-automation-panel' },
|
|
1806
2368
|
h('div', { className: 'capital-automation-panelhead' },
|
|
1807
2369
|
h('h3', null, zh.environment)),
|
|
@@ -1814,66 +2376,82 @@ function OverviewTab(props) {
|
|
|
1814
2376
|
h('span', { className: 'capital-automation-kvvalue' }, workspaceTitle(meta, detail.task.workspaceId))),
|
|
1815
2377
|
h('div', { className: 'capital-automation-kvitem' },
|
|
1816
2378
|
h('span', { className: 'capital-automation-kvlabel' }, zh.categoryField),
|
|
1817
|
-
h('span', { className: 'capital-automation-kvvalue' }, categoryLabel(detail.task.category))),
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
h('span', { className: 'capital-automation-kvvalue' }, modeLabel(detail.task.mode)))),
|
|
1821
|
-
h('div', { className: 'capital-automation-policy', style: { marginTop: 14 } },
|
|
1822
|
-
h('p', null, zh.policyText),
|
|
1823
|
-
h('p', null, zh.budgetHint)),
|
|
1824
|
-
h('details', { className: 'capital-automation-technical' },
|
|
1825
|
-
h('summary', null, zh.techInfo),
|
|
2379
|
+
h('span', { className: 'capital-automation-kvvalue' }, categoryLabel(detail.task.category)))),
|
|
2380
|
+
h('section', { className: 'capital-automation-technical' },
|
|
2381
|
+
h('h4', { className: 'capital-automation-technicaltitle' }, zh.techInfo),
|
|
1826
2382
|
h('dl', null,
|
|
1827
2383
|
h('dt', null, zh.profileKey),
|
|
1828
|
-
h('dd',
|
|
2384
|
+
h('dd', { title: 'profileKey 是 profile 名称的稳定哈希;与 session 标题里的 run id 无关。' }, meta?.profile?.key ?? '—'),
|
|
1829
2385
|
h('dt', null, zh.presetLabel),
|
|
1830
2386
|
h('dd', null, zh.presetValue),
|
|
1831
2387
|
h('dt', null, zh.policyField),
|
|
1832
2388
|
h('dd', null, meta?.execution?.policy ?? 'automation-v1'),
|
|
1833
2389
|
h('dt', null, zh.cwdKind),
|
|
1834
|
-
h('dd',
|
|
1835
|
-
h('dt', null, 'profile'),
|
|
2390
|
+
h('dd', { title: '当前固定为 profile(专属自动化工作区);没有 storage 等其他来源。' }, meta?.execution?.cwdKind ?? 'profile'),
|
|
2391
|
+
h('dt', null, 'profile 名称'),
|
|
1836
2392
|
h('dd', null, meta?.profile?.name ?? '—'),
|
|
1837
2393
|
h('dt', null, zh.calendarField),
|
|
1838
|
-
h('dd', null, calendarLabel(meta?.calendar))
|
|
1839
|
-
h('dt', null, zh.version),
|
|
1840
|
-
h('dd', null, meta?.version ? `v${meta.version}` : '—')))))
|
|
2394
|
+
h('dd', null, calendarLabel(meta?.calendar))))))
|
|
1841
2395
|
}
|
|
1842
2396
|
|
|
1843
2397
|
function ConfigTab(props) {
|
|
1844
2398
|
const { draft, setDraft, meta, busy, dirty, save, reset, disabled } = props
|
|
1845
2399
|
const task = draft
|
|
2400
|
+
const { issues, errorFor, touch, touchAll, clear } = useDraftIssues(task, task.id)
|
|
2401
|
+
const formRef = useRef(null)
|
|
2402
|
+
const titleError = errorFor('title')
|
|
2403
|
+
const instructionError = errorFor('instruction')
|
|
1846
2404
|
const set = (patch) => setDraft((current) => ({
|
|
1847
2405
|
...current,
|
|
1848
2406
|
...(typeof patch === 'function' ? patch(current) : patch),
|
|
1849
2407
|
}))
|
|
2408
|
+
const submit = () => {
|
|
2409
|
+
if (Object.keys(issues).length > 0) {
|
|
2410
|
+
touchAll()
|
|
2411
|
+
setTimeout(() => {
|
|
2412
|
+
const target = formRef.current?.querySelector('[data-invalid="true"] input, [data-invalid="true"] .capital-automation-texteditor')
|
|
2413
|
+
target?.focus?.()
|
|
2414
|
+
}, 0)
|
|
2415
|
+
return
|
|
2416
|
+
}
|
|
2417
|
+
void save()
|
|
2418
|
+
}
|
|
1850
2419
|
const timeoutRange = limitValue(meta, 'runTimeoutMinutes', [5, 120])
|
|
1851
2420
|
const idleRange = limitValue(meta, 'idleTimeoutMinutes', [1, 30])
|
|
1852
2421
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
1853
2422
|
const toolsRange = limitValue(meta, 'maxToolCalls', [10, 6000])
|
|
2423
|
+
const instructionEditor = h(Field, { label: zh.instructionField, error: instructionError },
|
|
2424
|
+
h(PlainTextEditor, {
|
|
2425
|
+
className: 'capital-automation-texteditor',
|
|
2426
|
+
value: task.instruction,
|
|
2427
|
+
disabled: busy || disabled,
|
|
2428
|
+
placeholder: zh.instructionPlaceholder,
|
|
2429
|
+
ariaLabel: zh.instructionField,
|
|
2430
|
+
invalid: instructionError ? true : undefined,
|
|
2431
|
+
onChange: (instruction) => set({ instruction }),
|
|
2432
|
+
onBlur: () => touch('instruction'),
|
|
2433
|
+
}))
|
|
2434
|
+
const instructionSide = h('div', { className: 'capital-automation-instructionside' },
|
|
2435
|
+
h(SelectField, {
|
|
2436
|
+
label: zh.workspaceField,
|
|
2437
|
+
value: task.workspaceId ?? '',
|
|
2438
|
+
options: workspaceOptions(meta, task.workspaceId).map((option) => ({ value: option.id, label: option.title })),
|
|
2439
|
+
disabled: busy || disabled,
|
|
2440
|
+
onChange: (workspaceId) => set({ workspaceId }),
|
|
2441
|
+
}),
|
|
2442
|
+
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null,
|
|
2443
|
+
h('div', null,
|
|
2444
|
+
h('span', { className: 'capital-automation-formlabel' }, zh.triggerField),
|
|
2445
|
+
h(TriggerFields, { trigger: task.trigger, disabled: busy || disabled, onChange: (trigger) => set({ trigger }), meta, errorFor, touch })))
|
|
1854
2446
|
const advanced = h('details', { className: 'capital-automation-advanced' },
|
|
1855
2447
|
h('summary', null, zh.advanced),
|
|
1856
2448
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
1857
2449
|
h('div', { className: 'capital-automation-formgrid' },
|
|
1858
|
-
h(
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
}, workspaceOptions(meta, task.workspaceId).map((option) => h('option', { key: option.id, value: option.id }, option.title)))),
|
|
1864
|
-
h(Field, { label: zh.modeField, hint: zh.modeHint },
|
|
1865
|
-
h('select', {
|
|
1866
|
-
value: task.mode,
|
|
1867
|
-
disabled: busy || disabled,
|
|
1868
|
-
onChange: (event) => set({ mode: event.target.value }),
|
|
1869
|
-
}, Object.entries(zh.mode).map(([value, label]) => h('option', { key: value, value }, label))))),
|
|
1870
|
-
h('div', { className: 'capital-automation-formgrid' },
|
|
1871
|
-
h(NumberField, { label: zh.runTimeout, value: task.limits.runTimeoutMinutes, min: timeoutRange[0], max: timeoutRange[1], disabled: busy || disabled, onChange: (value) => set((current) => ({ limits: { ...current.limits, runTimeoutMinutes: value } })) }),
|
|
1872
|
-
h(NumberField, { label: zh.idleTimeout, value: task.limits.idleTimeoutMinutes, min: idleRange[0], max: idleRange[1], disabled: busy || disabled, onChange: (value) => set((current) => ({ limits: { ...current.limits, idleTimeoutMinutes: value } })) }),
|
|
1873
|
-
h(NumberField, { label: zh.maxSteps, value: task.limits.maxSteps, min: stepsRange[0], max: stepsRange[1], disabled: busy || disabled, onChange: (value) => set((current) => ({ limits: { ...current.limits, maxSteps: value } })) }),
|
|
1874
|
-
h(NumberField, { label: zh.maxToolCalls, value: task.limits.maxToolCalls, min: toolsRange[0], max: toolsRange[1], disabled: busy || disabled, onChange: (value) => set((current) => ({ limits: { ...current.limits, maxToolCalls: value } })) })),
|
|
1875
|
-
h('p', { className: 'capital-automation-hint' }, zh.budgetHint),
|
|
1876
|
-
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null))
|
|
2450
|
+
h(NumberField, { label: zh.runTimeout, value: task.limits.runTimeoutMinutes, min: timeoutRange[0], max: timeoutRange[1], disabled: busy || disabled, error: errorFor('runTimeoutMinutes'), onBlur: () => touch('runTimeoutMinutes'), onChange: (value) => set((current) => ({ limits: { ...current.limits, runTimeoutMinutes: value } })) }),
|
|
2451
|
+
h(NumberField, { label: zh.idleTimeout, value: task.limits.idleTimeoutMinutes, min: idleRange[0], max: idleRange[1], disabled: busy || disabled, error: errorFor('idleTimeoutMinutes'), onBlur: () => touch('idleTimeoutMinutes'), onChange: (value) => set((current) => ({ limits: { ...current.limits, idleTimeoutMinutes: value } })) }),
|
|
2452
|
+
h(NumberField, { label: zh.maxSteps, value: task.limits.maxSteps, min: stepsRange[0], max: stepsRange[1], disabled: busy || disabled, error: errorFor('maxSteps'), onBlur: () => touch('maxSteps'), onChange: (value) => set((current) => ({ limits: { ...current.limits, maxSteps: value } })) }),
|
|
2453
|
+
h(NumberField, { label: zh.maxToolCalls, value: task.limits.maxToolCalls, min: toolsRange[0], max: toolsRange[1], disabled: busy || disabled, error: errorFor('maxToolCalls'), onBlur: () => touch('maxToolCalls'), onChange: (value) => set((current) => ({ limits: { ...current.limits, maxToolCalls: value } })) })),
|
|
2454
|
+
h('p', { className: 'capital-automation-hint' }, zh.budgetHint)))
|
|
1877
2455
|
const notificationRows = h('div', null,
|
|
1878
2456
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
1879
2457
|
h('div', { className: 'capital-automation-switchlist' },
|
|
@@ -1881,34 +2459,52 @@ function ConfigTab(props) {
|
|
|
1881
2459
|
h(SwitchRow, { label: zh.notifyFailure, checked: task.notifications.onFailure, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onFailure: value } })) }),
|
|
1882
2460
|
h(SwitchRow, { label: zh.notifySuccess, checked: task.notifications.onSuccess, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onSuccess: value } })) })),
|
|
1883
2461
|
h('p', { className: 'capital-automation-hint' }, zh.notifyHint))
|
|
1884
|
-
return h('div', { className: 'capital-automation-form' },
|
|
1885
|
-
h(
|
|
1886
|
-
h(
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2462
|
+
return h('div', { className: 'capital-automation-form', ref: formRef },
|
|
2463
|
+
h('div', { className: 'capital-automation-titlerow' },
|
|
2464
|
+
h(Field, {
|
|
2465
|
+
label: zh.titleField,
|
|
2466
|
+
error: titleError,
|
|
2467
|
+
note: zh.charCount.replace('{n}', String(Array.from(task.title ?? '').length)).replace('{max}', String(TITLE_MAX_LENGTH)),
|
|
2468
|
+
},
|
|
2469
|
+
h('input', {
|
|
2470
|
+
value: task.title,
|
|
2471
|
+
maxLength: TITLE_MAX_LENGTH,
|
|
2472
|
+
disabled: busy || disabled,
|
|
2473
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2474
|
+
onChange: (event) => set({ title: event.target.value }),
|
|
2475
|
+
onBlur: () => touch('title'),
|
|
2476
|
+
})),
|
|
2477
|
+
h(SelectField, {
|
|
2478
|
+
label: zh.categoryField,
|
|
2479
|
+
title: task.origin === 'builtin' ? zh.categoryFixed : undefined,
|
|
1891
2480
|
value: task.category,
|
|
2481
|
+
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
1892
2482
|
disabled: busy || disabled || task.origin === 'builtin',
|
|
1893
|
-
onChange: (
|
|
1894
|
-
}, Object.entries(zh.category).map(([value, label]) => h('option', { key: value, value }, label)))),
|
|
1895
|
-
h(Field, { label: zh.instructionField },
|
|
1896
|
-
h('textarea', {
|
|
1897
|
-
value: task.instruction,
|
|
1898
|
-
disabled: busy || disabled,
|
|
1899
|
-
rows: 14,
|
|
1900
|
-
onChange: (event) => set({ instruction: event.target.value }),
|
|
2483
|
+
onChange: (category) => set({ category }),
|
|
1901
2484
|
})),
|
|
1902
|
-
h(
|
|
1903
|
-
|
|
1904
|
-
|
|
2485
|
+
h(Field, {
|
|
2486
|
+
label: zh.descriptionField,
|
|
2487
|
+
note: zh.charCount.replace('{n}', String(Array.from(task.description ?? '').length)).replace('{max}', String(DESCRIPTION_MAX_LENGTH)),
|
|
2488
|
+
},
|
|
2489
|
+
h(PlainTextEditor, {
|
|
2490
|
+
className: 'capital-automation-texteditor capital-automation-descriptioninput',
|
|
2491
|
+
value: task.description ?? '',
|
|
2492
|
+
maxLength: DESCRIPTION_MAX_LENGTH,
|
|
2493
|
+
disabled: busy || disabled,
|
|
2494
|
+
placeholder: zh.descriptionPlaceholder,
|
|
2495
|
+
ariaLabel: zh.descriptionField,
|
|
2496
|
+
onChange: (description) => set({ description }),
|
|
2497
|
+
})),
|
|
2498
|
+
h('div', { className: 'capital-automation-instructionrow' },
|
|
2499
|
+
instructionEditor,
|
|
2500
|
+
instructionSide),
|
|
1905
2501
|
advanced,
|
|
1906
2502
|
notificationRows,
|
|
1907
2503
|
h('div', { className: 'capital-automation-formactions', 'data-sticky': 'true' },
|
|
1908
2504
|
h('span', { className: 'capital-automation-formstatus' },
|
|
1909
2505
|
disabled ? zh.storageUnavailable : dirty ? zh.dirty : zh.noDirty),
|
|
1910
|
-
h('button', { type: 'button', className: 'capital-automation-btn', disabled: busy || !dirty, onClick: reset }, zh.cancel),
|
|
1911
|
-
h('button', { type: 'button', className: 'capital-automation-btn', 'data-variant': 'primary', disabled: busy || !dirty || disabled, onClick:
|
|
2506
|
+
h('button', { type: 'button', className: 'capital-automation-btn', disabled: busy || !dirty, onClick: () => { clear(); reset() } }, zh.cancel),
|
|
2507
|
+
h('button', { type: 'button', className: 'capital-automation-btn', 'data-variant': 'primary', disabled: busy || !dirty || disabled, onClick: submit }, busy === 'save' ? zh.saving : zh.save)))
|
|
1912
2508
|
}
|
|
1913
2509
|
|
|
1914
2510
|
function NotificationsTab(props) {
|
|
@@ -1957,7 +2553,7 @@ function DetailBar(props) {
|
|
|
1957
2553
|
}
|
|
1958
2554
|
|
|
1959
2555
|
function TaskDetailPage(props) {
|
|
1960
|
-
const { detail, meta, api, onChanged, openSession, tab, onTab, onRequestConfirm, onRequestCancelRun,
|
|
2556
|
+
const { detail, meta, api, onChanged, openSession, tab, onTab, onRequestConfirm, onRequestCancelRun, schedulerActive, storageReady } = props
|
|
1961
2557
|
const [draft, setDraft] = useState(null)
|
|
1962
2558
|
const [busy, setBusy] = useState('')
|
|
1963
2559
|
const [notice, setNotice] = useState('')
|
|
@@ -2011,11 +2607,6 @@ function TaskDetailPage(props) {
|
|
|
2011
2607
|
}
|
|
2012
2608
|
|
|
2013
2609
|
async function save() {
|
|
2014
|
-
const invalid = validateDraft(task)
|
|
2015
|
-
if (invalid.length > 0) {
|
|
2016
|
-
setError(invalid)
|
|
2017
|
-
return
|
|
2018
|
-
}
|
|
2019
2610
|
await act('save', async () => {
|
|
2020
2611
|
await api.patch(detail.task.id, draftPayload(task))
|
|
2021
2612
|
}, zh.saved)
|
|
@@ -2063,7 +2654,7 @@ function TaskDetailPage(props) {
|
|
|
2063
2654
|
disabled: busy || !canRun,
|
|
2064
2655
|
title: schedulerActive === false ? zh.schedulerBlocked : running ? zh.runBlocked : undefined,
|
|
2065
2656
|
onClick: () => { void runNow() },
|
|
2066
|
-
}, zh.runNow)
|
|
2657
|
+
}, h(IconPlayOutlineRegular, { size: 14 }), zh.runNow)
|
|
2067
2658
|
|
|
2068
2659
|
const resetDraft = () => {
|
|
2069
2660
|
setDraft({
|
|
@@ -2084,13 +2675,7 @@ function TaskDetailPage(props) {
|
|
|
2084
2675
|
h('h2', null, detail.task.title),
|
|
2085
2676
|
h(StateBadge, { state: detail.task.state, running }),
|
|
2086
2677
|
unread > 0 ? h('span', { className: 'capital-automation-count', title: zh.unread }, String(unread)) : null),
|
|
2087
|
-
h('p', { className: 'capital-automation-herodesc' }, detail.task.description || zh.subtitle),
|
|
2088
|
-
h('div', { className: 'capital-automation-herometa' },
|
|
2089
|
-
h('span', null, h(IconClockOutlineRegular, { size: 13 }), scheduleSummary(detail.task.trigger)),
|
|
2090
|
-
h('span', null, `${zh.nextRun} ${detail.task.nextRunLocal ?? (detail.task.nextRunUnavailable ? zh.calendarUnavailable : zh.never)}`),
|
|
2091
|
-
h('span', null, `${zh.lastResult} ${detail.runs[0] ? statusLabel(detail.runs[0].status) : zh.never}`),
|
|
2092
|
-
h('span', null, categoryLabel(detail.task.category)),
|
|
2093
|
-
detail.task.origin === 'builtin' ? h('span', { className: 'capital-automation-tag' }, zh.origin.builtin) : h('span', { className: 'capital-automation-tag', 'data-tone': 'custom' }, zh.origin.custom))),
|
|
2678
|
+
h('p', { className: 'capital-automation-herodesc' }, detail.task.description || zh.subtitle)),
|
|
2094
2679
|
h('div', { className: 'capital-automation-heroactions' },
|
|
2095
2680
|
runButton,
|
|
2096
2681
|
running
|
|
@@ -2101,7 +2686,7 @@ function TaskDetailPage(props) {
|
|
|
2101
2686
|
'data-variant': detail.task.state === 'active' ? undefined : 'primary',
|
|
2102
2687
|
disabled: busy || !storageReadyNow,
|
|
2103
2688
|
onClick: () => { void toggleState() },
|
|
2104
|
-
}, activationLabel(detail.task)),
|
|
2689
|
+
}, h(activationIcon(detail.task), { size: 14 }), activationLabel(detail.task)),
|
|
2105
2690
|
h(TaskActionsMenu, {
|
|
2106
2691
|
task: detail.task,
|
|
2107
2692
|
open: menuOpen,
|
|
@@ -2135,9 +2720,14 @@ function TaskDetailPage(props) {
|
|
|
2135
2720
|
error.length > 0 ? h('p', { className: 'capital-automation-error', role: 'alert' }, error) : null)
|
|
2136
2721
|
: null,
|
|
2137
2722
|
running ? h('p', { className: 'capital-automation-hint', style: { paddingTop: 10 } }, zh.configWhileRunning) : null,
|
|
2138
|
-
detail.task.origin === 'builtin' ? h('p', { className: 'capital-automation-hint', style: { paddingTop: 8 } }, zh.configBuiltinHint) : null,
|
|
2139
2723
|
h('div', { className: 'capital-automation-detailbody' },
|
|
2140
|
-
tab === 'overview' ? h(OverviewTab, {
|
|
2724
|
+
tab === 'overview' ? h(OverviewTab, {
|
|
2725
|
+
detail,
|
|
2726
|
+
meta,
|
|
2727
|
+
onGoRuns: () => onTab('runs'),
|
|
2728
|
+
onCancelRun: onRequestCancelRun,
|
|
2729
|
+
onOpenSession: openSession,
|
|
2730
|
+
}) : null,
|
|
2141
2731
|
tab === 'config' ? h(ConfigTab, {
|
|
2142
2732
|
draft,
|
|
2143
2733
|
setDraft,
|
|
@@ -2171,6 +2761,9 @@ function NewTaskModal(props) {
|
|
|
2171
2761
|
const [draft, setDraft] = useState(defaultDraft)
|
|
2172
2762
|
const [busy, setBusy] = useState(false)
|
|
2173
2763
|
const [error, setError] = useState('')
|
|
2764
|
+
const { issues, errorFor, touch, touchAll } = useDraftIssues(draft, open)
|
|
2765
|
+
const titleError = errorFor('title')
|
|
2766
|
+
const instructionError = errorFor('instruction')
|
|
2174
2767
|
useEffect(() => {
|
|
2175
2768
|
if (!open) return
|
|
2176
2769
|
setDraft(defaultDraft())
|
|
@@ -2186,9 +2779,8 @@ function NewTaskModal(props) {
|
|
|
2186
2779
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
2187
2780
|
const toolsRange = limitValue(meta, 'maxToolCalls', [10, 6000])
|
|
2188
2781
|
async function create() {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
setError(invalid)
|
|
2782
|
+
if (Object.keys(issues).length > 0) {
|
|
2783
|
+
touchAll()
|
|
2192
2784
|
return
|
|
2193
2785
|
}
|
|
2194
2786
|
setBusy(true)
|
|
@@ -2215,67 +2807,81 @@ function NewTaskModal(props) {
|
|
|
2215
2807
|
h('button', { type: 'button', className: 'capital-automation-btn', 'data-variant': 'primary', disabled: busy || meta?.storage?.state === 'unavailable', onClick: create }, busy ? zh.creating : zh.create)),
|
|
2216
2808
|
},
|
|
2217
2809
|
h('div', { className: 'capital-automation-wizard' },
|
|
2218
|
-
h('div', { className: 'capital-automation-modalheadline' },
|
|
2810
|
+
h('div', { className: 'capital-automation-modalheadline', 'data-invalid': titleError ? 'true' : undefined },
|
|
2219
2811
|
h(Glyph, { task: draft, size: 42 }),
|
|
2220
2812
|
h('input', {
|
|
2221
2813
|
className: 'capital-automation-modaltitleinput',
|
|
2222
2814
|
value: draft.title,
|
|
2815
|
+
maxLength: TITLE_MAX_LENGTH,
|
|
2223
2816
|
placeholder: zh.titlePlaceholder,
|
|
2224
2817
|
'aria-label': zh.titleField,
|
|
2818
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2225
2819
|
onChange: (event) => set({ title: event.target.value }),
|
|
2820
|
+
onBlur: () => touch('title'),
|
|
2226
2821
|
})),
|
|
2822
|
+
titleError ? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, titleError) : null,
|
|
2227
2823
|
h('p', { className: 'capital-automation-hint' }, zh.newTaskHint),
|
|
2228
|
-
h(Field, {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2824
|
+
h(Field, {
|
|
2825
|
+
label: zh.descriptionField,
|
|
2826
|
+
note: zh.charCount.replace('{n}', String(Array.from(draft.description ?? '').length)).replace('{max}', String(DESCRIPTION_MAX_LENGTH)),
|
|
2827
|
+
},
|
|
2828
|
+
h('input', {
|
|
2829
|
+
value: draft.description,
|
|
2830
|
+
maxLength: DESCRIPTION_MAX_LENGTH,
|
|
2831
|
+
placeholder: zh.descriptionPlaceholder,
|
|
2832
|
+
onChange: (event) => set({ description: event.target.value }),
|
|
2833
|
+
})),
|
|
2234
2834
|
h('div', null,
|
|
2235
2835
|
h('span', { className: 'capital-automation-formlabel' }, zh.triggerField),
|
|
2236
|
-
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta })),
|
|
2836
|
+
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta, errorFor, touch })),
|
|
2237
2837
|
h('div', null,
|
|
2238
2838
|
h('span', { className: 'capital-automation-formlabel' }, zh.instructionField),
|
|
2239
|
-
h('div', { className: 'capital-automation-
|
|
2240
|
-
h('
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
h('
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2839
|
+
h('div', { className: 'capital-automation-promptfield', 'data-invalid': instructionError ? 'true' : undefined },
|
|
2840
|
+
h('div', { className: 'capital-automation-promptbox' },
|
|
2841
|
+
h(PlainTextEditor, {
|
|
2842
|
+
className: 'capital-automation-texteditor',
|
|
2843
|
+
value: draft.instruction,
|
|
2844
|
+
placeholder: zh.instructionPlaceholder,
|
|
2845
|
+
ariaLabel: zh.instructionField,
|
|
2846
|
+
invalid: instructionError ? true : undefined,
|
|
2847
|
+
onChange: (instruction) => set({ instruction }),
|
|
2848
|
+
onBlur: () => touch('instruction'),
|
|
2849
|
+
}),
|
|
2850
|
+
h('div', { className: 'capital-automation-prompttoolbar' },
|
|
2851
|
+
h('div', { className: 'capital-automation-toolbarfield' },
|
|
2852
|
+
h(IconFolderOpenOutlineRegular, { size: 13 }),
|
|
2853
|
+
h('span', null, zh.workspaceField),
|
|
2854
|
+
h(Select, {
|
|
2855
|
+
value: draft.workspaceId ?? '',
|
|
2856
|
+
options: workspaceOptions(meta, draft.workspaceId).map((option) => ({ value: option.id, label: option.title })),
|
|
2857
|
+
onChange: (workspaceId) => set({ workspaceId }),
|
|
2858
|
+
label: zh.workspaceField,
|
|
2859
|
+
size: 'sm',
|
|
2860
|
+
align: 'end',
|
|
2861
|
+
})),
|
|
2862
|
+
h('div', { className: 'capital-automation-toolbarfield' },
|
|
2863
|
+
h('span', null, zh.categoryField),
|
|
2864
|
+
h(Select, {
|
|
2865
|
+
value: draft.category,
|
|
2866
|
+
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
2867
|
+
onChange: (category) => set({ category }),
|
|
2868
|
+
label: zh.categoryField,
|
|
2869
|
+
size: 'sm',
|
|
2870
|
+
align: 'end',
|
|
2871
|
+
})),
|
|
2872
|
+
h('span', { className: 'capital-automation-presetchip', title: zh.presetHint },
|
|
2873
|
+
h(IconSparkleRegular, { size: 12 }),
|
|
2874
|
+
zh.presetValue))),
|
|
2875
|
+
instructionError ? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, instructionError) : null)),
|
|
2270
2876
|
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null,
|
|
2271
2877
|
h('details', { className: 'capital-automation-advanced' },
|
|
2272
2878
|
h('summary', null, zh.advanced),
|
|
2273
2879
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
2274
2880
|
h('div', { className: 'capital-automation-formgrid' },
|
|
2275
|
-
h(NumberField, { label: zh.runTimeout, value: draft.limits.runTimeoutMinutes, min: timeoutRange[0], max: timeoutRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, runTimeoutMinutes: value } })) }),
|
|
2276
|
-
h(NumberField, { label: zh.idleTimeout, value: draft.limits.idleTimeoutMinutes, min: idleRange[0], max: idleRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, idleTimeoutMinutes: value } })) }),
|
|
2277
|
-
h(NumberField, { label: zh.maxSteps, value: draft.limits.maxSteps, min: stepsRange[0], max: stepsRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, maxSteps: value } })) }),
|
|
2278
|
-
h(NumberField, { label: zh.maxToolCalls, value: draft.limits.maxToolCalls, min: toolsRange[0], max: toolsRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, maxToolCalls: value } })) })),
|
|
2881
|
+
h(NumberField, { label: zh.runTimeout, value: draft.limits.runTimeoutMinutes, min: timeoutRange[0], max: timeoutRange[1], error: errorFor('runTimeoutMinutes'), onBlur: () => touch('runTimeoutMinutes'), onChange: (value) => set((current) => ({ limits: { ...current.limits, runTimeoutMinutes: value } })) }),
|
|
2882
|
+
h(NumberField, { label: zh.idleTimeout, value: draft.limits.idleTimeoutMinutes, min: idleRange[0], max: idleRange[1], error: errorFor('idleTimeoutMinutes'), onBlur: () => touch('idleTimeoutMinutes'), onChange: (value) => set((current) => ({ limits: { ...current.limits, idleTimeoutMinutes: value } })) }),
|
|
2883
|
+
h(NumberField, { label: zh.maxSteps, value: draft.limits.maxSteps, min: stepsRange[0], max: stepsRange[1], error: errorFor('maxSteps'), onBlur: () => touch('maxSteps'), onChange: (value) => set((current) => ({ limits: { ...current.limits, maxSteps: value } })) }),
|
|
2884
|
+
h(NumberField, { label: zh.maxToolCalls, value: draft.limits.maxToolCalls, min: toolsRange[0], max: toolsRange[1], error: errorFor('maxToolCalls'), onBlur: () => touch('maxToolCalls'), onChange: (value) => set((current) => ({ limits: { ...current.limits, maxToolCalls: value } })) })),
|
|
2279
2885
|
h('p', { className: 'capital-automation-hint' }, zh.advancedHint))),
|
|
2280
2886
|
h('div', null,
|
|
2281
2887
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
@@ -2604,8 +3210,6 @@ function AutomationPage(props) {
|
|
|
2604
3210
|
const initialLoading = meta === null && loadError.length === 0
|
|
2605
3211
|
|
|
2606
3212
|
return h('div', { className: 'capital-automation-page' },
|
|
2607
|
-
h('datalist', { id: 'capital-automation-timezones' },
|
|
2608
|
-
TIMEZONES.map((zone) => h('option', { key: zone, value: zone }))),
|
|
2609
3213
|
detailOpen ? null : h(Header, {
|
|
2610
3214
|
tab,
|
|
2611
3215
|
onTab: switchTab,
|
|
@@ -2631,7 +3235,6 @@ function AutomationPage(props) {
|
|
|
2631
3235
|
onTab: setDetailTab,
|
|
2632
3236
|
onRequestConfirm: requestConfirm,
|
|
2633
3237
|
onRequestCancelRun: (run) => requestConfirm('cancel-run', selectedTask ?? { id: run.taskId, title: run.taskTitle }, run),
|
|
2634
|
-
onOpenAllRuns: () => switchTab('runs'),
|
|
2635
3238
|
schedulerActive,
|
|
2636
3239
|
storageReady,
|
|
2637
3240
|
onChanged: async (taskId) => {
|