Files
OpenMAIC/lib/whiteboard/element-reference.ts
LING DUANandClaude Opus 5.5 d23bc104dc feat(pi): reference a whiteboard element in classroom chat (#1656)
Let a learner pick one element on the classroom whiteboard and ask about
it, alongside the existing PPT element and interactive component
references behind the courseware-reference flag.

- Add a `whiteboard_element` reference: { kind, whiteboardId, elementId }.
  The Host resolves it only from this request's Stage snapshot
  (`stage.whiteboard[0]`), projects the element with the existing slide
  element projection, and passes it through the existing Director ->
  call_agent evidence path. It grants no Spotlight or other tool access.
- Reuse the playback pick overlay inside the whiteboard, scoped to the
  whiteboard DOM so pan/zoom and overlapping elements resolve correctly,
  and keep a highlight on the selected element.
- Share one displayed-whiteboard selector for display, picking, and the
  pre-send check. A runtime (RuntimeStore) whiteboard disables picking;
  runtime-board references are out of scope.
- Before sending, re-check the reference against the snapshot actually
  POSTed; if it is stale, keep the learner's text and ask them to pick
  again. Server rejections carry a `whiteboard_reference_changed` reason.
- Describe whiteboard references correctly next to page-reported state,
  and rename the entry to "Reference content" in all locales.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 11:13:03 +08:00

30 lines
1.2 KiB
TypeScript

import type { Stage, Whiteboard } from '@/lib/types/stage';
import type { WhiteboardElementReference } from '@/lib/types/chat';
/** Use the same authority for displaying and selecting a whiteboard. An empty
* durable board is authoritative too; it must not reveal the legacy board. */
export function getDisplayedWhiteboard(
stage: Stage | null,
projection: { stageId: string; lastSeq: number | null; whiteboard: Whiteboard | null } | null,
) {
const runtimeAuthoritative =
projection != null && projection.stageId === stage?.id && projection.lastSeq !== null;
return {
source: runtimeAuthoritative ? ('runtime_store' as const) : ('stage_snapshot' as const),
whiteboard: runtimeAuthoritative ? projection.whiteboard : stage?.whiteboard?.[0],
};
}
export function isWhiteboardReferenceAvailable(
reference: WhiteboardElementReference,
stage: Stage | null,
projection: Parameters<typeof getDisplayedWhiteboard>[1],
): boolean {
const { source, whiteboard } = getDisplayedWhiteboard(stage, projection);
return (
source === 'stage_snapshot' &&
whiteboard?.id === reference.whiteboardId &&
whiteboard.elements.filter((element) => element.id === reference.elementId).length === 1
);
}