diff --git a/src/features/assistant/director/DirectorPage.tsx b/src/features/assistant/director/DirectorPage.tsx index 014996b..85c9598 100644 --- a/src/features/assistant/director/DirectorPage.tsx +++ b/src/features/assistant/director/DirectorPage.tsx @@ -21,6 +21,7 @@ function Director({ campaign }: { campaign: Campaign }) { useDirectorSession(campaign); const characters = useCharacters(campaign.id); const setConfig = useDirectorStore((s) => s.setConfig); + const shareWithPlayers = useDirectorStore((s) => s.shareWithPlayers); const entries = session?.transcript ?? []; const started = entries.length > 0; @@ -89,8 +90,10 @@ function Director({ campaign }: { campaign: Campaign }) { persona={persona} controlledCharacterId={controlledCharacterId ?? ''} characters={characters} + shareWithPlayers={shareWithPlayers} onPersona={(p) => setConfig({ persona: p })} onSeat={(id) => setConfig({ controlledCharacterId: id })} + onShare={(v) => setConfig({ shareWithPlayers: v })} />
{persona === 'dm'
? 'The AI narrates the world and runs NPCs and monsters.'
diff --git a/src/features/assistant/director/useDirectorSession.ts b/src/features/assistant/director/useDirectorSession.ts
index 039ea73..16403be 100644
--- a/src/features/assistant/director/useDirectorSession.ts
+++ b/src/features/assistant/director/useDirectorSession.ts
@@ -16,12 +16,23 @@ import {
type RollRequest,
} from '@/lib/assistant/director';
import { complete } from '@/lib/llm/client';
+import { sendChat } from '@/lib/sync/wsSync';
+import { useSessionStore } from '@/stores/sessionStore';
import type { Degree } from '@/lib/dice/check';
import { useLatestAiSession } from './hooks';
import { useDirectorAction, type ApplyResult } from './useDirectorAction';
const nowIso = (): string => new Date().toISOString();
+/** When hosting a live session and sharing is on, push the director's narration to
+ * the players' screens via the table chat channel. A no-op when not hosting. */
+function broadcastNarration(text: string): void {
+ const { role, status } = useSessionStore.getState();
+ if (role === 'gm' && status === 'connected' && useDirectorStore.getState().shareWithPlayers && text.trim()) {
+ sendChat(text.trim());
+ }
+}
+
/**
* Fold older transcript turns into the session's rolling summary when the live
* window has grown past its budget. Uses the LLM when configured, else a
@@ -122,6 +133,7 @@ export function useDirectorSession(campaign: Campaign) {
text: res.turn.narration,
ts: nowIso(),
}]);
+ broadcastNarration(res.turn.speaker ? `${res.turn.speaker}: ${res.turn.narration}` : res.turn.narration);
}
setLastTurn({ rollRequests: res.turn.rollRequests, actions: res.turn.actions, suggestions: res.turn.suggestions });
diff --git a/src/stores/directorStore.ts b/src/stores/directorStore.ts
index ed46136..add6747 100644
--- a/src/stores/directorStore.ts
+++ b/src/stores/directorStore.ts
@@ -11,7 +11,9 @@ interface DirectorState {
narrationStyle: string;
/** how many recent transcript entries to send each turn (token budget) */
windowSize: number;
- setConfig: (patch: Partial