Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions apps/cutroom/src/app/api/walkthroughs/[id]/ask/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ interface PostBody {
question: string;
}

function parseAskEnvelope(stdout: string): { answer: string; citations: string[] } {
const lines = stdout
.trim()
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);

for (let i = lines.length - 1; i >= 0; i--) {
try {
const parsed = JSON.parse(lines[i]) as unknown;
if (
parsed &&
typeof parsed === "object" &&
"answer" in parsed &&
"citations" in parsed
) {
const envelope = parsed as { answer: unknown; citations: unknown };
if (
typeof envelope.answer === "string" &&
Array.isArray(envelope.citations) &&
envelope.citations.every((c) => typeof c === "string")
) {
return {
answer: envelope.answer,
citations: envelope.citations,
};
}
}
} catch {
/* keep scanning older log lines */
}
}

throw new Error("director ask returned no JSON answer envelope");
}

export async function POST(
req: NextRequest,
{ params }: { params: { id: string } },
Expand Down Expand Up @@ -70,14 +106,7 @@ export async function POST(
],
{ cwd: REPO_ROOT, env, timeout: 60_000, maxBuffer: 4 * 1024 * 1024 },
);
// CLI prints a single-line JSON envelope after a logfire status line;
// grab the last JSON object.
const lastBrace = stdout.lastIndexOf("{");
const trailing = lastBrace >= 0 ? stdout.slice(lastBrace) : stdout;
const parsed = JSON.parse(trailing) as {
answer: string;
citations: string[];
};
const parsed = parseAskEnvelope(stdout);
return NextResponse.json({ ok: true, ...parsed });
} catch (err) {
return directorErrorResponse(err, "ask_failed");
Expand Down
3 changes: 2 additions & 1 deletion apps/cutroom/src/app/api/walkthroughs/import/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ export async function POST(req: Request) {
await mkdir(stepsDir, { recursive: true });

const stepIdMap = new Map<string, string>(); // captured id -> stable slug
const usedStepIds = new Set<string>();
payload.steps.forEach((s, i) => {
stepIdMap.set(s.id, uniqueSlug(slugForIndex(i, s), new Set()));
stepIdMap.set(s.id, uniqueSlug(slugForIndex(i, s), usedStepIds));
});

for (const captured of payload.steps) {
Expand Down
18 changes: 15 additions & 3 deletions apps/cutroom/src/lib/walkthrough-mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,22 @@ export async function appendStep(
): Promise<RawStep> {
const raw = await readRaw(id);
const newId = opts.step_id ?? nextStepId(raw.steps);
assertStepId(newId);
const durationMs = opts.duration_ms ?? 5000;
if (!Number.isInteger(durationMs) || durationMs < 500 || durationMs > 30_000) {
throw new Error("duration_ms must be an integer between 500 and 30000");
}
if (raw.steps.some((s) => s.id === newId)) {
return raw.steps.find((s) => s.id === newId)!;
}
const step: RawStep = {
id: newId,
title: opts.title ?? "New step",
narration: opts.narration ?? "Describe what's on screen for this step.",
duration_ms: opts.duration_ms ?? 5000,
duration_ms: durationMs,
actions: [
{ kind: "goto", url: "/" },
{ kind: "wait", ms: opts.duration_ms ?? 4500 },
{ kind: "wait", ms: Math.max(0, durationMs - 500) },
],
};
raw.steps.push(step);
Expand All @@ -141,10 +146,17 @@ export async function reorderSteps(
id: string,
orderedIds: string[],
): Promise<string[]> {
for (const orderedId of orderedIds) {
assertStepId(orderedId);
}
const raw = await readRaw(id);
const have = new Set(raw.steps.map((s) => s.id));
const wanted = new Set(orderedIds);
if (have.size !== wanted.size || [...have].some((s) => !wanted.has(s))) {
if (
have.size !== wanted.size ||
orderedIds.length !== wanted.size ||
[...have].some((s) => !wanted.has(s))
) {
throw new Error(
`reorder ids must match: have [${[...have].sort().join(",")}], wanted [${[...wanted].sort().join(",")}]`,
);
Expand Down