@v587d/capital-generation 2.13.2 → 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 +33 -0
- package/README.md +2 -2
- package/automation/builtins.js +9 -69
- package/automation/client.js +72 -26
- package/automation/client.src.cjs +756 -277
- 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
|
@@ -52,9 +52,28 @@ const WEEKDAYS = [
|
|
|
52
52
|
{ value: 6, label: '六' },
|
|
53
53
|
{ value: 7, label: '日' },
|
|
54
54
|
]
|
|
55
|
-
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
|
+
]
|
|
56
73
|
const HISTORY_DAYS = 30
|
|
57
74
|
const DETAIL_RECENT_RUN_LIMIT = 3
|
|
75
|
+
const TITLE_MAX_LENGTH = 12
|
|
76
|
+
const DESCRIPTION_MAX_LENGTH = 100
|
|
58
77
|
|
|
59
78
|
const zh = {
|
|
60
79
|
panel: 'CapGen 投研自动化',
|
|
@@ -106,11 +125,8 @@ const zh = {
|
|
|
106
125
|
filterLabel: '筛选任务',
|
|
107
126
|
category: { news: '资讯', market: '行情', review: '复盘', research: '研究' },
|
|
108
127
|
categoryField: '任务分类',
|
|
109
|
-
categoryFixed: '
|
|
110
|
-
|
|
111
|
-
modeHint: '只影响输出结构;所有运行固定使用自动模式。',
|
|
112
|
-
mode: { news: '资讯', market: '行情', review: '复盘', research: '研究' },
|
|
113
|
-
cadence: { daily: '每天', weekly: '每周', monthly: '每月' },
|
|
128
|
+
categoryFixed: `'复制为我的任务'后,可调整任务分类。`,
|
|
129
|
+
cadence: { daily: '每天', weekly: '每周', monthly: '每月', once: '单次' },
|
|
114
130
|
stateLabel: '状态',
|
|
115
131
|
state: { dormant: '未添加', active: '已启用', paused: '已暂停' },
|
|
116
132
|
origin: { builtin: '内置模板', custom: '我的任务' },
|
|
@@ -137,7 +153,9 @@ const zh = {
|
|
|
137
153
|
cancel: '取消',
|
|
138
154
|
create: '创建任务',
|
|
139
155
|
creating: '创建中…',
|
|
140
|
-
|
|
156
|
+
titleRequired: '标题不能为空。',
|
|
157
|
+
instructionRequired: '任务指令不能为空。',
|
|
158
|
+
invalidLimit: '请输入有效数字。',
|
|
141
159
|
dirty: '有未保存的修改',
|
|
142
160
|
noDirty: '配置已同步',
|
|
143
161
|
nextRun: '下次运行',
|
|
@@ -153,25 +171,47 @@ const zh = {
|
|
|
153
171
|
config: '配置',
|
|
154
172
|
runs: '运行记录',
|
|
155
173
|
notifications: '通知',
|
|
156
|
-
descriptionField: '
|
|
157
|
-
titleField: '
|
|
174
|
+
descriptionField: '任务说明',
|
|
175
|
+
titleField: '任务标题',
|
|
158
176
|
instructionField: '任务指令',
|
|
159
177
|
instructionPlaceholder: '例如:读取今日自选股行情、公告与相关新闻,按「结论—证据—风险」整理成一份可复核的盘后复盘。',
|
|
160
178
|
titlePlaceholder: '给这个任务起个名字',
|
|
179
|
+
charCount: '{n}/{max}',
|
|
161
180
|
descriptionPlaceholder: '一句话说明任务目标(可选)',
|
|
162
181
|
triggerField: '触发器',
|
|
163
|
-
|
|
164
|
-
|
|
182
|
+
cadenceField: '频率',
|
|
183
|
+
triggerHint: '按设定时区的本地墙钟时间计算;仅交易日任务由交易日历判断周末与节假日。',
|
|
184
|
+
triggerHintNatural: '按设定时区的本地墙钟时间计算:每天都触发,周末与节假日也运行。',
|
|
185
|
+
triggerHintOnce: '单次任务只在指定日期运行一次;该日期不是交易日时,仅交易日设置会跳过本次运行。',
|
|
165
186
|
timeField: '时间',
|
|
166
187
|
timezoneField: '时区',
|
|
167
188
|
weekdayField: '星期',
|
|
168
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: '完成',
|
|
169
208
|
tradingDaysField: '仅交易日',
|
|
170
209
|
weekdayRequired: '每周至少选择一天。',
|
|
210
|
+
dateRequired: '请选择运行日期。',
|
|
211
|
+
invalidDate: '请选择有效的日期。',
|
|
171
212
|
invalidTime: '运行时间格式不正确,请选择 HH:mm。',
|
|
172
213
|
invalidTimezone: '时区不能为空,请填写 IANA 时区。',
|
|
173
214
|
invalidTimezoneValue: '时区不被识别,请填写有效的 IANA 时区。',
|
|
174
|
-
invalidLimits: '运行上限必须是有效数字。',
|
|
175
215
|
filterNoRuns: '没有符合筛选条件的运行记录。',
|
|
176
216
|
schedulePrefix: '下次',
|
|
177
217
|
workspaceField: '工作区',
|
|
@@ -192,6 +232,7 @@ const zh = {
|
|
|
192
232
|
notifyHint: '通知只记录在当前 profile;不会发送邮件或外部消息。',
|
|
193
233
|
presetLabel: '运行模式',
|
|
194
234
|
presetValue: '自动模式',
|
|
235
|
+
presetHint: '所有运行固定使用自动模式。',
|
|
195
236
|
budgetHint: '工具调用与步骤预算覆盖整棵运行子树:子 Agent 与 PTC 派发都计入。',
|
|
196
237
|
environment: '执行环境',
|
|
197
238
|
policyField: '无人值守策略',
|
|
@@ -262,7 +303,6 @@ const zh = {
|
|
|
262
303
|
schedulerBlocked: '调度器未取得运行权,暂时不能手动运行。',
|
|
263
304
|
runBlocked: '任务正在运行,结束或取消后才能再次运行。',
|
|
264
305
|
configWhileRunning: '任务正在运行;修改会在下一次运行时生效。',
|
|
265
|
-
configBuiltinHint: '这是内置模板;修改只覆盖当前 profile,休眠后会恢复默认。',
|
|
266
306
|
detailRecentRuns: '最近 {n} 次运行',
|
|
267
307
|
detailNoRuns: '还没有运行记录。',
|
|
268
308
|
detailAllRuns: '完整记录',
|
|
@@ -281,7 +321,6 @@ const zh = {
|
|
|
281
321
|
lastRun: '上次运行',
|
|
282
322
|
neverRun: '尚未运行',
|
|
283
323
|
relativeNow: '刚刚',
|
|
284
|
-
timezoneHint: 'IANA 时区,例如 Asia/Shanghai。',
|
|
285
324
|
nextRunUnavailable: '交易日历不可用,无法计算下一次运行。',
|
|
286
325
|
copyHint: '复制后会生成一个暂停状态的自定义任务。',
|
|
287
326
|
notificationTaskGone: '任务已不存在',
|
|
@@ -293,7 +332,9 @@ const zh = {
|
|
|
293
332
|
everyPrefix: '每',
|
|
294
333
|
weeklyPrefix: '每周',
|
|
295
334
|
monthlyPrefix: '每月',
|
|
335
|
+
monthlyLastDay: '最后一天',
|
|
296
336
|
dailyPrefix: '每天',
|
|
337
|
+
oncePrefix: '单次',
|
|
297
338
|
tradingSuffix: '(仅交易日)',
|
|
298
339
|
sessionMissing: '没有可打开的 session',
|
|
299
340
|
}
|
|
@@ -510,17 +551,31 @@ function installStyles() {
|
|
|
510
551
|
.capital-automation-error { margin: 0; color: var(--ca-error-text); font-size: 12px; line-height: 18px; }
|
|
511
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; }
|
|
512
553
|
.capital-automation-form { display: flex; flex-direction: column; gap: 16px; }
|
|
513
|
-
.capital-automation-
|
|
514
|
-
.capital-automation-
|
|
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; }
|
|
515
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; }
|
|
516
558
|
.capital-automation-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
|
517
|
-
.capital-automation-
|
|
518
|
-
.capital-automation-
|
|
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; }
|
|
519
563
|
.capital-automation-field input { height: 34px; }
|
|
520
|
-
.capital-automation-
|
|
521
|
-
.capital-automation-field
|
|
522
|
-
.capital-automation-field
|
|
523
|
-
.capital-automation-
|
|
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; }
|
|
524
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; }
|
|
525
580
|
.capital-automation-select:hover:not(:disabled) { background: var(--ca-hover); }
|
|
526
581
|
.capital-automation-select[data-block="true"] { width: 100%; }
|
|
@@ -532,20 +587,37 @@ function installStyles() {
|
|
|
532
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); }
|
|
533
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); }
|
|
534
589
|
.capital-automation-formstatus { margin-right: auto; color: var(--ca-caption); font-size: 11.5px; line-height: 18px; }
|
|
535
|
-
.capital-automation-triggerbox { display: flex; flex-direction: column; gap: 12px; padding:
|
|
536
|
-
.capital-automation-
|
|
537
|
-
.capital-automation-
|
|
538
|
-
.capital-automation-
|
|
539
|
-
.capital-automation-
|
|
540
|
-
.capital-automation-
|
|
541
|
-
.capital-automation-
|
|
542
|
-
.capital-automation-
|
|
543
|
-
.capital-automation-
|
|
544
|
-
.capital-automation-
|
|
545
|
-
.capital-automation-
|
|
546
|
-
.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; }
|
|
547
620
|
.capital-automation-switch { display: inline-flex; align-items: center; gap: 7px; color: var(--ca-muted); font-size: 12px; }
|
|
548
|
-
.capital-automation-triggersummary { margin: 0; color: var(--ca-caption); font-size: 11.5px; line-height: 18px; }
|
|
549
621
|
.capital-automation-advanced { border-top: .5px solid var(--ca-line); padding-top: 12px; }
|
|
550
622
|
.capital-automation-advanced summary { color: var(--ca-muted); font-size: 12px; line-height: 18px; cursor: pointer; user-select: none; }
|
|
551
623
|
.capital-automation-advanced summary:hover { color: var(--dsw-alias-label-primary); }
|
|
@@ -604,10 +676,13 @@ function installStyles() {
|
|
|
604
676
|
.capital-automation-modalheadline { display: flex; align-items: center; gap: 10px; }
|
|
605
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; }
|
|
606
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)); }
|
|
607
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)); }
|
|
608
683
|
.capital-automation-promptbox { overflow: hidden; border: .5px solid var(--ca-line); border-radius: 16px; background: var(--dsw-alias-bg-module-platform); }
|
|
609
|
-
.capital-automation-promptbox
|
|
610
|
-
.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; }
|
|
611
686
|
.capital-automation-prompttoolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; border-top: .5px solid var(--ca-line); }
|
|
612
687
|
.capital-automation-prompttoolbar label, .capital-automation-toolbarfield { display: inline-flex; align-items: center; gap: 5px; color: var(--ca-muted); font-size: 11.5px; }
|
|
613
688
|
.capital-automation-prompttoolbar .capital-automation-select { max-width: 168px; background: var(--ca-card); }
|
|
@@ -626,6 +701,9 @@ function installStyles() {
|
|
|
626
701
|
.capital-automation-theadmain > :nth-child(3), .capital-automation-cell[data-cell="next"] { display: none; }
|
|
627
702
|
.capital-automation-statgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
628
703
|
}
|
|
704
|
+
@media (max-width: 960px) {
|
|
705
|
+
.capital-automation-instructionrow { grid-template-columns: minmax(0, 1fr); }
|
|
706
|
+
}
|
|
629
707
|
@media (max-width: 760px) {
|
|
630
708
|
.capital-automation-head { padding-top: 14px; }
|
|
631
709
|
.capital-automation-topbar { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
@@ -643,8 +721,16 @@ function installStyles() {
|
|
|
643
721
|
.capital-automation-templates { grid-template-columns: 1fr; }
|
|
644
722
|
.capital-automation-sectiontools { width: 100%; margin-left: 0; }
|
|
645
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
|
+
}
|
|
646
731
|
@media (max-width: 520px) {
|
|
647
732
|
.capital-automation-brand h1 { font-size: 16px; }
|
|
733
|
+
.capital-automation-titlerow { grid-template-columns: minmax(0, 1fr); }
|
|
648
734
|
.capital-automation-topactions .capital-automation-btn:not([data-variant="contrast"]) { width: auto; min-width: 32px; padding: 0 8px; }
|
|
649
735
|
.capital-automation-topactions .capital-automation-btn[data-variant="contrast"] { width: 32px; padding: 0; }
|
|
650
736
|
.capital-automation-topactions .capital-automation-btn .capital-automation-btnlabel { display: none; }
|
|
@@ -734,10 +820,6 @@ function clipTagLabel(label, limit = 12) {
|
|
|
734
820
|
return chars.length > limit ? `${chars.slice(0, limit).join('')}…` : text
|
|
735
821
|
}
|
|
736
822
|
|
|
737
|
-
function modeLabel(mode) {
|
|
738
|
-
return zh.mode[mode] ?? mode
|
|
739
|
-
}
|
|
740
|
-
|
|
741
823
|
function calendarLabel(calendar) {
|
|
742
824
|
if (calendar === null || calendar === undefined || calendar.available === false) return zh.calendarSourceNone
|
|
743
825
|
const source = calendar.source === 'szse'
|
|
@@ -839,26 +921,31 @@ function formatRangeLabel(date) {
|
|
|
839
921
|
return new Intl.DateTimeFormat('zh-CN', { month: 'short', day: 'numeric' }).format(date)
|
|
840
922
|
}
|
|
841
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
|
+
|
|
842
935
|
function scheduleSummary(trigger) {
|
|
843
936
|
if (trigger === null || trigger === undefined) return zh.never
|
|
844
937
|
const suffix = trigger.tradingDaysOnly === true ? zh.tradingSuffix : ''
|
|
845
|
-
if (trigger.cadence === 'weekly') {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
if (trigger.cadence === 'monthly') {
|
|
850
|
-
return `${zh.monthlyPrefix} ${trigger.dayOfMonth ?? 1} 日 ${trigger.time}${suffix}`
|
|
851
|
-
}
|
|
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}`
|
|
852
941
|
return `${zh.dailyPrefix} ${trigger.time}${suffix}`
|
|
853
942
|
}
|
|
854
943
|
|
|
855
944
|
function scheduleRangeSummary(trigger) {
|
|
856
|
-
if (trigger?.cadence === 'weekly') {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
if (trigger?.cadence === 'monthly') return `每月 ${trigger.dayOfMonth ?? 1} 日`
|
|
861
|
-
return '每天'
|
|
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
|
|
862
949
|
}
|
|
863
950
|
|
|
864
951
|
// Dormant/paused tasks still carry a computed nextRunLocal, but that instant will not fire until
|
|
@@ -866,6 +953,11 @@ function scheduleRangeSummary(trigger) {
|
|
|
866
953
|
function nextRunView(task) {
|
|
867
954
|
if (task?.state === 'dormant') return { value: zh.nextRunDormant, sub: zh.notScheduled }
|
|
868
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 }
|
|
960
|
+
}
|
|
869
961
|
const value = task?.nextRunLocal ?? (task?.nextRunUnavailable ? zh.calendarUnavailable : zh.never)
|
|
870
962
|
const sub = task?.nextRunAt ? formatRelative(task.nextRunAt) : (task?.nextRunUnavailable ? zh.nextRunUnavailable : zh.never)
|
|
871
963
|
return { value, sub }
|
|
@@ -904,16 +996,120 @@ function workspaceTitle(meta, id) {
|
|
|
904
996
|
return workspaceOptions(meta, id).find((item) => item.id === id)?.title ?? zh.workspaceMissing
|
|
905
997
|
}
|
|
906
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
|
+
|
|
907
1096
|
function normalizeTrigger(trigger) {
|
|
1097
|
+
const cadence = Object.prototype.hasOwnProperty.call(zh.cadence, trigger?.cadence) ? trigger.cadence : 'daily'
|
|
908
1098
|
const next = {
|
|
909
1099
|
kind: 'schedule',
|
|
910
|
-
cadence
|
|
1100
|
+
cadence,
|
|
911
1101
|
time: trigger?.time ?? '09:00',
|
|
912
1102
|
timezone: trigger?.timezone ?? 'Asia/Shanghai',
|
|
913
1103
|
tradingDaysOnly: trigger?.tradingDaysOnly === true,
|
|
914
1104
|
}
|
|
915
1105
|
if (next.cadence === 'weekly') next.weekdays = (trigger?.weekdays ?? [1, 3, 5]).slice().sort((left, right) => left - right)
|
|
916
|
-
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
|
+
}
|
|
917
1113
|
return next
|
|
918
1114
|
}
|
|
919
1115
|
|
|
@@ -930,7 +1126,6 @@ function defaultDraft() {
|
|
|
930
1126
|
instruction: '',
|
|
931
1127
|
category: 'research',
|
|
932
1128
|
trigger: { kind: 'schedule', cadence: 'daily', time: '09:00', timezone: 'Asia/Shanghai', tradingDaysOnly: true },
|
|
933
|
-
mode: 'research',
|
|
934
1129
|
workspaceId: '',
|
|
935
1130
|
limits: { ...DEFAULT_LIMITS },
|
|
936
1131
|
notifications: { inApp: true, onFailure: true, onSuccess: false },
|
|
@@ -944,7 +1139,6 @@ function draftPayload(task) {
|
|
|
944
1139
|
instruction: task.instruction ?? '',
|
|
945
1140
|
category: task.category,
|
|
946
1141
|
trigger: normalizeTrigger(task.trigger),
|
|
947
|
-
mode: task.mode,
|
|
948
1142
|
workspaceId: task.workspaceId ?? '',
|
|
949
1143
|
limits: task.limits,
|
|
950
1144
|
notifications: task.notifications,
|
|
@@ -1014,19 +1208,50 @@ function draftChanged(draft, task) {
|
|
|
1014
1208
|
return !samePayload(draft, task)
|
|
1015
1209
|
}
|
|
1016
1210
|
|
|
1017
|
-
function
|
|
1018
|
-
|
|
1019
|
-
if (
|
|
1020
|
-
if (String(task.
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
|
1025
1230
|
}
|
|
1026
|
-
if (task.trigger?.cadence === 'weekly' && (task.trigger.weekdays ?? []).length === 0) return zh.weekdayRequired
|
|
1027
1231
|
const limits = task.limits ?? {}
|
|
1028
|
-
|
|
1029
|
-
|
|
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 }
|
|
1030
1255
|
}
|
|
1031
1256
|
|
|
1032
1257
|
function limitValue(meta, key, fallback) {
|
|
@@ -1642,85 +1867,233 @@ function TaskDashboard(props) {
|
|
|
1642
1867
|
: null)
|
|
1643
1868
|
}
|
|
1644
1869
|
|
|
1645
|
-
function
|
|
1646
|
-
const {
|
|
1647
|
-
const
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
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)
|
|
1651
1910
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
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)
|
|
1659
1970
|
}
|
|
1660
|
-
update({ weekdays: [...selected].sort((left, right) => left - right) })
|
|
1661
1971
|
}
|
|
1662
|
-
return h(
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
:
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
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)
|
|
1724
2097
|
}
|
|
1725
2098
|
|
|
1726
2099
|
function SwitchRow(props) {
|
|
@@ -1732,10 +2105,84 @@ function SwitchRow(props) {
|
|
|
1732
2105
|
h(Switch, { checked: checked === true, onChange, label, disabled }))
|
|
1733
2106
|
}
|
|
1734
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
|
+
|
|
1735
2181
|
function Field(props) {
|
|
1736
|
-
const { label, hint, children } = props
|
|
1737
|
-
|
|
1738
|
-
|
|
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 }),
|
|
1739
2186
|
children,
|
|
1740
2187
|
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
1741
2188
|
}
|
|
@@ -1745,7 +2192,7 @@ function Field(props) {
|
|
|
1745
2192
|
* Native <select> is intentionally avoided so light/dark themes follow host tokens.
|
|
1746
2193
|
*/
|
|
1747
2194
|
function Select(props) {
|
|
1748
|
-
const { value, options, onChange, disabled, label, block, size, align = 'start' } = props
|
|
2195
|
+
const { value, options, onChange, disabled, label, block, size, align = 'start', title } = props
|
|
1749
2196
|
const [open, setOpen] = useState(false)
|
|
1750
2197
|
const triggerRef = useRef(null)
|
|
1751
2198
|
const items = options.map((option) => ({
|
|
@@ -1774,11 +2221,12 @@ function Select(props) {
|
|
|
1774
2221
|
'aria-label': current?.label ? `${label}:${current.label}` : label,
|
|
1775
2222
|
'aria-haspopup': 'menu',
|
|
1776
2223
|
'aria-expanded': open ? 'true' : 'false',
|
|
2224
|
+
title: title ?? current?.label,
|
|
1777
2225
|
onClick: () => setOpen((currentOpen) => !currentOpen),
|
|
1778
2226
|
},
|
|
1779
2227
|
h('span', {
|
|
1780
2228
|
className: 'capital-automation-selectvalue',
|
|
1781
|
-
title: current?.label,
|
|
2229
|
+
title: title ?? current?.label,
|
|
1782
2230
|
}, current?.label ?? ''),
|
|
1783
2231
|
h(IconChevronDownOutlineRegular, { size: size === 'sm' ? 12 : 14 }))
|
|
1784
2232
|
return h(Menu, {
|
|
@@ -1794,23 +2242,26 @@ function Select(props) {
|
|
|
1794
2242
|
}
|
|
1795
2243
|
|
|
1796
2244
|
function SelectField(props) {
|
|
1797
|
-
const { label, hint, ariaLabel, block = true, ...rest } = props
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
h(
|
|
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 }),
|
|
1801
2250
|
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
1802
2251
|
}
|
|
1803
2252
|
|
|
1804
2253
|
function NumberField(props) {
|
|
1805
|
-
const { label, value, min, max, disabled, onChange } = props
|
|
1806
|
-
return h(Field, { label },
|
|
2254
|
+
const { label, value, min, max, disabled, onChange, error, onBlur } = props
|
|
2255
|
+
return h(Field, { label, error },
|
|
1807
2256
|
h('input', {
|
|
1808
2257
|
type: 'number',
|
|
1809
2258
|
min,
|
|
1810
2259
|
max,
|
|
1811
2260
|
value,
|
|
1812
2261
|
disabled,
|
|
2262
|
+
'aria-invalid': error ? 'true' : undefined,
|
|
1813
2263
|
onChange: (event) => onChange(Number(event.target.value)),
|
|
2264
|
+
onBlur,
|
|
1814
2265
|
}))
|
|
1815
2266
|
}
|
|
1816
2267
|
|
|
@@ -1879,13 +2330,15 @@ function OverviewTab(props) {
|
|
|
1879
2330
|
const stateText = anyRunning ? zh.status.running : stateLabel(detail.task.state)
|
|
1880
2331
|
const originLabel = detail.task.origin === 'builtin' ? zh.origin.builtin : zh.origin.custom
|
|
1881
2332
|
const scheduleText = `${scheduleRangeSummary(detail.task.trigger)}${detail.task.trigger.tradingDaysOnly === true ? ` · ${zh.tradingDaysField}` : ''}`
|
|
1882
|
-
const scheduleHint = detail.task.trigger.
|
|
2333
|
+
const scheduleHint = detail.task.trigger.cadence === 'once'
|
|
2334
|
+
? zh.triggerHintOnce
|
|
2335
|
+
: detail.task.trigger.tradingDaysOnly === true ? zh.triggerHint : zh.triggerHintNatural
|
|
1883
2336
|
return h('div', { className: 'capital-automation-overview' },
|
|
1884
2337
|
h('div', { className: 'capital-automation-statgrid' },
|
|
1885
2338
|
h('div', { className: 'capital-automation-stat' },
|
|
1886
2339
|
h('span', { className: 'capital-automation-statlabel' }, zh.stateLabel),
|
|
1887
2340
|
h('span', { className: 'capital-automation-statvalue', 'data-tone': anyRunning ? 'running' : undefined }, stateText),
|
|
1888
|
-
h('span', { className: 'capital-automation-statsub'
|
|
2341
|
+
h('span', { className: 'capital-automation-statsub' }, originLabel)),
|
|
1889
2342
|
h('div', { className: 'capital-automation-stat' },
|
|
1890
2343
|
h('span', { className: 'capital-automation-statlabel' }, zh.nextRun),
|
|
1891
2344
|
h('span', { className: 'capital-automation-statvalue' }, next.value),
|
|
@@ -1923,10 +2376,7 @@ function OverviewTab(props) {
|
|
|
1923
2376
|
h('span', { className: 'capital-automation-kvvalue' }, workspaceTitle(meta, detail.task.workspaceId))),
|
|
1924
2377
|
h('div', { className: 'capital-automation-kvitem' },
|
|
1925
2378
|
h('span', { className: 'capital-automation-kvlabel' }, zh.categoryField),
|
|
1926
|
-
h('span', { className: 'capital-automation-kvvalue' }, categoryLabel(detail.task.category))),
|
|
1927
|
-
h('div', { className: 'capital-automation-kvitem' },
|
|
1928
|
-
h('span', { className: 'capital-automation-kvlabel' }, zh.modeField),
|
|
1929
|
-
h('span', { className: 'capital-automation-kvvalue' }, modeLabel(detail.task.mode)))),
|
|
2379
|
+
h('span', { className: 'capital-automation-kvvalue' }, categoryLabel(detail.task.category)))),
|
|
1930
2380
|
h('section', { className: 'capital-automation-technical' },
|
|
1931
2381
|
h('h4', { className: 'capital-automation-technicaltitle' }, zh.techInfo),
|
|
1932
2382
|
h('dl', null,
|
|
@@ -1947,40 +2397,61 @@ function OverviewTab(props) {
|
|
|
1947
2397
|
function ConfigTab(props) {
|
|
1948
2398
|
const { draft, setDraft, meta, busy, dirty, save, reset, disabled } = props
|
|
1949
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')
|
|
1950
2404
|
const set = (patch) => setDraft((current) => ({
|
|
1951
2405
|
...current,
|
|
1952
2406
|
...(typeof patch === 'function' ? patch(current) : patch),
|
|
1953
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
|
+
}
|
|
1954
2419
|
const timeoutRange = limitValue(meta, 'runTimeoutMinutes', [5, 120])
|
|
1955
2420
|
const idleRange = limitValue(meta, 'idleTimeoutMinutes', [1, 30])
|
|
1956
2421
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
1957
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 })))
|
|
1958
2446
|
const advanced = h('details', { className: 'capital-automation-advanced' },
|
|
1959
2447
|
h('summary', null, zh.advanced),
|
|
1960
2448
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
1961
2449
|
h('div', { className: 'capital-automation-formgrid' },
|
|
1962
|
-
h(
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
onChange: (workspaceId) => set({ workspaceId }),
|
|
1968
|
-
}),
|
|
1969
|
-
h(SelectField, {
|
|
1970
|
-
label: zh.modeField,
|
|
1971
|
-
hint: zh.modeHint,
|
|
1972
|
-
value: task.mode,
|
|
1973
|
-
options: Object.entries(zh.mode).map(([value, label]) => ({ value, label })),
|
|
1974
|
-
disabled: busy || disabled,
|
|
1975
|
-
onChange: (mode) => set({ mode }),
|
|
1976
|
-
})),
|
|
1977
|
-
h('div', { className: 'capital-automation-formgrid' },
|
|
1978
|
-
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 } })) }),
|
|
1979
|
-
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 } })) }),
|
|
1980
|
-
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 } })) }),
|
|
1981
|
-
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 } })) })),
|
|
1982
|
-
h('p', { className: 'capital-automation-hint' }, zh.budgetHint),
|
|
1983
|
-
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)))
|
|
1984
2455
|
const notificationRows = h('div', null,
|
|
1985
2456
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
1986
2457
|
h('div', { className: 'capital-automation-switchlist' },
|
|
@@ -1988,41 +2459,52 @@ function ConfigTab(props) {
|
|
|
1988
2459
|
h(SwitchRow, { label: zh.notifyFailure, checked: task.notifications.onFailure, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onFailure: value } })) }),
|
|
1989
2460
|
h(SwitchRow, { label: zh.notifySuccess, checked: task.notifications.onSuccess, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onSuccess: value } })) })),
|
|
1990
2461
|
h('p', { className: 'capital-automation-hint' }, zh.notifyHint))
|
|
1991
|
-
return h('div', { className: 'capital-automation-form' },
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
h('input', {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
h(SelectField, {
|
|
2002
|
-
label: zh.categoryField,
|
|
2003
|
-
hint: task.origin === 'builtin' ? zh.categoryFixed : undefined,
|
|
2004
|
-
value: task.category,
|
|
2005
|
-
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
2006
|
-
disabled: busy || disabled || task.origin === 'builtin',
|
|
2007
|
-
onChange: (category) => set({ category }),
|
|
2008
|
-
}),
|
|
2009
|
-
h(Field, { label: zh.instructionField },
|
|
2010
|
-
h('textarea', {
|
|
2011
|
-
value: task.instruction,
|
|
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,
|
|
2012
2472
|
disabled: busy || disabled,
|
|
2013
|
-
|
|
2014
|
-
onChange: (event) => set({
|
|
2473
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2474
|
+
onChange: (event) => set({ title: event.target.value }),
|
|
2475
|
+
onBlur: () => touch('title'),
|
|
2015
2476
|
})),
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2477
|
+
h(SelectField, {
|
|
2478
|
+
label: zh.categoryField,
|
|
2479
|
+
title: task.origin === 'builtin' ? zh.categoryFixed : undefined,
|
|
2480
|
+
value: task.category,
|
|
2481
|
+
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
2482
|
+
disabled: busy || disabled || task.origin === 'builtin',
|
|
2483
|
+
onChange: (category) => set({ category }),
|
|
2484
|
+
})),
|
|
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),
|
|
2019
2501
|
advanced,
|
|
2020
2502
|
notificationRows,
|
|
2021
2503
|
h('div', { className: 'capital-automation-formactions', 'data-sticky': 'true' },
|
|
2022
2504
|
h('span', { className: 'capital-automation-formstatus' },
|
|
2023
2505
|
disabled ? zh.storageUnavailable : dirty ? zh.dirty : zh.noDirty),
|
|
2024
|
-
h('button', { type: 'button', className: 'capital-automation-btn', disabled: busy || !dirty, onClick: reset }, zh.cancel),
|
|
2025
|
-
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)))
|
|
2026
2508
|
}
|
|
2027
2509
|
|
|
2028
2510
|
function NotificationsTab(props) {
|
|
@@ -2125,11 +2607,6 @@ function TaskDetailPage(props) {
|
|
|
2125
2607
|
}
|
|
2126
2608
|
|
|
2127
2609
|
async function save() {
|
|
2128
|
-
const invalid = validateDraft(task)
|
|
2129
|
-
if (invalid.length > 0) {
|
|
2130
|
-
setError(invalid)
|
|
2131
|
-
return
|
|
2132
|
-
}
|
|
2133
2610
|
await act('save', async () => {
|
|
2134
2611
|
await api.patch(detail.task.id, draftPayload(task))
|
|
2135
2612
|
}, zh.saved)
|
|
@@ -2284,6 +2761,9 @@ function NewTaskModal(props) {
|
|
|
2284
2761
|
const [draft, setDraft] = useState(defaultDraft)
|
|
2285
2762
|
const [busy, setBusy] = useState(false)
|
|
2286
2763
|
const [error, setError] = useState('')
|
|
2764
|
+
const { issues, errorFor, touch, touchAll } = useDraftIssues(draft, open)
|
|
2765
|
+
const titleError = errorFor('title')
|
|
2766
|
+
const instructionError = errorFor('instruction')
|
|
2287
2767
|
useEffect(() => {
|
|
2288
2768
|
if (!open) return
|
|
2289
2769
|
setDraft(defaultDraft())
|
|
@@ -2299,9 +2779,8 @@ function NewTaskModal(props) {
|
|
|
2299
2779
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
2300
2780
|
const toolsRange = limitValue(meta, 'maxToolCalls', [10, 6000])
|
|
2301
2781
|
async function create() {
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
setError(invalid)
|
|
2782
|
+
if (Object.keys(issues).length > 0) {
|
|
2783
|
+
touchAll()
|
|
2305
2784
|
return
|
|
2306
2785
|
}
|
|
2307
2786
|
setBusy(true)
|
|
@@ -2328,79 +2807,81 @@ function NewTaskModal(props) {
|
|
|
2328
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)),
|
|
2329
2808
|
},
|
|
2330
2809
|
h('div', { className: 'capital-automation-wizard' },
|
|
2331
|
-
h('div', { className: 'capital-automation-modalheadline' },
|
|
2810
|
+
h('div', { className: 'capital-automation-modalheadline', 'data-invalid': titleError ? 'true' : undefined },
|
|
2332
2811
|
h(Glyph, { task: draft, size: 42 }),
|
|
2333
2812
|
h('input', {
|
|
2334
2813
|
className: 'capital-automation-modaltitleinput',
|
|
2335
2814
|
value: draft.title,
|
|
2815
|
+
maxLength: TITLE_MAX_LENGTH,
|
|
2336
2816
|
placeholder: zh.titlePlaceholder,
|
|
2337
2817
|
'aria-label': zh.titleField,
|
|
2818
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2338
2819
|
onChange: (event) => set({ title: event.target.value }),
|
|
2820
|
+
onBlur: () => touch('title'),
|
|
2339
2821
|
})),
|
|
2822
|
+
titleError ? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, titleError) : null,
|
|
2340
2823
|
h('p', { className: 'capital-automation-hint' }, zh.newTaskHint),
|
|
2341
|
-
h(Field, {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
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
|
+
})),
|
|
2347
2834
|
h('div', null,
|
|
2348
2835
|
h('span', { className: 'capital-automation-formlabel' }, zh.triggerField),
|
|
2349
|
-
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta })),
|
|
2836
|
+
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta, errorFor, touch })),
|
|
2350
2837
|
h('div', null,
|
|
2351
2838
|
h('span', { className: 'capital-automation-formlabel' }, zh.instructionField),
|
|
2352
|
-
h('div', { className: 'capital-automation-
|
|
2353
|
-
h('
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
align: 'end',
|
|
2390
|
-
})),
|
|
2391
|
-
h('span', { className: 'capital-automation-presetchip', title: zh.modeHint },
|
|
2392
|
-
h(IconSparkleRegular, { size: 12 }),
|
|
2393
|
-
zh.presetValue)))),
|
|
2394
|
-
h('p', { className: 'capital-automation-hint' }, zh.modeHint),
|
|
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)),
|
|
2395
2876
|
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null,
|
|
2396
2877
|
h('details', { className: 'capital-automation-advanced' },
|
|
2397
2878
|
h('summary', null, zh.advanced),
|
|
2398
2879
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
2399
2880
|
h('div', { className: 'capital-automation-formgrid' },
|
|
2400
|
-
h(NumberField, { label: zh.runTimeout, value: draft.limits.runTimeoutMinutes, min: timeoutRange[0], max: timeoutRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, runTimeoutMinutes: value } })) }),
|
|
2401
|
-
h(NumberField, { label: zh.idleTimeout, value: draft.limits.idleTimeoutMinutes, min: idleRange[0], max: idleRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, idleTimeoutMinutes: value } })) }),
|
|
2402
|
-
h(NumberField, { label: zh.maxSteps, value: draft.limits.maxSteps, min: stepsRange[0], max: stepsRange[1], onChange: (value) => set((current) => ({ limits: { ...current.limits, maxSteps: value } })) }),
|
|
2403
|
-
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 } })) })),
|
|
2404
2885
|
h('p', { className: 'capital-automation-hint' }, zh.advancedHint))),
|
|
2405
2886
|
h('div', null,
|
|
2406
2887
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
@@ -2729,8 +3210,6 @@ function AutomationPage(props) {
|
|
|
2729
3210
|
const initialLoading = meta === null && loadError.length === 0
|
|
2730
3211
|
|
|
2731
3212
|
return h('div', { className: 'capital-automation-page' },
|
|
2732
|
-
h('datalist', { id: 'capital-automation-timezones' },
|
|
2733
|
-
TIMEZONES.map((zone) => h('option', { key: zone, value: zone }))),
|
|
2734
3213
|
detailOpen ? null : h(Header, {
|
|
2735
3214
|
tab,
|
|
2736
3215
|
onTab: switchTab,
|