/** Execution status view (active Tickets and pause control) and Execution settings group (Executor capacity and workspace environment). */ import { useRef, useState } from 'react' import { TICKET_FILTERS, type TicketFilter } from '../app/api.ts' import { LIMITS } from '../limits.ts' import { usePoll, type SettingsView, type SourceView, type StateView } from './api.ts' import { DisclosureRow, Icon, InfoTip, Input, Pill } from './controls.tsx' import { SaveBar } from './draft.tsx' import { usePanel } from './provider.tsx' import { TicketLine } from './rows.tsx' import { ConfigHistory, Field, Fields, ModelField, numberOf, useBlockDraft, useSettings } from './settings-kit.tsx' import { localized } from './text.ts' import { ConfirmButton, Hint, LabelValue, LoadMore, LoadState, Section, StatusHead } from './widgets.tsx' export function ExecutionStatus({ filter: current, state, reload }: { filter: TicketFilter; state: StateView; reload: () => Promise }) { const { t, call, navigate } = usePanel() const [limit, setLimit] = useState(20) const { data, error, reload: reloadTickets } = usePoll(() => call('GET tickets', { query: { state: current, limit } }), [current, limit]) const { paused, load: { maxWorkers } } = state // Pausing uses its own block revision so toggling pause does not invalidate an open Execution settings draft. const pause = async () => { await call('POST executor/pause', { body: { paused: !paused } }); await reload() } const status = state.setup.problem ? { tone: 'warning', label: t('executorWaitingSetup') } : paused ? { tone: 'warning', label: t('processingPaused') } : { tone: 'ongoing', label: t('executorRunning') } const action = paused ? 'resumeProcessing' : 'pauseProcessing' return <>
{status.label} {!state.setup.problem && <> }
{TICKET_FILTERS.map(value => { setLimit(20); navigate({ page: 'execution', query: value === 'open' ? {} : { filter: value } }, true) }}>{t(`ticketFilter.${value}`)})}
{current === 'idle' && {t('idleHint')}} {data && (data.items.length ?
    {data.items.map(ticket => ticket.stage === 'person' ? : )}
: {t('none')})} {data?.next_offset != null && setLimit(limit + 20)} />} } const firstSentence = (text: string): string => /^[^。;;.]*(?:[。;;]|\.(?=\s|$))?/.exec(text)?.[0] ?? text function EnvironmentVariables({ groups, sources, onPick }: { groups: SettingsView['environment']['variables']; sources: SourceView[]; onPick: (key: string) => void }) { const { t } = usePanel() const [open, setOpen] = useState(false) const owner = (id: string) => id === 'executor' ? t('term.executor') : localized(t, sources.find(source => source.id === id)?.title ?? id) const count = groups.reduce((sum, group) => sum + group.variables.length, 0) return } title={<>{t('environmentVariables')} {count}} open={open} onToggle={() => setOpen(!open)}>
{t('environmentPick')} {groups.flatMap(group => group.variables.map(variable => ))}
{t('environmentKey')}{t('environmentDescription')}{t('environmentOwner')}
{firstSentence(localized(t, variable.description))} {owner(group.owner)}{!group.enabled && · {t('sourceOff')}}
} export function ExecutionSettings() { const { t } = usePanel() const { blocks, environment, sources } = useSettings().settings // Keep raw user text (such as separators between blocked commands) in the draft and parse only on save. const executor = blocks.executor.value const saved = { ...executor, maxWorkers: String(executor.maxWorkers), blockedCommands: executor.blockedCommands.join(' ') } const draft = useBlockDraft('executor', saved, blocks.executor.revision, value => ({ ...value, maxWorkers: numberOf(value.maxWorkers), blockedCommands: value.blockedCommands.split(/[\s,]+/).filter(Boolean) })) const value = draft.value const edit = (next: Partial) => draft.edit({ ...value, ...next }) const prepare = useRef(null) const insert = (key: string) => { const field = prepare.current const at = field?.selectionStart ?? value.prepare.length, to = field?.selectionEnd ?? at edit({ prepare: `${value.prepare.slice(0, at)}"$${key}"${value.prepare.slice(to)}` }) requestAnimationFrame(() => { field?.focus(); const caret = at + key.length + 3; field?.setSelectionRange(caret, caret) }) } return
{ void draft.submit(event) }}>

{t('term.executor')}

edit({ model })} />
edit({ maxWorkers: event.target.value })} />

{t('term.environment')}

edit({ directory: event.target.value })} />