feat(desktop): cron + blueprint recipes in the sidebar nav rail
Adds a 'Scheduled jobs' row to the sidebar's top nav (below Artifacts, watch codicon, wired to the existing nav.cron keybind action) so the cron overlay opens from the same rail as New session / Capabilities / Messaging / Artifacts. Inside the cron overlay, the list rail now also shows the Automation Blueprint recipes below the jobs (same search box filters both). Clicking a recipe opens the create dialog pre-seeded to that blueprint's typed-slot form via a new optional blueprintKey on the create EditorState. Catalog fetch reuses the ['cron-blueprints'] query key, so no extra request. i18n: sidebar.nav.cron added to en/zh/zh-hant/ja (ar already had it).
This commit is contained in:
parent
7fa084f58e
commit
926c8d591a
|
|
@ -127,6 +127,7 @@ import { $sidebarSessionRankIds } from '@/store/sidebar-sort'
|
|||
import {
|
||||
type AppView,
|
||||
ARTIFACTS_ROUTE,
|
||||
CRON_ROUTE,
|
||||
MESSAGING_ROUTE,
|
||||
SIDEBAR_NAV_AREA,
|
||||
type SidebarNavContribution,
|
||||
|
|
@ -203,6 +204,13 @@ const SIDEBAR_NAV: SidebarNavItem[] = [
|
|||
icon: props => <Codicon name="files" {...props} />,
|
||||
route: ARTIFACTS_ROUTE,
|
||||
keybindActionId: 'nav.artifacts'
|
||||
},
|
||||
{
|
||||
id: 'cron',
|
||||
label: '',
|
||||
icon: props => <Codicon name="watch" {...props} />,
|
||||
route: CRON_ROUTE,
|
||||
keybindActionId: 'nav.cron'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -1405,6 +1413,7 @@ export function ChatSidebar({
|
|||
(item.id === 'skills' && currentView === 'skills') ||
|
||||
(item.id === 'messaging' && currentView === 'messaging') ||
|
||||
(item.id === 'artifacts' && currentView === 'artifacts') ||
|
||||
(item.id === 'cron' && currentView === 'cron') ||
|
||||
// Contributed rows light up at their own route.
|
||||
(Boolean(item.route) && pathname === item.route)
|
||||
|
||||
|
|
|
|||
|
|
@ -356,6 +356,23 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
|
|||
[jobs, query]
|
||||
)
|
||||
|
||||
// Blueprint recipes render in the same list rail, below the jobs — clicking
|
||||
// one opens the create dialog pre-seeded to that recipe. Same query key as
|
||||
// the dialog's "Start from" dropdown, so the catalog is fetched once.
|
||||
const blueprintsQuery = useQuery({
|
||||
queryKey: ['cron-blueprints'],
|
||||
queryFn: async () => (await getAutomationBlueprints()).blueprints
|
||||
})
|
||||
|
||||
const visibleBlueprints = useMemo(() => {
|
||||
const list = blueprintsQuery.data ?? []
|
||||
const needle = query.trim().toLowerCase()
|
||||
|
||||
return needle
|
||||
? list.filter(item => `${item.title} ${item.description}`.toLowerCase().includes(needle))
|
||||
: list
|
||||
}, [blueprintsQuery.data, query])
|
||||
|
||||
// Detail always reflects a concrete job: the explicitly selected one, else the
|
||||
// first visible row, so the right pane is never empty while jobs exist.
|
||||
const selectedJob = useMemo(
|
||||
|
|
@ -480,7 +497,7 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
|
|||
|
||||
{loading && jobs.length === 0 ? (
|
||||
<PageLoader label={c.loading} />
|
||||
) : totalCount === 0 ? (
|
||||
) : totalCount === 0 && visibleBlueprints.length === 0 ? (
|
||||
<PanelEmpty
|
||||
action={
|
||||
<Button onClick={() => setEditor({ mode: 'create' })} size="sm">
|
||||
|
|
@ -521,6 +538,21 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
|
|||
<p className="px-2 py-4 text-center text-xs text-muted-foreground">{c.emptyTitleSearch}</p>
|
||||
)}
|
||||
<PanelAddButton label={c.newCron} onClick={() => setEditor({ mode: 'create' })} />
|
||||
{visibleBlueprints.length > 0 && (
|
||||
<>
|
||||
<PanelSectionLabel className="mt-3 px-2">{c.blueprints.tab}</PanelSectionLabel>
|
||||
{visibleBlueprints.map(item => (
|
||||
<PanelListRow
|
||||
active={false}
|
||||
icon="rocket"
|
||||
key={item.key}
|
||||
onSelect={() => setEditor({ blueprintKey: item.key, mode: 'create' })}
|
||||
rowKey={`blueprint-${item.key}`}
|
||||
title={item.title}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</PanelList>
|
||||
|
||||
{selectedJob ? (
|
||||
|
|
@ -919,10 +951,10 @@ function CronEditorDialog({
|
|||
setDeliver(initial ? jobDeliver(initial) : DEFAULT_DELIVER)
|
||||
setModelChoice(initial && jobModel(initial) ? `${jobProvider(initial)}:${jobModel(initial)}` : MODEL_DEFAULT_VALUE)
|
||||
setSlotValues({})
|
||||
setTemplateChoice(CUSTOM_TEMPLATE)
|
||||
setTemplateChoice(editor.mode === 'create' ? (editor.blueprintKey ?? CUSTOM_TEMPLATE) : CUSTOM_TEMPLATE)
|
||||
setError(null)
|
||||
setSaving(false)
|
||||
}, [initial, open])
|
||||
}, [editor, initial, open])
|
||||
|
||||
// Seed the typed slots with the blueprint's defaults whenever a blueprint is
|
||||
// picked from "Start from" (and reset them when switching back to Custom).
|
||||
|
|
@ -1232,7 +1264,12 @@ function CronEditorDialog({
|
|||
)
|
||||
}
|
||||
|
||||
type EditorState = { job: CronJob; mode: 'edit' } | { mode: 'closed' } | { mode: 'create' }
|
||||
type EditorState =
|
||||
| { job: CronJob; mode: 'edit' }
|
||||
| { mode: 'closed' }
|
||||
// `blueprintKey` pre-selects a blueprint in the create dialog's "Start from"
|
||||
// dropdown (set when a recipe row in the list rail is clicked).
|
||||
| { blueprintKey?: string; mode: 'create' }
|
||||
|
||||
interface EditorValues {
|
||||
deliver: string
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export type CommandDispatchResponse =
|
|||
| SendCommandDispatchResponse
|
||||
| PrefillCommandDispatchResponse
|
||||
|
||||
export type SidebarNavId = 'artifacts' | 'command-center' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
export type SidebarNavId = 'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
|
||||
export interface SidebarNavItem {
|
||||
/** Built-in view id, or a contributed row's namespaced contribution id. */
|
||||
|
|
|
|||
|
|
@ -1865,7 +1865,8 @@ export const en: Translations = {
|
|||
'new-session': 'New session',
|
||||
skills: 'Capabilities',
|
||||
messaging: 'Messaging',
|
||||
artifacts: 'Artifacts'
|
||||
artifacts: 'Artifacts',
|
||||
cron: 'Scheduled jobs'
|
||||
},
|
||||
searchAria: 'Search sessions',
|
||||
searchPlaceholder: 'Search sessions…',
|
||||
|
|
|
|||
|
|
@ -1689,7 +1689,8 @@ export const ja = defineLocale({
|
|||
'new-session': '新しいセッション',
|
||||
skills: 'スキルとツール',
|
||||
messaging: 'メッセージング',
|
||||
artifacts: 'アーティファクト'
|
||||
artifacts: 'アーティファクト',
|
||||
cron: 'スケジュール済みジョブ'
|
||||
},
|
||||
searchAria: 'セッションを検索',
|
||||
searchPlaceholder: 'セッションを検索…',
|
||||
|
|
|
|||
|
|
@ -1634,7 +1634,8 @@ export const zhHant = defineLocale({
|
|||
'new-session': '新工作階段',
|
||||
skills: '技能與工具',
|
||||
messaging: '訊息平台',
|
||||
artifacts: '成品'
|
||||
artifacts: '成品',
|
||||
cron: '排程工作'
|
||||
},
|
||||
searchAria: '搜尋工作階段',
|
||||
searchPlaceholder: '搜尋工作階段…',
|
||||
|
|
|
|||
|
|
@ -2056,7 +2056,8 @@ export const zh: Translations = {
|
|||
'new-session': '新建会话',
|
||||
skills: '技能与工具',
|
||||
messaging: '消息平台',
|
||||
artifacts: '产物'
|
||||
artifacts: '产物',
|
||||
cron: '定时任务'
|
||||
},
|
||||
searchAria: '搜索会话',
|
||||
searchPlaceholder: '搜索会话…',
|
||||
|
|
|
|||
Loading…
Reference in New Issue