MCPcopy
hub / github.com/nowork-studio/NotFair / LiveTranscript

Function LiveTranscript

notfair-cmo/src/components/live-transcript.tsx:148–786  ·  view source on GitHub ↗
({
  projectSlug,
  agentSlug,
  agentDisplayName,
  threadId,
  sessionKey,
  initialEvents,
  initialByteOffset,
  composerDisabled = false,
  blockedReason,
  onPolled,
  autoKickoff = false,
  kickoffMessage,
  taskId,
  mcpCatalog,
}: Props)

Source from the content-addressed store, hash-verified

146const KICKOFF_FIRED = new Set<string>();
147
148export function LiveTranscript({
149 projectSlug,
150 agentSlug,
151 agentDisplayName,
152 threadId,
153 sessionKey,
154 initialEvents,
155 initialByteOffset,
156 composerDisabled = false,
157 blockedReason,
158 onPolled,
159 autoKickoff = false,
160 kickoffMessage,
161 taskId,
162 mcpCatalog,
163}: Props) {
164 const router = useRouter();
165 const [events, setEvents] = useState<TranscriptEvent[]>(initialEvents);
166 const [byteOffset, setByteOffset] = useState(initialByteOffset);
167 const [input, setInput] = useState("");
168 const [sendingChat, setSendingChat] = useState(false);
169 /**
170 * Wallclock when the current turn started, in ms. Drives the "elapsed"
171 * counter in WorkingStatus during the gap between hitting send and the
172 * agent's first transcript event landing — without this, elapsed reflects
173 * the *previous* turn's last event timestamp (often minutes/hours stale).
174 * Cleared once the new user_message lands and rendering catches up.
175 */
176 const [turnStartedAt, setTurnStartedAt] = useState<number | null>(null);
177 const [stopPolling, setStopPolling] = useState(false);
178 const [slashIndex, setSlashIndex] = useState(0);
179 const textareaRef = useRef<HTMLTextAreaElement>(null);
180
181 // Slash command autocomplete: open while input starts with "/" and the
182 // user is still composing the command name (no space yet). After they
183 // pick or type a space, the popover closes.
184 const slashQuery = input.startsWith("/") && !input.includes(" ") ? input : null;
185 const slashOpen = slashQuery !== null && !sendingChat && !composerDisabled;
186 const slashMatches = useMemo<SlashCommand[]>(
187 () => (slashOpen ? filterSlashCommands(slashQuery!) : []),
188 [slashOpen, slashQuery],
189 );
190 const safeSlashIndex =
191 slashMatches.length === 0
192 ? 0
193 : Math.min(slashIndex, slashMatches.length - 1);
194
195 function insertSlashCommand(cmd: SlashCommand) {
196 // Catalog `name` is the command without the leading slash ("new", "clear").
197 // Insert ends with a trailing space so the popover closes — a second
198 // Enter then submits.
199 const insert = cmd.insert ?? `/${cmd.name} `;
200 setInput(insert);
201 setSlashIndex(0);
202 requestAnimationFrame(() => {
203 const ta = textareaRef.current;
204 if (ta) {
205 ta.focus();

Callers

nothing calls this directly

Calls 13

filterSlashCommandsFunction · 0.90
parseSlashMessageFunction · 0.90
executeLocalSlashCommandFunction · 0.90
projectHrefFunction · 0.90
fetchFunction · 0.85
eventSignatureFunction · 0.85
handleSseEventFunction · 0.85
upsertToolEntryFunction · 0.85
collapseEventsFunction · 0.85
insertSlashCommandFunction · 0.85
logFunction · 0.70
sendFunction · 0.50

Tested by

no test coverage detected