feat: support custom supervisor review date ranges
This commit is contained in:
@@ -86,7 +86,13 @@ Modal 按[共享表单 Modal 规则](../../../UI-DESIGN.md#615-表单-modal)复
|
||||
|
||||
2026-09-30 已实现(规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计)):主按钮“回顾”执行增量整理,文案不再称“重新分析”;旁边“更多”菜单提供“重新整理…”,确认对话说明将重新分析所选区间并可能产生模型费用。计划 Modal 增加“介入方式”单选:“生成建议”“仅更新记忆”;主动介入上线前不显示。最近建议替代,每条建议显示依据、打开来源及转任务/确认/忽略。“最近建议”替代,每条建议显示依据、打开来源及转任务/确认/忽略。
|
||||
|
||||
当前页面提供全局或单项目范围,以及 1、7、30 天时间区间选择。没有自动计划也能手动回顾;成功结果显示自身冻结的范围、时间和未解决事项,可选择历史结果或进入图谱。来源筛选由本次回顾范围和时间限制;图谱详情只列出所选事件的关联来源,实体或关系列出相关事件的来源。独立的来源类型选择器尚未实现。
|
||||
当前页面提供全局或单项目范围。时间选择保留 1、7、30 天快捷项,默认 7 天,并提供“自定义天数”和“指定日期”。快捷项与自定义天数均从提交时刻向前计算,每天按 24 小时计;自定义值必须为正整数,且起始时间不得早于公元 1 年。
|
||||
|
||||
指定日期使用带可见标签的原生日期输入,按本地时区解释。起始时间为开始日期的零点,结束时间包含结束日期全天:取下一个本地日期的零点减 1 毫秒,跨夏令时不按固定 24 小时推算;结束日期为今天时截到提交时刻。允许同一天,拒绝缺失、无效、未来日期及开始晚于结束。天数和日期输入复用共享字段样式,窄窗口换行;错误就地显示,通过 `aria-invalid` 和 `aria-describedby` 关联输入,无效时禁用回顾和重新整理,提交入口再次校验。
|
||||
|
||||
重新整理确认显示当前范围及最近天数或明确起止日期,日期文案说明本地日期和今天截至当前的规则。修改范围或时间输入后关闭旧确认,重新整理仍需再次确认。新选择只改变请求时间范围,普通回顾继续跳过已处理且未变化的来源,重新整理仍使用既有显式标记,不改变增量处理规则。
|
||||
|
||||
没有自动计划也能手动回顾;成功结果显示自身冻结的范围、时间和未解决事项,可选择历史结果或进入图谱。来源筛选由本次回顾范围和时间限制;图谱详情只列出所选事件的关联来源,实体或关系列出相关事件的来源。独立的来源类型选择器尚未实现。
|
||||
|
||||
选择历史结果后,图谱读取该结果的成员和内容,历史菜单显示其回顾区间和生成时间。切换时清空旧图谱和详情并显示加载状态,迟到请求不得替换新选择。确认、修订和移除反馈更新当前所选结果,后续自动回顾不改写旧结果。
|
||||
|
||||
|
||||
@@ -15,6 +15,111 @@ const selectHistory = (id: string) => {
|
||||
}
|
||||
|
||||
describe('SupervisorWorkspace', () => {
|
||||
it.each(['zh-CN', 'en-US'] as const)('submits custom days and keeps reanalysis confirmation consistent in %s', async locale => {
|
||||
await changeUiLocale(locale)
|
||||
const copy = i18nResources[locale].heartbeat.supervisor
|
||||
const run = vi.fn().mockResolvedValue(undefined)
|
||||
window.goodbuddy = { supervision: { overview: async () => [], run } } as never
|
||||
render(<SupervisorWorkspace />)
|
||||
await screen.findByText(copy.empty)
|
||||
vi.useFakeTimers({ toFake: ['Date'] })
|
||||
const now = new Date(2026, 9, 5, 15, 30)
|
||||
vi.setSystemTime(now)
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.newReview }))
|
||||
const picker = screen.getByLabelText(copy.period)
|
||||
expect(within(picker).getAllByRole('option').map(option => (option as HTMLOptionElement).value)).toEqual(['1', '7', '30', 'custom', 'range'])
|
||||
expect(picker).toHaveValue('7')
|
||||
fireEvent.change(picker, { target: { value: 'custom' } })
|
||||
fireEvent.change(screen.getByRole('spinbutton', { name: copy.dayCount }), { target: { value: '45' } })
|
||||
await act(async () => fireEvent.click(screen.getByRole('button', { name: copy.run })))
|
||||
const timeRange = { from: new Date(now.getTime() - 45 * 86400_000).toISOString(), to: now.toISOString() }
|
||||
expect(run).toHaveBeenLastCalledWith({ trigger: 'manual', scope: { kind: 'global' }, timeRange })
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.more }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: copy.reanalyze }))
|
||||
expect(screen.getByRole('alertdialog')).toHaveTextContent(copy.recentDays.replace('{{count}}', '45'))
|
||||
fireEvent.change(screen.getByRole('spinbutton', { name: copy.dayCount }), { target: { value: '46' } })
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.more }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: copy.reanalyze }))
|
||||
expect(screen.getByRole('alertdialog')).toHaveTextContent(copy.recentDays.replace('{{count}}', '46'))
|
||||
await act(async () => fireEvent.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: copy.reanalyzeConfirm })))
|
||||
expect(run).toHaveBeenLastCalledWith({ trigger: 'manual', reanalyze: true, scope: { kind: 'global' },
|
||||
timeRange: { from: new Date(now.getTime() - 46 * 86400_000).toISOString(), to: now.toISOString() } })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['2025-11-02', '2025-11-02'],
|
||||
['2026-03-08', '2026-03-08'],
|
||||
['2026-09-29', '2026-10-02'],
|
||||
['2026-10-05', '2026-10-05']
|
||||
])('includes local calendar days %s through %s for review and reanalysis', async (start, end) => {
|
||||
const copy = i18nResources['zh-CN'].heartbeat.supervisor
|
||||
const run = vi.fn().mockResolvedValue(undefined)
|
||||
window.goodbuddy = { supervision: { overview: async () => [], run } } as never
|
||||
render(<SupervisorWorkspace />)
|
||||
await screen.findByText(copy.empty)
|
||||
vi.useFakeTimers({ toFake: ['Date'] })
|
||||
const now = new Date(2026, 9, 5, 15, 30)
|
||||
vi.setSystemTime(now)
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.newReview }))
|
||||
fireEvent.change(screen.getByLabelText(copy.period), { target: { value: 'range' } })
|
||||
fireEvent.change(screen.getByLabelText(copy.startDate), { target: { value: start } })
|
||||
fireEvent.change(screen.getByLabelText(copy.endDate), { target: { value: end } })
|
||||
const timeRange = { from: new Date(`${start}T00:00:00`).toISOString(),
|
||||
to: end === '2026-10-05' ? now.toISOString() : new Date(`${end}T23:59:59.999`).toISOString() }
|
||||
await act(async () => fireEvent.click(screen.getByRole('button', { name: copy.run })))
|
||||
expect(run).toHaveBeenLastCalledWith({ trigger: 'manual', scope: { kind: 'global' }, timeRange })
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.more }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: copy.reanalyze }))
|
||||
const dialog = screen.getByRole('alertdialog')
|
||||
expect(dialog).toHaveTextContent(copy.dateRangeLabel.replace('{{from}}', start).replace('{{to}}', end))
|
||||
await act(async () => fireEvent.click(within(dialog).getByRole('button', { name: copy.reanalyzeConfirm })))
|
||||
expect(run).toHaveBeenLastCalledWith({ trigger: 'manual', reanalyze: true, scope: { kind: 'global' }, timeRange })
|
||||
})
|
||||
|
||||
it('blocks invalid days and dates before execution lookup, preserves drafts and recovers', async () => {
|
||||
const copy = i18nResources['zh-CN'].heartbeat.supervisor
|
||||
const run = vi.fn().mockResolvedValue(undefined)
|
||||
const execution = vi.fn().mockResolvedValue({ active: false })
|
||||
window.goodbuddy = { supervision: { overview: async () => [], run, execution } } as never
|
||||
render(<SupervisorWorkspace />)
|
||||
await screen.findByText(copy.empty)
|
||||
vi.useFakeTimers({ toFake: ['Date'] })
|
||||
vi.setSystemTime(new Date(2026, 9, 5, 15, 30))
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.newReview }))
|
||||
const picker = screen.getByLabelText(copy.period)
|
||||
fireEvent.change(picker, { target: { value: 'custom' } })
|
||||
const days = screen.getByRole('spinbutton', { name: copy.dayCount })
|
||||
for (const value of ['', '0', '-1', '1.5', '1e3', '999999999999999999999', '1000000']) {
|
||||
fireEvent.change(days, { target: { value } })
|
||||
expect(days).toHaveAttribute('aria-invalid', 'true')
|
||||
expect(days).toHaveAccessibleDescription(copy.invalidDays)
|
||||
expect(screen.getByRole('button', { name: copy.run })).toBeDisabled()
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.run }))
|
||||
fireEvent.click(screen.getByRole('button', { name: copy.more }))
|
||||
expect(screen.getByRole('menuitem', { name: copy.reanalyze })).toBeDisabled()
|
||||
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Escape' })
|
||||
}
|
||||
fireEvent.change(days, { target: { value: '42' } })
|
||||
fireEvent.change(picker, { target: { value: 'range' } })
|
||||
for (const [start, end, error] of [
|
||||
['', '', copy.invalidDates], ['2026-02-30', '2026-03-02', copy.invalidDates],
|
||||
['2026-10-03', '', copy.invalidDates], ['2026-10-04', '2026-10-03', copy.reversedDates],
|
||||
['2026-10-04', '2026-10-06', copy.futureDates], ['2026-10-06', '2026-10-06', copy.futureDates]
|
||||
]) {
|
||||
fireEvent.change(screen.getByLabelText(copy.startDate), { target: { value: start } })
|
||||
fireEvent.change(screen.getByLabelText(copy.endDate), { target: { value: end } })
|
||||
expect(screen.getByLabelText(copy.endDate)).toHaveAccessibleDescription(error)
|
||||
expect(screen.getByRole('button', { name: copy.run })).toBeDisabled()
|
||||
}
|
||||
expect(execution).not.toHaveBeenCalled()
|
||||
expect(run).not.toHaveBeenCalled()
|
||||
fireEvent.change(picker, { target: { value: 'custom' } })
|
||||
expect(screen.getByRole('spinbutton', { name: copy.dayCount })).toHaveValue(42)
|
||||
await act(async () => fireEvent.click(screen.getByRole('button', { name: copy.run })))
|
||||
expect(run).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it.each(['overview', 'graph'] as const)('shows each history result scope and range only inside the %s picker', async tab => {
|
||||
const old = { ...result, id: 'old', createdAt: '2026-08-02T00:00:00.000Z',
|
||||
scope: { kind: 'projects', projectIds: ['11111111-1111-4111-8111-111111111111'] },
|
||||
|
||||
@@ -95,7 +95,10 @@ export function SupervisorWorkspace({
|
||||
conversationId?: string
|
||||
}>()
|
||||
const [projectId, setProjectId] = useState('global')
|
||||
const [days, setDays] = useState(7)
|
||||
const [period, setPeriod] = useState('7')
|
||||
const [customDays, setCustomDays] = useState('7')
|
||||
const [startDate, setStartDate] = useState('')
|
||||
const [endDate, setEndDate] = useState('')
|
||||
const [newReviewOpen, setNewReviewOpen] = useState(false)
|
||||
const [detailOpen, setDetailOpen] = useState(Boolean(graphNavigation?.focus))
|
||||
const detailToggleRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -215,9 +218,40 @@ export function SupervisorWorkspace({
|
||||
return () => { window.clearTimeout(task); invalidate() }
|
||||
}, [refresh, graphNavigation])
|
||||
|
||||
const resolvePeriod = (now: Date): { range?: SupervisionRunRequest['timeRange']; label?: string; error?: string } => {
|
||||
if (period === 'range') {
|
||||
const from = new Date(`${startDate}T00:00:00`)
|
||||
const end = new Date(`${endDate}T00:00:00`)
|
||||
const validDate = (value: string, parsed: Date) => /^\d{4}-\d{2}-\d{2}$/.test(value)
|
||||
&& Number(value.slice(0, 4)) >= 1 && Number.isFinite(parsed.getTime())
|
||||
&& parsed.getFullYear() === Number(value.slice(0, 4))
|
||||
&& parsed.getMonth() + 1 === Number(value.slice(5, 7)) && parsed.getDate() === Number(value.slice(8, 10))
|
||||
if (!validDate(startDate, from) || !validDate(endDate, end)) return { error: t('supervisor.invalidDates') }
|
||||
if (from > now || end > now) return { error: t('supervisor.futureDates') }
|
||||
if (from > end) return { error: t('supervisor.reversedDates') }
|
||||
// The backend includes both endpoints. Advance by a local calendar day for DST.
|
||||
const to = new Date(end)
|
||||
to.setDate(to.getDate() + 1)
|
||||
to.setHours(0, 0, 0, -1)
|
||||
return { range: { from: from.toISOString(), to: new Date(Math.min(to.getTime(), now.getTime())).toISOString() },
|
||||
label: t('supervisor.dateRangeLabel', { from: startDate, to: endDate }) }
|
||||
}
|
||||
const value = period === 'custom' ? customDays : period
|
||||
const days = Number(value)
|
||||
const from = new Date(now.getTime() - days * 86400_000)
|
||||
if (!/^\d+$/.test(value) || !Number.isSafeInteger(days) || days < 1
|
||||
|| !Number.isFinite(from.getTime()) || from.getFullYear() < 1) return { error: t('supervisor.invalidDays') }
|
||||
return { range: { from: from.toISOString(), to: now.toISOString() }, label: t('supervisor.recentDays', { count: days }) }
|
||||
}
|
||||
const reviewPeriod = resolvePeriod(new Date())
|
||||
const today = new Date()
|
||||
const maxDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
||||
|
||||
// A review is incremental by default. Only an explicit re-analysis reads the whole interval again.
|
||||
const run = async (reanalyze = false) => {
|
||||
if (!api) return
|
||||
const { range } = resolvePeriod(new Date())
|
||||
if (!range) return
|
||||
setConfirmReanalyze(false)
|
||||
const notice = (message: string, tone: AppNotificationInput['tone'] = 'info') => {
|
||||
if (mounted.current) onNotify?.({ tone, message, dedupeKey: 'supervisor-review' })
|
||||
@@ -228,7 +262,6 @@ export function SupervisorWorkspace({
|
||||
}
|
||||
runPending.current = true
|
||||
const generation = loadGeneration.current
|
||||
const to = new Date()
|
||||
const request: SupervisionRunRequest = {
|
||||
trigger: 'manual',
|
||||
...(reanalyze ? { reanalyze: true } : {}),
|
||||
@@ -236,10 +269,7 @@ export function SupervisorWorkspace({
|
||||
projectId === 'global'
|
||||
? { kind: 'global' }
|
||||
: { kind: 'projects', projectIds: [projectId] },
|
||||
timeRange: {
|
||||
from: new Date(to.getTime() - days * 86400_000).toISOString(),
|
||||
to: to.toISOString()
|
||||
}
|
||||
timeRange: range
|
||||
}
|
||||
let submitted = false
|
||||
try {
|
||||
@@ -578,7 +608,7 @@ export function SupervisorWorkspace({
|
||||
<select
|
||||
aria-label={t('supervisor.scope')}
|
||||
value={projectId}
|
||||
onChange={(event) => setProjectId(event.target.value)}
|
||||
onChange={(event) => { setProjectId(event.target.value); setConfirmReanalyze(false) }}
|
||||
>
|
||||
<option value="global">{t('center.scope.global')}</option>
|
||||
{projects
|
||||
@@ -593,18 +623,39 @@ export function SupervisorWorkspace({
|
||||
<label>
|
||||
<select
|
||||
aria-label={t('supervisor.period')}
|
||||
value={days}
|
||||
onChange={(event) => setDays(Number(event.target.value))}
|
||||
value={period}
|
||||
onChange={(event) => { setPeriod(event.target.value); setConfirmReanalyze(false) }}
|
||||
>
|
||||
{[1, 7, 30].map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{t('supervisor.days', { count: value })}
|
||||
</option>
|
||||
))}
|
||||
<option value="custom">{t('supervisor.customDays')}</option>
|
||||
<option value="range">{t('supervisor.dateRange')}</option>
|
||||
</select>
|
||||
</label>
|
||||
{period === 'custom' && <label className="supervisor-workspace__period-field">
|
||||
<span>{t('supervisor.dayCount')}</span>
|
||||
<input className="field-control" type="number" min={1} step={1} required value={customDays}
|
||||
aria-invalid={Boolean(reviewPeriod.error)} aria-describedby={`${graphId}-period-help`}
|
||||
onChange={event => { setCustomDays(event.target.value); setConfirmReanalyze(false) }} />
|
||||
</label>}
|
||||
{period === 'range' && <div className="supervisor-workspace__date-range">
|
||||
<label><span>{t('supervisor.startDate')}</span>
|
||||
<input className="field-control" type="date" min="0001-01-01" max={maxDate} required value={startDate}
|
||||
aria-invalid={Boolean(reviewPeriod.error)} aria-describedby={`${graphId}-period-help`}
|
||||
onChange={event => { setStartDate(event.target.value); setConfirmReanalyze(false) }} />
|
||||
</label>
|
||||
<label><span>{t('supervisor.endDate')}</span>
|
||||
<input className="field-control" type="date" min={startDate || '0001-01-01'} max={maxDate} required value={endDate}
|
||||
aria-invalid={Boolean(reviewPeriod.error)} aria-describedby={`${graphId}-period-help`}
|
||||
onChange={event => { setEndDate(event.target.value); setConfirmReanalyze(false) }} />
|
||||
</label>
|
||||
</div>}
|
||||
<button
|
||||
className="primary-button"
|
||||
disabled={!reviewPeriod.range}
|
||||
onClick={() => void run()}
|
||||
>
|
||||
{t('supervisor.run')}
|
||||
@@ -613,21 +664,25 @@ export function SupervisorWorkspace({
|
||||
aria-haspopup="menu" aria-expanded={moreOpen} aria-controls={moreOpen ? moreId : undefined}
|
||||
onClick={() => setMoreOpen(!moreOpen)}><Ellipsis size={16} aria-hidden="true" /></button>
|
||||
{moreOpen && <AnchoredMenu anchorRef={moreRef} id={moreId} label={t('supervisor.more')} width={220} onClose={() => setMoreOpen(false)}>
|
||||
<button type="button" role="menuitem" onClick={() => {
|
||||
<button type="button" role="menuitem" disabled={!reviewPeriod.range} onClick={() => {
|
||||
setMoreOpen(false)
|
||||
moreRef.current?.focus()
|
||||
setConfirmReanalyze(true)
|
||||
}}>{t('supervisor.reanalyze')}</button>
|
||||
</AnchoredMenu>}
|
||||
{(period === 'custom' || period === 'range') && <p id={`${graphId}-period-help`}
|
||||
className="supervisor-workspace__period-help" role={reviewPeriod.error ? 'alert' : undefined}>
|
||||
{reviewPeriod.error ?? t(period === 'range' ? 'supervisor.dateRangeHelp' : 'supervisor.customDaysHelp')}
|
||||
</p>}
|
||||
</div>
|
||||
{confirmReanalyze && <div className="supervisor-workspace__confirm" role="alertdialog" aria-labelledby={`${moreId}-title`}
|
||||
aria-describedby={`${moreId}-description`} onKeyDown={(event) => { if (event.key === 'Escape') setConfirmReanalyze(false) }}>
|
||||
<strong id={`${moreId}-title`}>{t('supervisor.reanalyzeTitle')}</strong>
|
||||
<p id={`${moreId}-description`}>{t('supervisor.reanalyzeHint', { scope: projectId === 'global' ? t('center.scope.global')
|
||||
: projects.find(project => project.id === projectId)?.name ?? '', period: t('supervisor.days', { count: days }) })}</p>
|
||||
: projects.find(project => project.id === projectId)?.name ?? '', period: reviewPeriod.label })}</p>
|
||||
<div>
|
||||
<button type="button" className="secondary-button" autoFocus onClick={() => setConfirmReanalyze(false)}>{t('supervisor.cancel')}</button>
|
||||
<button type="button" className="primary-button" onClick={() => void run(true)}>{t('supervisor.reanalyzeConfirm')}</button>
|
||||
<button type="button" className="primary-button" disabled={!reviewPeriod.range} onClick={() => void run(true)}>{t('supervisor.reanalyzeConfirm')}</button>
|
||||
</div>
|
||||
</div>}
|
||||
</>
|
||||
|
||||
@@ -120,7 +120,12 @@ export const heartbeat = {
|
||||
newReview: 'New review', summary: 'Work summary', changes: 'Changes', runningHint: 'A new review is running. The existing result remains available to read.',
|
||||
empty: 'No successful review yet', emptyHint: 'You can review current progress manually even without an heartbeat plan.', run: 'Review', running: 'Reviewing…',
|
||||
more: 'More review actions', reanalyze: 'Reanalyze…', reanalyzeTitle: 'Reanalyze this period?', reanalyzeConfirm: 'Reanalyze',
|
||||
reanalyzeHint: 'All content in {{scope}} from the last {{period}} will be analyzed again, which may use more model tokens. Confirmed content and heartbeat progress are not affected.', retryRun: 'Retry review', dismiss: 'Dismiss',
|
||||
reanalyzeHint: 'All content in {{scope}} for {{period}} will be analyzed again, which may use more model tokens. Confirmed content and heartbeat progress are not affected.', retryRun: 'Retry review', dismiss: 'Dismiss',
|
||||
customDays: 'Custom days', dateRange: 'Date range', dayCount: 'Number of days', startDate: 'Start date', endDate: 'End date',
|
||||
recentDays: 'the last {{count}} days', dateRangeLabel: '{{from}} through {{to}} (local dates, through now if ending today)',
|
||||
customDaysHelp: 'Counts back from now in 24-hour days.', dateRangeHelp: 'Local dates, including the end day. If ending today, includes content through now.',
|
||||
invalidDays: 'Enter a positive whole number of days that produces a date in year 1 or later.',
|
||||
invalidDates: 'Enter valid start and end dates.', futureDates: 'Dates cannot be in the future.', reversedDates: 'Start date must be on or before end date.',
|
||||
graphScope: 'Graph scope', graphEmpty: 'No story events in this scope', legend: 'Solid lines show event impact on entities; dashed lines show entity relations. Time runs counter-clockwise with a visible gap.', start: 'Start', end: 'End',
|
||||
listTabs: { event: 'Events', entity: 'Entities', relation: 'Relations', story: 'Stories', experience: 'Experiences' },
|
||||
experiences: {
|
||||
|
||||
@@ -117,7 +117,12 @@ export const heartbeat = {
|
||||
newReview: '新回顾', summary: '工作总结', changes: '本次变化', runningHint: '新回顾正在整理,已有结果仍可阅读。',
|
||||
empty: '还没有成功回顾', emptyHint: '即使没有心跳计划,也可以手动回顾当前进展。', run: '回顾', running: '回顾整理中…',
|
||||
more: '更多回顾操作', reanalyze: '重新整理…', reanalyzeTitle: '重新整理这段时间?', reanalyzeConfirm: '重新整理',
|
||||
reanalyzeHint: '将重新分析{{scope}}最近 {{period}}的全部内容,可能产生较多模型用量。已确认的内容和心跳处理进度不受影响。', retryRun: '重试回顾', dismiss: '关闭提示',
|
||||
reanalyzeHint: '将重新分析{{scope}}在{{period}}内的全部内容,可能产生较多模型用量。已确认的内容和心跳处理进度不受影响。', retryRun: '重试回顾', dismiss: '关闭提示',
|
||||
customDays: '自定义天数', dateRange: '指定日期', dayCount: '天数', startDate: '开始日期', endDate: '结束日期',
|
||||
recentDays: '最近 {{count}} 天', dateRangeLabel: '{{from}} 至 {{to}}(本地日期,结束日期为今天时截至当前)',
|
||||
customDaysHelp: '从当前时刻向前计算,每天按 24 小时计。', dateRangeHelp: '按本地日期计算,包含结束日期当天;结束日期为今天时截至当前时刻。',
|
||||
invalidDays: '请输入正整数天数,且起始时间不能早于公元 1 年。',
|
||||
invalidDates: '请输入有效的开始和结束日期。', futureDates: '日期不能晚于今天。', reversedDates: '开始日期不能晚于结束日期。',
|
||||
graphScope: '图谱范围', graphEmpty: '当前范围没有故事线事件', legend: '实线表示事件影响实体,虚线表示实体关系。时间轴逆时针排列,起止之间保留缺口。', start: '起点', end: '终点',
|
||||
listTabs: { event: '事件', entity: '实体', relation: '关系', story: '故事', experience: '经验' },
|
||||
experiences: {
|
||||
|
||||
@@ -362,6 +362,31 @@
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.supervisor-workspace__date-range {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.supervisor-workspace__toolbar .field-control {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.supervisor-workspace__period-field {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.supervisor-workspace__period-help {
|
||||
flex-basis: 100%;
|
||||
grid-column: 1 / -1;
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.supervisor-workspace__period-help[role='alert'] {
|
||||
color: var(--danger);
|
||||
}
|
||||
.supervisor-workspace__history-menu [role="menuitemradio"] > span {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
Reference in New Issue
Block a user