@v587d/capital-generation 2.13.2 → 2.14.1
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 +44 -0
- package/README.md +2 -2
- package/automation/builtins.js +9 -69
- package/automation/client.js +73 -26
- package/automation/client.src.cjs +755 -278
- 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,46 @@ 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: '每天',
|
|
169
207
|
tradingDaysField: '仅交易日',
|
|
170
208
|
weekdayRequired: '每周至少选择一天。',
|
|
209
|
+
dateRequired: '请选择运行日期。',
|
|
210
|
+
invalidDate: '请选择有效的日期。',
|
|
171
211
|
invalidTime: '运行时间格式不正确,请选择 HH:mm。',
|
|
172
212
|
invalidTimezone: '时区不能为空,请填写 IANA 时区。',
|
|
173
213
|
invalidTimezoneValue: '时区不被识别,请填写有效的 IANA 时区。',
|
|
174
|
-
invalidLimits: '运行上限必须是有效数字。',
|
|
175
214
|
filterNoRuns: '没有符合筛选条件的运行记录。',
|
|
176
215
|
schedulePrefix: '下次',
|
|
177
216
|
workspaceField: '工作区',
|
|
@@ -192,6 +231,7 @@ const zh = {
|
|
|
192
231
|
notifyHint: '通知只记录在当前 profile;不会发送邮件或外部消息。',
|
|
193
232
|
presetLabel: '运行模式',
|
|
194
233
|
presetValue: '自动模式',
|
|
234
|
+
presetHint: '所有运行固定使用自动模式。',
|
|
195
235
|
budgetHint: '工具调用与步骤预算覆盖整棵运行子树:子 Agent 与 PTC 派发都计入。',
|
|
196
236
|
environment: '执行环境',
|
|
197
237
|
policyField: '无人值守策略',
|
|
@@ -262,7 +302,6 @@ const zh = {
|
|
|
262
302
|
schedulerBlocked: '调度器未取得运行权,暂时不能手动运行。',
|
|
263
303
|
runBlocked: '任务正在运行,结束或取消后才能再次运行。',
|
|
264
304
|
configWhileRunning: '任务正在运行;修改会在下一次运行时生效。',
|
|
265
|
-
configBuiltinHint: '这是内置模板;修改只覆盖当前 profile,休眠后会恢复默认。',
|
|
266
305
|
detailRecentRuns: '最近 {n} 次运行',
|
|
267
306
|
detailNoRuns: '还没有运行记录。',
|
|
268
307
|
detailAllRuns: '完整记录',
|
|
@@ -281,7 +320,6 @@ const zh = {
|
|
|
281
320
|
lastRun: '上次运行',
|
|
282
321
|
neverRun: '尚未运行',
|
|
283
322
|
relativeNow: '刚刚',
|
|
284
|
-
timezoneHint: 'IANA 时区,例如 Asia/Shanghai。',
|
|
285
323
|
nextRunUnavailable: '交易日历不可用,无法计算下一次运行。',
|
|
286
324
|
copyHint: '复制后会生成一个暂停状态的自定义任务。',
|
|
287
325
|
notificationTaskGone: '任务已不存在',
|
|
@@ -293,7 +331,9 @@ const zh = {
|
|
|
293
331
|
everyPrefix: '每',
|
|
294
332
|
weeklyPrefix: '每周',
|
|
295
333
|
monthlyPrefix: '每月',
|
|
334
|
+
monthlyLastDay: '最后一天',
|
|
296
335
|
dailyPrefix: '每天',
|
|
336
|
+
oncePrefix: '单次',
|
|
297
337
|
tradingSuffix: '(仅交易日)',
|
|
298
338
|
sessionMissing: '没有可打开的 session',
|
|
299
339
|
}
|
|
@@ -510,17 +550,31 @@ function installStyles() {
|
|
|
510
550
|
.capital-automation-error { margin: 0; color: var(--ca-error-text); font-size: 12px; line-height: 18px; }
|
|
511
551
|
.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
552
|
.capital-automation-form { display: flex; flex-direction: column; gap: 16px; }
|
|
513
|
-
.capital-automation-
|
|
514
|
-
.capital-automation-
|
|
553
|
+
.capital-automation-instructionrow { display: grid; grid-template-columns: minmax(320px, 1fr) minmax(420px, 520px); align-items: stretch; gap: 16px; }
|
|
554
|
+
.capital-automation-instructionside { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
515
555
|
.capital-automation-formgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
|
|
556
|
+
.capital-automation-titlerow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(150px, 210px); align-items: start; gap: 12px; }
|
|
516
557
|
.capital-automation-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
|
517
|
-
.capital-automation-
|
|
518
|
-
.capital-automation-
|
|
558
|
+
.capital-automation-fieldhead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; min-width: 0; }
|
|
559
|
+
.capital-automation-fieldlabel { color: var(--ca-muted); font-size: 11.5px; line-height: 17px; }
|
|
560
|
+
.capital-automation-fieldhint { color: var(--ca-caption); font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums; }
|
|
561
|
+
.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
562
|
.capital-automation-field input { height: 34px; }
|
|
520
|
-
.capital-automation-
|
|
521
|
-
.capital-automation-field
|
|
522
|
-
.capital-automation-field
|
|
523
|
-
.capital-automation-
|
|
563
|
+
.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; }
|
|
564
|
+
.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; }
|
|
565
|
+
.capital-automation-field .capital-automation-texteditor.capital-automation-descriptioninput { flex: none; height: 58px; min-height: 58px; }
|
|
566
|
+
.capital-automation-texteditor:empty:before { content: attr(data-placeholder); color: var(--ca-caption); pointer-events: none; }
|
|
567
|
+
.capital-automation-field .capital-automation-texteditor:hover { border-color: var(--ca-line); }
|
|
568
|
+
.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; }
|
|
569
|
+
.capital-automation-field[data-invalid="true"] .capital-automation-texteditor { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
570
|
+
.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); }
|
|
571
|
+
.capital-automation-field .capital-automation-texteditor[contenteditable="false"] { color: var(--ca-muted); opacity: .8; cursor: not-allowed; }
|
|
572
|
+
.capital-automation-field input:hover { border-color: var(--ca-line); }
|
|
573
|
+
.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; }
|
|
574
|
+
.capital-automation-field[data-invalid="true"] input { border-color: color-mix(in srgb, var(--ca-error) 58%, var(--ca-line-soft)); }
|
|
575
|
+
.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); }
|
|
576
|
+
.capital-automation-field input:disabled { color: var(--ca-muted); opacity: .8; }
|
|
577
|
+
.capital-automation-fielderror { color: var(--ca-error-text); font-size: 11px; line-height: 16px; }
|
|
524
578
|
.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
579
|
.capital-automation-select:hover:not(:disabled) { background: var(--ca-hover); }
|
|
526
580
|
.capital-automation-select[data-block="true"] { width: 100%; }
|
|
@@ -532,20 +586,37 @@ function installStyles() {
|
|
|
532
586
|
.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
587
|
.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
588
|
.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-
|
|
589
|
+
.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); }
|
|
590
|
+
.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); }
|
|
591
|
+
.capital-automation-triggerhead { display: flex; flex-wrap: nowrap; align-items: center; justify-content: space-between; gap: 12px; min-width: 0; }
|
|
592
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:first-child { flex: 1 1 auto; min-width: 0; }
|
|
593
|
+
.capital-automation-triggerhead > .capital-automation-triggerfield:last-child { flex: none; }
|
|
594
|
+
.capital-automation-triggerfield { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
|
|
595
|
+
.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; }
|
|
596
|
+
.capital-automation-triggerfield .capital-automation-select:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
597
|
+
.capital-automation-triggerlabel { color: var(--ca-muted); font-size: 11.5px; line-height: 16px; white-space: nowrap; }
|
|
598
|
+
.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; }
|
|
599
|
+
.capital-automation-triggerbox input.capital-automation-triggerdate { flex: 0 1 146px; width: 146px; min-width: 120px; }
|
|
600
|
+
.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); }
|
|
601
|
+
.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; }
|
|
602
|
+
.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)); }
|
|
603
|
+
.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); }
|
|
604
|
+
.capital-automation-triggerbox input.capital-automation-triggertime:disabled, .capital-automation-triggerbox input.capital-automation-triggerdate:disabled { opacity: .5; cursor: not-allowed; }
|
|
605
|
+
body[data-ds-dark-theme] .capital-automation-triggertime, body[data-ds-dark-theme] .capital-automation-triggerdate { color-scheme: dark; }
|
|
606
|
+
body:not([data-ds-dark-theme]) .capital-automation-triggertime, body:not([data-ds-dark-theme]) .capital-automation-triggerdate { color-scheme: light; }
|
|
607
|
+
.capital-automation-triggerdetail { display: flex; flex: 1 1 auto; align-items: center; gap: 6px; min-width: 0; min-height: 32px; }
|
|
608
|
+
.capital-automation-triggerdetail > .capital-automation-triggerfield { flex: 1 1 auto; min-width: 0; }
|
|
609
|
+
.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; }
|
|
610
|
+
.capital-automation-triggerdetail .capital-automation-triggerpick:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
611
|
+
.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; }
|
|
612
|
+
.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; }
|
|
613
|
+
.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); }
|
|
614
|
+
.capital-automation-triggerzone { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
615
|
+
.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; }
|
|
616
|
+
.capital-automation-triggersettings .capital-automation-select:hover:not(:disabled) { border-color: var(--ca-line); background: var(--ca-card); }
|
|
617
|
+
.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; }
|
|
618
|
+
.capital-automation-triggererrors { display: flex; flex-direction: column; gap: 4px; }
|
|
547
619
|
.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
620
|
.capital-automation-advanced { border-top: .5px solid var(--ca-line); padding-top: 12px; }
|
|
550
621
|
.capital-automation-advanced summary { color: var(--ca-muted); font-size: 12px; line-height: 18px; cursor: pointer; user-select: none; }
|
|
551
622
|
.capital-automation-advanced summary:hover { color: var(--dsw-alias-label-primary); }
|
|
@@ -598,16 +669,20 @@ function installStyles() {
|
|
|
598
669
|
.capital-automation-notificationmessage { display: -webkit-box; overflow: hidden; margin-top: 2px; color: var(--ca-muted); font-size: 11.5px; line-height: 18px; overflow-wrap: anywhere; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
|
|
599
670
|
.capital-automation-notificationtime { display: block; margin-top: 3px; color: var(--ca-caption); font-size: 11px; line-height: 16px; }
|
|
600
671
|
.capital-automation-modal { width: min(720px, calc(100vw - 32px)); max-width: min(720px, calc(100vw - 32px)); max-height: calc(100dvh - 40px); }
|
|
672
|
+
.capital-automation-modal-wizard { width: min(600px, calc(100vw - 32px)); max-width: min(600px, calc(100vw - 32px)); }
|
|
601
673
|
.capital-automation-modal-content { max-height: calc(100dvh - 128px); overflow: auto; }
|
|
602
674
|
.capital-automation-modal-narrow { width: min(480px, calc(100vw - 32px)); max-width: min(480px, calc(100vw - 32px)); }
|
|
603
675
|
.capital-automation-wizard { display: flex; flex-direction: column; gap: 14px; }
|
|
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,231 @@ 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 === 'workday') { onChange([1, 2, 3, 4, 5]); return }
|
|
1904
|
+
if (id === 'weekend') { onChange([6, 7]); return }
|
|
1905
|
+
const day = Number(id.slice('weekday-'.length))
|
|
1906
|
+
if (!WEEKDAYS.some((item) => item.value === day)) return
|
|
1907
|
+
const next = has(day) ? selected.filter((item) => item !== day) : [...selected, day].sort((left, right) => left - right)
|
|
1908
|
+
if (next.length > 0) onChange(next)
|
|
1651
1909
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1910
|
+
return h(Menu, {
|
|
1911
|
+
open,
|
|
1912
|
+
anchor: h('button', {
|
|
1913
|
+
type: 'button',
|
|
1914
|
+
className: 'capital-automation-select capital-automation-triggerpick',
|
|
1915
|
+
disabled,
|
|
1916
|
+
'aria-label': `${zh.weekdayField}:${summary}`,
|
|
1917
|
+
'aria-haspopup': 'menu',
|
|
1918
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1919
|
+
title: `${zh.weekdayField}:${summary}`,
|
|
1920
|
+
onClick: () => setOpen((current) => !current),
|
|
1921
|
+
},
|
|
1922
|
+
h('span', { className: 'capital-automation-selectvalue' }, summary),
|
|
1923
|
+
h(IconChevronDownOutlineRegular, { size: 14 })),
|
|
1924
|
+
items,
|
|
1925
|
+
selectedIds,
|
|
1926
|
+
onSelect: pick,
|
|
1927
|
+
onClose: () => setOpen(false),
|
|
1928
|
+
align: 'start',
|
|
1929
|
+
portal: true,
|
|
1930
|
+
})
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function MonthlySelect(props) {
|
|
1934
|
+
const { value, onChange, disabled } = props
|
|
1935
|
+
const [open, setOpen] = useState(false)
|
|
1936
|
+
const day = value === 'last' ? 'last' : Number(value ?? 1)
|
|
1937
|
+
const summary = day === 'last'
|
|
1938
|
+
? zh.monthlyLast
|
|
1939
|
+
: day === 1
|
|
1940
|
+
? zh.monthlyFirst
|
|
1941
|
+
: day === 15
|
|
1942
|
+
? zh.monthlyMid
|
|
1943
|
+
: zh.dayOfMonth.replace('{n}', String(day))
|
|
1944
|
+
const selectedId = day === 'last' ? 'last' : day === 1 ? 'first' : day === 15 ? 'mid' : 'custom'
|
|
1945
|
+
useEffect(() => {
|
|
1946
|
+
if (disabled === true && open) setOpen(false)
|
|
1947
|
+
}, [disabled, open])
|
|
1948
|
+
const items = [
|
|
1949
|
+
{ id: 'first', label: zh.monthlyFirst },
|
|
1950
|
+
{ id: 'mid', label: zh.monthlyMid },
|
|
1951
|
+
{ id: 'last', label: zh.monthlyLast },
|
|
1952
|
+
{
|
|
1953
|
+
id: 'custom',
|
|
1954
|
+
label: zh.monthlyCustom,
|
|
1955
|
+
submenu: Array.from({ length: 31 }, (_, index) => ({
|
|
1956
|
+
id: `monthly-day-${index + 1}`,
|
|
1957
|
+
label: zh.dayOfMonth.replace('{n}', String(index + 1)),
|
|
1958
|
+
})),
|
|
1959
|
+
},
|
|
1960
|
+
]
|
|
1961
|
+
const pick = (id) => {
|
|
1962
|
+
if (id === 'first') { onChange(1); setOpen(false); return }
|
|
1963
|
+
if (id === 'mid') { onChange(15); setOpen(false); return }
|
|
1964
|
+
if (id === 'last') { onChange('last'); setOpen(false); return }
|
|
1965
|
+
if (id.startsWith('monthly-day-')) {
|
|
1966
|
+
onChange(Number(id.slice('monthly-day-'.length)))
|
|
1967
|
+
setOpen(false)
|
|
1659
1968
|
}
|
|
1660
|
-
update({ weekdays: [...selected].sort((left, right) => left - right) })
|
|
1661
1969
|
}
|
|
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
|
-
|
|
1970
|
+
return h(Menu, {
|
|
1971
|
+
open,
|
|
1972
|
+
className: 'capital-automation-monthpick',
|
|
1973
|
+
anchor: h('button', {
|
|
1974
|
+
type: 'button',
|
|
1975
|
+
className: 'capital-automation-select capital-automation-triggerpick',
|
|
1976
|
+
disabled,
|
|
1977
|
+
'aria-label': `${zh.monthlyField}:${summary}`,
|
|
1978
|
+
'aria-haspopup': 'menu',
|
|
1979
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1980
|
+
title: `${zh.monthlyField}:${summary}`,
|
|
1981
|
+
onClick: () => setOpen((current) => !current),
|
|
1982
|
+
},
|
|
1983
|
+
h('span', { className: 'capital-automation-selectvalue' }, summary),
|
|
1984
|
+
h(IconChevronDownOutlineRegular, { size: 14 })),
|
|
1985
|
+
items,
|
|
1986
|
+
selectedId,
|
|
1987
|
+
onSelect: pick,
|
|
1988
|
+
onClose: () => setOpen(false),
|
|
1989
|
+
align: 'start',
|
|
1990
|
+
portal: false,
|
|
1991
|
+
})
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function TriggerFields(props) {
|
|
1995
|
+
const { trigger, onChange, disabled, meta, errorFor, touch } = props
|
|
1996
|
+
const update = (patch) => onChange(normalizeTrigger({ ...trigger, ...patch }))
|
|
1997
|
+
const setCadence = (cadence) => onChange(triggerChanged(cadence, trigger))
|
|
1998
|
+
|
|
1999
|
+
const issue = (field) => (typeof errorFor === 'function' ? errorFor(field) : undefined)
|
|
2000
|
+
const touchField = (field) => { if (typeof touch === 'function') touch(field) }
|
|
2001
|
+
const timeError = issue('time')
|
|
2002
|
+
const timezoneError = issue('timezone')
|
|
2003
|
+
const weekdayError = issue('weekdays')
|
|
2004
|
+
const dateError = issue('date')
|
|
2005
|
+
const errors = [timeError, timezoneError, weekdayError, dateError]
|
|
2006
|
+
.filter((message) => typeof message === 'string' && message.length > 0)
|
|
2007
|
+
const cadenceOptions = Object.entries(zh.cadence).map(([value, label]) => ({ value, label }))
|
|
2008
|
+
|
|
2009
|
+
return h('div', {
|
|
2010
|
+
className: 'capital-automation-triggerbox',
|
|
2011
|
+
'data-cadence': trigger.cadence,
|
|
2012
|
+
'data-invalid': errors.length > 0 ? 'true' : undefined,
|
|
2013
|
+
},
|
|
2014
|
+
h('div', { className: 'capital-automation-triggerhead' },
|
|
2015
|
+
h('div', { className: 'capital-automation-triggerfield' },
|
|
2016
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.cadenceField),
|
|
2017
|
+
h(Select, {
|
|
2018
|
+
value: trigger.cadence,
|
|
2019
|
+
options: cadenceOptions,
|
|
2020
|
+
onChange: setCadence,
|
|
2021
|
+
disabled,
|
|
2022
|
+
label: zh.cadenceField,
|
|
2023
|
+
}),
|
|
2024
|
+
h('div', { className: 'capital-automation-triggerdetail', 'data-cadence': trigger.cadence },
|
|
2025
|
+
trigger.cadence === 'weekly'
|
|
2026
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2027
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.weekdayField),
|
|
2028
|
+
h(WeekdaySelect, {
|
|
2029
|
+
value: trigger.weekdays ?? [],
|
|
2030
|
+
onChange: (weekdays) => update({ weekdays }),
|
|
2031
|
+
disabled,
|
|
2032
|
+
}))
|
|
2033
|
+
: trigger.cadence === 'monthly'
|
|
2034
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2035
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.dayField),
|
|
2036
|
+
h(MonthlySelect, {
|
|
2037
|
+
value: trigger.dayOfMonth ?? 1,
|
|
2038
|
+
onChange: (dayOfMonth) => update({ dayOfMonth }),
|
|
2039
|
+
disabled,
|
|
2040
|
+
}))
|
|
2041
|
+
: trigger.cadence === 'once'
|
|
2042
|
+
? h('div', { className: 'capital-automation-triggerfield' },
|
|
2043
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.dayField),
|
|
2044
|
+
h('input', {
|
|
2045
|
+
type: 'date',
|
|
2046
|
+
className: 'capital-automation-triggerdate',
|
|
2047
|
+
value: trigger.date ?? '',
|
|
2048
|
+
disabled,
|
|
2049
|
+
title: zh.onceHint,
|
|
2050
|
+
'aria-label': zh.dayField,
|
|
2051
|
+
'aria-invalid': dateError ? 'true' : undefined,
|
|
2052
|
+
'data-invalid': dateError ? 'true' : undefined,
|
|
2053
|
+
onChange: (event) => update({ date: event.target.value }),
|
|
2054
|
+
onBlur: () => touchField('date'),
|
|
2055
|
+
}))
|
|
2056
|
+
: null)),
|
|
2057
|
+
h('div', { className: 'capital-automation-triggerfield' },
|
|
2058
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.timeField),
|
|
2059
|
+
h('input', {
|
|
2060
|
+
type: 'time',
|
|
2061
|
+
className: 'capital-automation-triggertime',
|
|
2062
|
+
value: trigger.time,
|
|
2063
|
+
disabled,
|
|
2064
|
+
'aria-label': zh.timeField,
|
|
2065
|
+
'aria-invalid': timeError ? 'true' : undefined,
|
|
2066
|
+
'data-invalid': timeError ? 'true' : undefined,
|
|
2067
|
+
onChange: (event) => update({ time: event.target.value }),
|
|
2068
|
+
onBlur: () => touchField('time'),
|
|
2069
|
+
}))),
|
|
2070
|
+
h('div', { className: 'capital-automation-triggersettings' },
|
|
2071
|
+
h('span', { className: 'capital-automation-triggerzone' },
|
|
2072
|
+
h('span', { className: 'capital-automation-triggerlabel' }, zh.timezoneField),
|
|
2073
|
+
h(Select, {
|
|
2074
|
+
value: trigger.timezone,
|
|
2075
|
+
options: timezoneOptions(trigger.timezone),
|
|
2076
|
+
onChange: (timezone) => update({ timezone }),
|
|
2077
|
+
disabled,
|
|
2078
|
+
label: zh.timezoneField,
|
|
2079
|
+
size: 'sm',
|
|
2080
|
+
align: 'end',
|
|
2081
|
+
})),
|
|
2082
|
+
h('label', { className: 'capital-automation-triggerswitch' },
|
|
2083
|
+
h(Switch, {
|
|
2084
|
+
checked: trigger.tradingDaysOnly === true,
|
|
2085
|
+
onChange: (value) => update({ tradingDaysOnly: value }),
|
|
2086
|
+
label: zh.tradingDaysField,
|
|
2087
|
+
disabled,
|
|
2088
|
+
}),
|
|
2089
|
+
h('span', null, zh.tradingDaysField))),
|
|
2090
|
+
errors.length > 0
|
|
2091
|
+
? h('div', { className: 'capital-automation-triggererrors' },
|
|
2092
|
+
errors.map((message) => h('span', { key: message, className: 'capital-automation-fielderror', role: 'alert' }, message)))
|
|
2093
|
+
: null,
|
|
2094
|
+
meta?.calendar?.available === false ? h('p', { className: 'capital-automation-hint' }, zh.nextRunUnavailable) : null)
|
|
1724
2095
|
}
|
|
1725
2096
|
|
|
1726
2097
|
function SwitchRow(props) {
|
|
@@ -1732,10 +2103,84 @@ function SwitchRow(props) {
|
|
|
1732
2103
|
h(Switch, { checked: checked === true, onChange, label, disabled }))
|
|
1733
2104
|
}
|
|
1734
2105
|
|
|
2106
|
+
function FieldHead(props) {
|
|
2107
|
+
const { label, note, error } = props
|
|
2108
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2109
|
+
return h('span', { className: 'capital-automation-fieldhead' },
|
|
2110
|
+
h('span', { className: 'capital-automation-fieldlabel' }, label),
|
|
2111
|
+
invalid
|
|
2112
|
+
? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, error)
|
|
2113
|
+
: note ? h('span', { className: 'capital-automation-fieldhint' }, note) : null)
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* Fixed-height plain-text box. A native textarea brings a resize handle and browser chrome that
|
|
2118
|
+
* break the two-column height contract, so the editable surface is a contenteditable div; paste is
|
|
2119
|
+
* forced to plain text and maxLength is applied on input.
|
|
2120
|
+
*/
|
|
2121
|
+
function PlainTextEditor(props) {
|
|
2122
|
+
const { value, onChange, onBlur, placeholder, disabled, ariaLabel, invalid, className, maxLength } = props
|
|
2123
|
+
const ref = useRef(null)
|
|
2124
|
+
const limit = typeof maxLength === 'number' ? maxLength : undefined
|
|
2125
|
+
useEffect(() => {
|
|
2126
|
+
const node = ref.current
|
|
2127
|
+
if (node === null || node === undefined) return
|
|
2128
|
+
const next = value ?? ''
|
|
2129
|
+
if (document.activeElement !== node && node.innerText !== next) node.innerText = next
|
|
2130
|
+
}, [value])
|
|
2131
|
+
const read = (node) => (node.innerText ?? '').replace(/\r\n?/g, '\n')
|
|
2132
|
+
const commit = (node) => {
|
|
2133
|
+
let text = read(node)
|
|
2134
|
+
if (limit !== undefined && Array.from(text).length > limit) {
|
|
2135
|
+
text = Array.from(text).slice(0, limit).join('')
|
|
2136
|
+
node.innerText = text
|
|
2137
|
+
const range = document.createRange()
|
|
2138
|
+
range.selectNodeContents(node)
|
|
2139
|
+
range.collapse(false)
|
|
2140
|
+
const selection = window.getSelection()
|
|
2141
|
+
selection?.removeAllRanges()
|
|
2142
|
+
selection?.addRange(range)
|
|
2143
|
+
}
|
|
2144
|
+
if (text.length === 0 && node.innerHTML !== '') node.innerHTML = ''
|
|
2145
|
+
onChange(text)
|
|
2146
|
+
}
|
|
2147
|
+
return h('div', {
|
|
2148
|
+
ref,
|
|
2149
|
+
className,
|
|
2150
|
+
contentEditable: disabled === true ? 'false' : 'true',
|
|
2151
|
+
role: 'textbox',
|
|
2152
|
+
'aria-multiline': 'true',
|
|
2153
|
+
'aria-label': ariaLabel,
|
|
2154
|
+
'aria-invalid': invalid ? 'true' : undefined,
|
|
2155
|
+
'data-placeholder': placeholder,
|
|
2156
|
+
spellCheck: false,
|
|
2157
|
+
suppressContentEditableWarning: true,
|
|
2158
|
+
onInput: (event) => commit(event.currentTarget),
|
|
2159
|
+
onBlur: (event) => { commit(event.currentTarget); if (typeof onBlur === 'function') onBlur() },
|
|
2160
|
+
onPaste: (event) => {
|
|
2161
|
+
const text = event.clipboardData?.getData('text/plain')
|
|
2162
|
+
if (typeof text !== 'string') return
|
|
2163
|
+
event.preventDefault()
|
|
2164
|
+
const selection = window.getSelection()
|
|
2165
|
+
if (selection === null || selection.rangeCount === 0) return
|
|
2166
|
+
selection.deleteFromDocument()
|
|
2167
|
+
const range = selection.getRangeAt(0)
|
|
2168
|
+
const node = document.createTextNode(text)
|
|
2169
|
+
range.insertNode(node)
|
|
2170
|
+
range.setStartAfter(node)
|
|
2171
|
+
range.collapse(true)
|
|
2172
|
+
selection.removeAllRanges()
|
|
2173
|
+
selection.addRange(range)
|
|
2174
|
+
commit(event.currentTarget)
|
|
2175
|
+
},
|
|
2176
|
+
})
|
|
2177
|
+
}
|
|
2178
|
+
|
|
1735
2179
|
function Field(props) {
|
|
1736
|
-
const { label, hint, children } = props
|
|
1737
|
-
|
|
1738
|
-
|
|
2180
|
+
const { label, hint, note, error, children } = props
|
|
2181
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2182
|
+
return h('label', { className: 'capital-automation-field', 'data-invalid': invalid ? 'true' : undefined },
|
|
2183
|
+
h(FieldHead, { label, note, error }),
|
|
1739
2184
|
children,
|
|
1740
2185
|
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
1741
2186
|
}
|
|
@@ -1745,7 +2190,7 @@ function Field(props) {
|
|
|
1745
2190
|
* Native <select> is intentionally avoided so light/dark themes follow host tokens.
|
|
1746
2191
|
*/
|
|
1747
2192
|
function Select(props) {
|
|
1748
|
-
const { value, options, onChange, disabled, label, block, size, align = 'start' } = props
|
|
2193
|
+
const { value, options, onChange, disabled, label, block, size, align = 'start', title } = props
|
|
1749
2194
|
const [open, setOpen] = useState(false)
|
|
1750
2195
|
const triggerRef = useRef(null)
|
|
1751
2196
|
const items = options.map((option) => ({
|
|
@@ -1774,11 +2219,12 @@ function Select(props) {
|
|
|
1774
2219
|
'aria-label': current?.label ? `${label}:${current.label}` : label,
|
|
1775
2220
|
'aria-haspopup': 'menu',
|
|
1776
2221
|
'aria-expanded': open ? 'true' : 'false',
|
|
2222
|
+
title: title ?? current?.label,
|
|
1777
2223
|
onClick: () => setOpen((currentOpen) => !currentOpen),
|
|
1778
2224
|
},
|
|
1779
2225
|
h('span', {
|
|
1780
2226
|
className: 'capital-automation-selectvalue',
|
|
1781
|
-
title: current?.label,
|
|
2227
|
+
title: title ?? current?.label,
|
|
1782
2228
|
}, current?.label ?? ''),
|
|
1783
2229
|
h(IconChevronDownOutlineRegular, { size: size === 'sm' ? 12 : 14 }))
|
|
1784
2230
|
return h(Menu, {
|
|
@@ -1794,23 +2240,26 @@ function Select(props) {
|
|
|
1794
2240
|
}
|
|
1795
2241
|
|
|
1796
2242
|
function SelectField(props) {
|
|
1797
|
-
const { label, hint, ariaLabel, block = true, ...rest } = props
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
h(
|
|
2243
|
+
const { label, hint, note, error, ariaLabel, block = true, title, ...rest } = props
|
|
2244
|
+
const invalid = typeof error === 'string' && error.length > 0
|
|
2245
|
+
return h('div', { className: 'capital-automation-field', title, 'data-invalid': invalid ? 'true' : undefined },
|
|
2246
|
+
h(FieldHead, { label, note, error }),
|
|
2247
|
+
h(Select, { ...rest, label: ariaLabel ?? label, block, title }),
|
|
1801
2248
|
hint ? h('span', { className: 'capital-automation-hint' }, hint) : null)
|
|
1802
2249
|
}
|
|
1803
2250
|
|
|
1804
2251
|
function NumberField(props) {
|
|
1805
|
-
const { label, value, min, max, disabled, onChange } = props
|
|
1806
|
-
return h(Field, { label },
|
|
2252
|
+
const { label, value, min, max, disabled, onChange, error, onBlur } = props
|
|
2253
|
+
return h(Field, { label, error },
|
|
1807
2254
|
h('input', {
|
|
1808
2255
|
type: 'number',
|
|
1809
2256
|
min,
|
|
1810
2257
|
max,
|
|
1811
2258
|
value,
|
|
1812
2259
|
disabled,
|
|
2260
|
+
'aria-invalid': error ? 'true' : undefined,
|
|
1813
2261
|
onChange: (event) => onChange(Number(event.target.value)),
|
|
2262
|
+
onBlur,
|
|
1814
2263
|
}))
|
|
1815
2264
|
}
|
|
1816
2265
|
|
|
@@ -1879,13 +2328,15 @@ function OverviewTab(props) {
|
|
|
1879
2328
|
const stateText = anyRunning ? zh.status.running : stateLabel(detail.task.state)
|
|
1880
2329
|
const originLabel = detail.task.origin === 'builtin' ? zh.origin.builtin : zh.origin.custom
|
|
1881
2330
|
const scheduleText = `${scheduleRangeSummary(detail.task.trigger)}${detail.task.trigger.tradingDaysOnly === true ? ` · ${zh.tradingDaysField}` : ''}`
|
|
1882
|
-
const scheduleHint = detail.task.trigger.
|
|
2331
|
+
const scheduleHint = detail.task.trigger.cadence === 'once'
|
|
2332
|
+
? zh.triggerHintOnce
|
|
2333
|
+
: detail.task.trigger.tradingDaysOnly === true ? zh.triggerHint : zh.triggerHintNatural
|
|
1883
2334
|
return h('div', { className: 'capital-automation-overview' },
|
|
1884
2335
|
h('div', { className: 'capital-automation-statgrid' },
|
|
1885
2336
|
h('div', { className: 'capital-automation-stat' },
|
|
1886
2337
|
h('span', { className: 'capital-automation-statlabel' }, zh.stateLabel),
|
|
1887
2338
|
h('span', { className: 'capital-automation-statvalue', 'data-tone': anyRunning ? 'running' : undefined }, stateText),
|
|
1888
|
-
h('span', { className: 'capital-automation-statsub'
|
|
2339
|
+
h('span', { className: 'capital-automation-statsub' }, originLabel)),
|
|
1889
2340
|
h('div', { className: 'capital-automation-stat' },
|
|
1890
2341
|
h('span', { className: 'capital-automation-statlabel' }, zh.nextRun),
|
|
1891
2342
|
h('span', { className: 'capital-automation-statvalue' }, next.value),
|
|
@@ -1923,10 +2374,7 @@ function OverviewTab(props) {
|
|
|
1923
2374
|
h('span', { className: 'capital-automation-kvvalue' }, workspaceTitle(meta, detail.task.workspaceId))),
|
|
1924
2375
|
h('div', { className: 'capital-automation-kvitem' },
|
|
1925
2376
|
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)))),
|
|
2377
|
+
h('span', { className: 'capital-automation-kvvalue' }, categoryLabel(detail.task.category)))),
|
|
1930
2378
|
h('section', { className: 'capital-automation-technical' },
|
|
1931
2379
|
h('h4', { className: 'capital-automation-technicaltitle' }, zh.techInfo),
|
|
1932
2380
|
h('dl', null,
|
|
@@ -1947,40 +2395,61 @@ function OverviewTab(props) {
|
|
|
1947
2395
|
function ConfigTab(props) {
|
|
1948
2396
|
const { draft, setDraft, meta, busy, dirty, save, reset, disabled } = props
|
|
1949
2397
|
const task = draft
|
|
2398
|
+
const { issues, errorFor, touch, touchAll, clear } = useDraftIssues(task, task.id)
|
|
2399
|
+
const formRef = useRef(null)
|
|
2400
|
+
const titleError = errorFor('title')
|
|
2401
|
+
const instructionError = errorFor('instruction')
|
|
1950
2402
|
const set = (patch) => setDraft((current) => ({
|
|
1951
2403
|
...current,
|
|
1952
2404
|
...(typeof patch === 'function' ? patch(current) : patch),
|
|
1953
2405
|
}))
|
|
2406
|
+
const submit = () => {
|
|
2407
|
+
if (Object.keys(issues).length > 0) {
|
|
2408
|
+
touchAll()
|
|
2409
|
+
setTimeout(() => {
|
|
2410
|
+
const target = formRef.current?.querySelector('[data-invalid="true"] input, [data-invalid="true"] .capital-automation-texteditor')
|
|
2411
|
+
target?.focus?.()
|
|
2412
|
+
}, 0)
|
|
2413
|
+
return
|
|
2414
|
+
}
|
|
2415
|
+
void save()
|
|
2416
|
+
}
|
|
1954
2417
|
const timeoutRange = limitValue(meta, 'runTimeoutMinutes', [5, 120])
|
|
1955
2418
|
const idleRange = limitValue(meta, 'idleTimeoutMinutes', [1, 30])
|
|
1956
2419
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
1957
2420
|
const toolsRange = limitValue(meta, 'maxToolCalls', [10, 6000])
|
|
2421
|
+
const instructionEditor = h(Field, { label: zh.instructionField, error: instructionError },
|
|
2422
|
+
h(PlainTextEditor, {
|
|
2423
|
+
className: 'capital-automation-texteditor',
|
|
2424
|
+
value: task.instruction,
|
|
2425
|
+
disabled: busy || disabled,
|
|
2426
|
+
placeholder: zh.instructionPlaceholder,
|
|
2427
|
+
ariaLabel: zh.instructionField,
|
|
2428
|
+
invalid: instructionError ? true : undefined,
|
|
2429
|
+
onChange: (instruction) => set({ instruction }),
|
|
2430
|
+
onBlur: () => touch('instruction'),
|
|
2431
|
+
}))
|
|
2432
|
+
const instructionSide = h('div', { className: 'capital-automation-instructionside' },
|
|
2433
|
+
h(SelectField, {
|
|
2434
|
+
label: zh.workspaceField,
|
|
2435
|
+
value: task.workspaceId ?? '',
|
|
2436
|
+
options: workspaceOptions(meta, task.workspaceId).map((option) => ({ value: option.id, label: option.title })),
|
|
2437
|
+
disabled: busy || disabled,
|
|
2438
|
+
onChange: (workspaceId) => set({ workspaceId }),
|
|
2439
|
+
}),
|
|
2440
|
+
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null,
|
|
2441
|
+
h('div', null,
|
|
2442
|
+
h('span', { className: 'capital-automation-formlabel' }, zh.triggerField),
|
|
2443
|
+
h(TriggerFields, { trigger: task.trigger, disabled: busy || disabled, onChange: (trigger) => set({ trigger }), meta, errorFor, touch })))
|
|
1958
2444
|
const advanced = h('details', { className: 'capital-automation-advanced' },
|
|
1959
2445
|
h('summary', null, zh.advanced),
|
|
1960
2446
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
1961
2447
|
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))
|
|
2448
|
+
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 } })) }),
|
|
2449
|
+
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 } })) }),
|
|
2450
|
+
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 } })) }),
|
|
2451
|
+
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 } })) })),
|
|
2452
|
+
h('p', { className: 'capital-automation-hint' }, zh.budgetHint)))
|
|
1984
2453
|
const notificationRows = h('div', null,
|
|
1985
2454
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
1986
2455
|
h('div', { className: 'capital-automation-switchlist' },
|
|
@@ -1988,41 +2457,52 @@ function ConfigTab(props) {
|
|
|
1988
2457
|
h(SwitchRow, { label: zh.notifyFailure, checked: task.notifications.onFailure, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onFailure: value } })) }),
|
|
1989
2458
|
h(SwitchRow, { label: zh.notifySuccess, checked: task.notifications.onSuccess, disabled: busy || disabled, onChange: (value) => set((current) => ({ notifications: { ...current.notifications, onSuccess: value } })) })),
|
|
1990
2459
|
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,
|
|
2460
|
+
return h('div', { className: 'capital-automation-form', ref: formRef },
|
|
2461
|
+
h('div', { className: 'capital-automation-titlerow' },
|
|
2462
|
+
h(Field, {
|
|
2463
|
+
label: zh.titleField,
|
|
2464
|
+
error: titleError,
|
|
2465
|
+
note: zh.charCount.replace('{n}', String(Array.from(task.title ?? '').length)).replace('{max}', String(TITLE_MAX_LENGTH)),
|
|
2466
|
+
},
|
|
2467
|
+
h('input', {
|
|
2468
|
+
value: task.title,
|
|
2469
|
+
maxLength: TITLE_MAX_LENGTH,
|
|
2012
2470
|
disabled: busy || disabled,
|
|
2013
|
-
|
|
2014
|
-
onChange: (event) => set({
|
|
2471
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2472
|
+
onChange: (event) => set({ title: event.target.value }),
|
|
2473
|
+
onBlur: () => touch('title'),
|
|
2015
2474
|
})),
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2475
|
+
h(SelectField, {
|
|
2476
|
+
label: zh.categoryField,
|
|
2477
|
+
title: task.origin === 'builtin' ? zh.categoryFixed : undefined,
|
|
2478
|
+
value: task.category,
|
|
2479
|
+
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
2480
|
+
disabled: busy || disabled || task.origin === 'builtin',
|
|
2481
|
+
onChange: (category) => set({ category }),
|
|
2482
|
+
})),
|
|
2483
|
+
h(Field, {
|
|
2484
|
+
label: zh.descriptionField,
|
|
2485
|
+
note: zh.charCount.replace('{n}', String(Array.from(task.description ?? '').length)).replace('{max}', String(DESCRIPTION_MAX_LENGTH)),
|
|
2486
|
+
},
|
|
2487
|
+
h(PlainTextEditor, {
|
|
2488
|
+
className: 'capital-automation-texteditor capital-automation-descriptioninput',
|
|
2489
|
+
value: task.description ?? '',
|
|
2490
|
+
maxLength: DESCRIPTION_MAX_LENGTH,
|
|
2491
|
+
disabled: busy || disabled,
|
|
2492
|
+
placeholder: zh.descriptionPlaceholder,
|
|
2493
|
+
ariaLabel: zh.descriptionField,
|
|
2494
|
+
onChange: (description) => set({ description }),
|
|
2495
|
+
})),
|
|
2496
|
+
h('div', { className: 'capital-automation-instructionrow' },
|
|
2497
|
+
instructionEditor,
|
|
2498
|
+
instructionSide),
|
|
2019
2499
|
advanced,
|
|
2020
2500
|
notificationRows,
|
|
2021
2501
|
h('div', { className: 'capital-automation-formactions', 'data-sticky': 'true' },
|
|
2022
2502
|
h('span', { className: 'capital-automation-formstatus' },
|
|
2023
2503
|
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:
|
|
2504
|
+
h('button', { type: 'button', className: 'capital-automation-btn', disabled: busy || !dirty, onClick: () => { clear(); reset() } }, zh.cancel),
|
|
2505
|
+
h('button', { type: 'button', className: 'capital-automation-btn', 'data-variant': 'primary', disabled: busy || !dirty || disabled, onClick: submit }, busy === 'save' ? zh.saving : zh.save)))
|
|
2026
2506
|
}
|
|
2027
2507
|
|
|
2028
2508
|
function NotificationsTab(props) {
|
|
@@ -2125,11 +2605,6 @@ function TaskDetailPage(props) {
|
|
|
2125
2605
|
}
|
|
2126
2606
|
|
|
2127
2607
|
async function save() {
|
|
2128
|
-
const invalid = validateDraft(task)
|
|
2129
|
-
if (invalid.length > 0) {
|
|
2130
|
-
setError(invalid)
|
|
2131
|
-
return
|
|
2132
|
-
}
|
|
2133
2608
|
await act('save', async () => {
|
|
2134
2609
|
await api.patch(detail.task.id, draftPayload(task))
|
|
2135
2610
|
}, zh.saved)
|
|
@@ -2284,6 +2759,9 @@ function NewTaskModal(props) {
|
|
|
2284
2759
|
const [draft, setDraft] = useState(defaultDraft)
|
|
2285
2760
|
const [busy, setBusy] = useState(false)
|
|
2286
2761
|
const [error, setError] = useState('')
|
|
2762
|
+
const { issues, errorFor, touch, touchAll } = useDraftIssues(draft, open)
|
|
2763
|
+
const titleError = errorFor('title')
|
|
2764
|
+
const instructionError = errorFor('instruction')
|
|
2287
2765
|
useEffect(() => {
|
|
2288
2766
|
if (!open) return
|
|
2289
2767
|
setDraft(defaultDraft())
|
|
@@ -2299,9 +2777,8 @@ function NewTaskModal(props) {
|
|
|
2299
2777
|
const stepsRange = limitValue(meta, 'maxSteps', [10, 2000])
|
|
2300
2778
|
const toolsRange = limitValue(meta, 'maxToolCalls', [10, 6000])
|
|
2301
2779
|
async function create() {
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
setError(invalid)
|
|
2780
|
+
if (Object.keys(issues).length > 0) {
|
|
2781
|
+
touchAll()
|
|
2305
2782
|
return
|
|
2306
2783
|
}
|
|
2307
2784
|
setBusy(true)
|
|
@@ -2321,86 +2798,88 @@ function NewTaskModal(props) {
|
|
|
2321
2798
|
onClose: () => { if (!busy) onClose() },
|
|
2322
2799
|
title: zh.newTaskTitle,
|
|
2323
2800
|
closeLabel: zh.close,
|
|
2324
|
-
className: 'capital-automation-modal',
|
|
2801
|
+
className: 'capital-automation-modal capital-automation-modal-wizard',
|
|
2325
2802
|
contentClassName: 'capital-automation-modal-content',
|
|
2326
2803
|
footer: h('div', { className: 'capital-automation-formactions', style: { borderTop: 0, paddingTop: 0 } },
|
|
2327
2804
|
h('button', { type: 'button', className: 'capital-automation-btn', disabled: busy, onClick: onClose }, zh.cancel),
|
|
2328
2805
|
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
2806
|
},
|
|
2330
2807
|
h('div', { className: 'capital-automation-wizard' },
|
|
2331
|
-
h('div', { className: 'capital-automation-modalheadline' },
|
|
2808
|
+
h('div', { className: 'capital-automation-modalheadline', 'data-invalid': titleError ? 'true' : undefined },
|
|
2332
2809
|
h(Glyph, { task: draft, size: 42 }),
|
|
2333
2810
|
h('input', {
|
|
2334
2811
|
className: 'capital-automation-modaltitleinput',
|
|
2335
2812
|
value: draft.title,
|
|
2813
|
+
maxLength: TITLE_MAX_LENGTH,
|
|
2336
2814
|
placeholder: zh.titlePlaceholder,
|
|
2337
2815
|
'aria-label': zh.titleField,
|
|
2816
|
+
'aria-invalid': titleError ? 'true' : undefined,
|
|
2338
2817
|
onChange: (event) => set({ title: event.target.value }),
|
|
2818
|
+
onBlur: () => touch('title'),
|
|
2339
2819
|
})),
|
|
2820
|
+
titleError ? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, titleError) : null,
|
|
2340
2821
|
h('p', { className: 'capital-automation-hint' }, zh.newTaskHint),
|
|
2341
|
-
h(Field, {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2822
|
+
h(Field, {
|
|
2823
|
+
label: zh.descriptionField,
|
|
2824
|
+
note: zh.charCount.replace('{n}', String(Array.from(draft.description ?? '').length)).replace('{max}', String(DESCRIPTION_MAX_LENGTH)),
|
|
2825
|
+
},
|
|
2826
|
+
h('input', {
|
|
2827
|
+
value: draft.description,
|
|
2828
|
+
maxLength: DESCRIPTION_MAX_LENGTH,
|
|
2829
|
+
placeholder: zh.descriptionPlaceholder,
|
|
2830
|
+
onChange: (event) => set({ description: event.target.value }),
|
|
2831
|
+
})),
|
|
2347
2832
|
h('div', null,
|
|
2348
2833
|
h('span', { className: 'capital-automation-formlabel' }, zh.triggerField),
|
|
2349
|
-
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta })),
|
|
2834
|
+
h(TriggerFields, { trigger: draft.trigger, onChange: (trigger) => set({ trigger }), meta, errorFor, touch })),
|
|
2350
2835
|
h('div', null,
|
|
2351
2836
|
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),
|
|
2837
|
+
h('div', { className: 'capital-automation-promptfield', 'data-invalid': instructionError ? 'true' : undefined },
|
|
2838
|
+
h('div', { className: 'capital-automation-promptbox' },
|
|
2839
|
+
h(PlainTextEditor, {
|
|
2840
|
+
className: 'capital-automation-texteditor',
|
|
2841
|
+
value: draft.instruction,
|
|
2842
|
+
placeholder: zh.instructionPlaceholder,
|
|
2843
|
+
ariaLabel: zh.instructionField,
|
|
2844
|
+
invalid: instructionError ? true : undefined,
|
|
2845
|
+
onChange: (instruction) => set({ instruction }),
|
|
2846
|
+
onBlur: () => touch('instruction'),
|
|
2847
|
+
}),
|
|
2848
|
+
h('div', { className: 'capital-automation-prompttoolbar' },
|
|
2849
|
+
h('div', { className: 'capital-automation-toolbarfield' },
|
|
2850
|
+
h(IconFolderOpenOutlineRegular, { size: 13 }),
|
|
2851
|
+
h('span', null, zh.workspaceField),
|
|
2852
|
+
h(Select, {
|
|
2853
|
+
value: draft.workspaceId ?? '',
|
|
2854
|
+
options: workspaceOptions(meta, draft.workspaceId).map((option) => ({ value: option.id, label: option.title })),
|
|
2855
|
+
onChange: (workspaceId) => set({ workspaceId }),
|
|
2856
|
+
label: zh.workspaceField,
|
|
2857
|
+
size: 'sm',
|
|
2858
|
+
align: 'end',
|
|
2859
|
+
})),
|
|
2860
|
+
h('div', { className: 'capital-automation-toolbarfield' },
|
|
2861
|
+
h('span', null, zh.categoryField),
|
|
2862
|
+
h(Select, {
|
|
2863
|
+
value: draft.category,
|
|
2864
|
+
options: Object.entries(zh.category).map(([value, label]) => ({ value, label })),
|
|
2865
|
+
onChange: (category) => set({ category }),
|
|
2866
|
+
label: zh.categoryField,
|
|
2867
|
+
size: 'sm',
|
|
2868
|
+
align: 'end',
|
|
2869
|
+
})),
|
|
2870
|
+
h('span', { className: 'capital-automation-presetchip', title: zh.presetHint },
|
|
2871
|
+
h(IconSparkleRegular, { size: 12 }),
|
|
2872
|
+
zh.presetValue))),
|
|
2873
|
+
instructionError ? h('span', { className: 'capital-automation-fielderror', role: 'alert' }, instructionError) : null)),
|
|
2395
2874
|
meta?.workspaceError ? h('p', { className: 'capital-automation-hint', 'data-tone': 'error' }, zh.workspaceUnavailable) : null,
|
|
2396
2875
|
h('details', { className: 'capital-automation-advanced' },
|
|
2397
2876
|
h('summary', null, zh.advanced),
|
|
2398
2877
|
h('div', { className: 'capital-automation-advancedbody' },
|
|
2399
2878
|
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 } })) })),
|
|
2879
|
+
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 } })) }),
|
|
2880
|
+
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 } })) }),
|
|
2881
|
+
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 } })) }),
|
|
2882
|
+
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
2883
|
h('p', { className: 'capital-automation-hint' }, zh.advancedHint))),
|
|
2405
2884
|
h('div', null,
|
|
2406
2885
|
h('span', { className: 'capital-automation-formlabel' }, zh.notifyField),
|
|
@@ -2729,8 +3208,6 @@ function AutomationPage(props) {
|
|
|
2729
3208
|
const initialLoading = meta === null && loadError.length === 0
|
|
2730
3209
|
|
|
2731
3210
|
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
3211
|
detailOpen ? null : h(Header, {
|
|
2735
3212
|
tab,
|
|
2736
3213
|
onTab: switchTab,
|