feat(schedule): add durable after reminders

This commit is contained in:
pku-xht
2026-08-05 19:00:02 +08:00
committed by Tianyi Cui
parent a229b42e24
commit f7e7851e3f
102 changed files with 2619 additions and 122 deletions
@@ -0,0 +1,63 @@
.root {
display: grid;
min-width: 0;
gap: 8px;
padding: 12px 14px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 10px;
background: var(--dsw-alias-bg-module-platform);
color: var(--dsw-alias-label-primary);
}
.header {
display: flex;
min-width: 0;
align-items: center;
gap: 7px;
}
.icon {
display: inline-flex;
flex: none;
color: var(--dsw-alias-brand-text);
}
.title {
min-width: 0;
flex: 1;
font: 600 13px/18px var(--ds-font-family);
}
.delivery {
flex: none;
color: var(--dsw-alias-label-tertiary);
font: 400 11px/16px var(--ds-font-family);
}
.prompt {
margin: 0;
color: var(--dsw-alias-label-primary);
font: 400 14px/21px var(--ds-font-family);
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.meta {
display: flex;
min-width: 0;
flex-wrap: wrap;
gap: 4px 12px;
color: var(--dsw-alias-label-tertiary);
font: 400 11px/16px var(--ds-font-family);
}
.id {
font-family: var(--ds-font-family-code);
overflow-wrap: anywhere;
}
.invalid {
margin: 0;
color: var(--dsw-alias-label-secondary);
font: 400 13px/18px var(--ds-font-family);
}
@@ -0,0 +1,61 @@
import { IconSparkle16 } from '@deepseek-ai/dsh-client-ui-primitives'
import type { EventRowProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
import css from './ReminderRow.module.css'
interface ReminderPresentation {
scheduleId: string
prompt: string
occurrenceAt: string
deliveryMode: 'session-local'
}
/** Full Schedule row props: event owner/runtime share plus the locale seat. */
export type ReminderRowProps = EventRowProps & PropsLocale<'schedule'>
/** Narrow the domain-owned JSON sidecar without trusting its unknown carrier type. */
function reminderPresentation(value: unknown): ReminderPresentation | null {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
const record = value as Record<string, unknown>
if (typeof record['scheduleId'] !== 'string' || record['scheduleId'].length === 0) return null
if (typeof record['prompt'] !== 'string') return null
if (typeof record['occurrenceAt'] !== 'string' || record['occurrenceAt'].length === 0) return null
if (record['deliveryMode'] !== 'session-local') return null
return {
scheduleId: record['scheduleId'],
prompt: record['prompt'],
occurrenceAt: record['occurrenceAt'],
deliveryMode: record['deliveryMode'],
}
}
/**
* Render one durable reminder dispatch carried by the generic event sidecar.
* @param props - Keyed event owner payload and the Schedule translator.
* @returns A visible reminder receipt, or a contained invalid-payload row.
*/
export function ReminderRow({ node, t }: ReminderRowProps) {
const reminder = reminderPresentation(node.view)
return (
<section className={css.root} role="note" data-schedule-reminder>
<header className={css.header}>
<span className={css.icon} aria-hidden><IconSparkle16 size={14} /></span>
<span className={css.title}>{t('reminder.title')}</span>
{reminder !== null && <span className={css.delivery}>{t('reminder.delivery')}</span>}
</header>
{reminder === null
? <p className={css.invalid}>{t('reminder.invalid')} · {node.presentationKey}</p>
: (
<>
<p className={css.prompt}>{reminder.prompt}</p>
<footer className={css.meta}>
<span className={css.id}>{t('reminder.id', { id: reminder.scheduleId })}</span>
<time dateTime={reminder.occurrenceAt}>
{t('reminder.occurrence', { time: reminder.occurrenceAt })}
</time>
</footer>
</>
)}
</section>
)
}
@@ -0,0 +1,38 @@
/** Register the Schedule durable-reminder renderer into the conversation event slot. */
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
import type {} from '@deepseek-ai/dsh-client-locale/client'
import { ReminderRow } from './ReminderRow.tsx'
import { en, NS, zh, type ScheduleKey } from './locales.ts'
export type { ReminderRowProps } from './ReminderRow.tsx'
export type { ScheduleKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
/** Copy for durable Schedule reminder receipts. */
schedule: ScheduleKey
}
}
/**
* `conversation` is an ordering edge: its service is published after the chat
* entry has declared `conversation.chat.eventview`.
*/
export const inject = ['slots', 'conversation', 'locale']
/**
* Register bilingual copy and the Schedule reminder keyed row.
* @param ctx - Client root context.
*/
export function apply(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-schedule: dictionaries')
ctx.effect(
() => ctx.slots.register({
name: 'conversation.chat.eventview',
key: 'schedule/reminder',
locale: NS,
}, ReminderRow),
'ui-schedule: reminder row registration',
)
}
@@ -0,0 +1,25 @@
/** `schedule` namespace dictionaries. */
/** Dictionary namespace owned by this plugin. */
export const NS = 'schedule'
/** Simplified Chinese dictionary (the key-set source of truth). */
export const zh = {
'reminder.title': '定时提醒',
'reminder.delivery': '仅在当前会话中交付',
'reminder.invalid': '提醒回执不可用',
'reminder.id': '编号 {id}',
'reminder.occurrence': '触发时间 {time}',
} satisfies Record<string, string>
/** The Schedule namespace key union. */
export type ScheduleKey = keyof typeof zh
/** English dictionary, checked complete against the Chinese key set. */
export const en = {
'reminder.title': 'Scheduled reminder',
'reminder.delivery': 'Delivered in this session only',
'reminder.invalid': 'Reminder receipt unavailable',
'reminder.id': 'ID {id}',
'reminder.occurrence': 'Due at {time}',
} satisfies Record<ScheduleKey, string>
+4
View File
@@ -0,0 +1,4 @@
declare module '*.module.css' {
const classes: Readonly<Record<string, string>>
export default classes
}
+4
View File
@@ -0,0 +1,4 @@
/** Host loader entry for the browser-only Schedule receipt renderer. */
/** Provides no host-side behavior. */
export function apply(): void {}
@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-schedule`.
* @module @deepseek-ai/dsh-client-ui-schedule/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-schedule'
/** Cordis companion plugin name. */
export const name = 'client-ui-schedule-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: the keyed slot registry owns contribution lifecycle,
* and the component has no state outside its immutable owner payload.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns The installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */