newpr 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -103
- package/package.json +2 -2
- package/src/analyzer/pipeline.ts +1 -4
- package/src/cli/args.ts +1 -1
- package/src/cli/index.ts +2 -1
- package/src/github/fetch-pr.ts +1 -0
- package/src/history/store.ts +25 -1
- package/src/llm/prompts.ts +82 -27
- package/src/llm/slides.ts +381 -0
- package/src/types/config.ts +1 -1
- package/src/types/github.ts +1 -0
- package/src/types/output.ts +26 -0
- package/src/version.ts +23 -0
- package/src/web/client/App.tsx +51 -1
- package/src/web/client/components/AppShell.tsx +173 -45
- package/src/web/client/components/ChatSection.tsx +76 -185
- package/src/web/client/components/DetailPane.tsx +1 -0
- package/src/web/client/components/DiffViewer.tsx +200 -4
- package/src/web/client/components/InputScreen.tsx +3 -0
- package/src/web/client/components/Markdown.tsx +66 -16
- package/src/web/client/components/ResultsScreen.tsx +32 -2
- package/src/web/client/components/SettingsPanel.tsx +1 -1
- package/src/web/client/hooks/useBackgroundAnalyses.ts +152 -0
- package/src/web/client/hooks/useChatStore.ts +247 -0
- package/src/web/client/hooks/useFeatures.ts +2 -1
- package/src/web/client/hooks/useOutdatedCheck.ts +41 -0
- package/src/web/client/lib/notify.ts +21 -0
- package/src/web/client/panels/SlidesPanel.tsx +316 -0
- package/src/web/index.html +1 -0
- package/src/web/server/routes.ts +226 -4
- package/src/web/server/session-manager.ts +34 -0
- package/src/web/server.ts +20 -1
- package/src/web/styles/built.css +1 -1
- package/src/workspace/explore.ts +39 -6
- package/src/workspace/types.ts +1 -0
package/src/web/server/routes.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { NewprConfig } from "../../types/config.ts";
|
|
2
2
|
import type { NewprOutput, ChatMessage, ChatToolCall, ChatSegment } from "../../types/output.ts";
|
|
3
3
|
import { DEFAULT_CONFIG } from "../../types/config.ts";
|
|
4
|
-
import { listSessions, loadSession, loadSinglePatch, savePatchesSidecar, loadCommentsSidecar, saveCommentsSidecar, loadChatSidecar, saveChatSidecar, loadPatchesSidecar, saveCartoonSidecar, loadCartoonSidecar } from "../../history/store.ts";
|
|
4
|
+
import { listSessions, loadSession, loadSinglePatch, savePatchesSidecar, loadCommentsSidecar, saveCommentsSidecar, loadChatSidecar, saveChatSidecar, loadPatchesSidecar, saveCartoonSidecar, loadCartoonSidecar, saveSlidesSidecar, loadSlidesSidecar } from "../../history/store.ts";
|
|
5
5
|
import type { DiffComment } from "../../types/output.ts";
|
|
6
6
|
import { fetchPrDiff } from "../../github/fetch-diff.ts";
|
|
7
7
|
import { fetchPrBody, fetchPrComments } from "../../github/fetch-pr.ts";
|
|
8
8
|
import { parseDiff } from "../../diff/parser.ts";
|
|
9
9
|
import { parsePrInput } from "../../github/parse-pr.ts";
|
|
10
10
|
import { writeStoredConfig, type StoredConfig } from "../../config/store.ts";
|
|
11
|
-
import { startAnalysis, getSession, cancelAnalysis, subscribe } from "./session-manager.ts";
|
|
11
|
+
import { startAnalysis, getSession, cancelAnalysis, subscribe, listActiveSessions } from "./session-manager.ts";
|
|
12
12
|
import { generateCartoon } from "../../llm/cartoon.ts";
|
|
13
|
+
import { generateSlides } from "../../llm/slides.ts";
|
|
13
14
|
import { chatWithTools, type ChatTool, type ChatStreamEvent } from "../../llm/client.ts";
|
|
14
15
|
import { detectAgents, runAgent } from "../../workspace/agent.ts";
|
|
15
16
|
import { randomBytes } from "node:crypto";
|
|
@@ -70,6 +71,16 @@ export function createRoutes(token: string, config: NewprConfig, options: RouteO
|
|
|
70
71
|
return { login: "anonymous" };
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
interface SlideJob {
|
|
75
|
+
status: "running" | "done" | "error";
|
|
76
|
+
message: string;
|
|
77
|
+
current: number;
|
|
78
|
+
total: number;
|
|
79
|
+
plan?: { stylePrompt: string; slides: Array<{ index: number; title: string; contentPrompt: string }> };
|
|
80
|
+
imagePrompts?: Array<{ index: number; prompt: string }>;
|
|
81
|
+
}
|
|
82
|
+
const slideJobs = new Map<string, SlideJob>();
|
|
83
|
+
|
|
73
84
|
function buildChatSystemPrompt(data: NewprOutput): string {
|
|
74
85
|
const fileSummaries = data.files
|
|
75
86
|
.map((f) => `- ${f.path} (${f.status}, +${f.additions}/-${f.deletions}): ${f.summary}`)
|
|
@@ -182,6 +193,14 @@ $$
|
|
|
182
193
|
parameters: { type: "object", properties: {} },
|
|
183
194
|
},
|
|
184
195
|
},
|
|
196
|
+
{
|
|
197
|
+
type: "function",
|
|
198
|
+
function: {
|
|
199
|
+
name: "run_react_doctor",
|
|
200
|
+
description: "Run react-doctor on the PR's codebase to get a React code quality score (0-100) and diagnostics for security, performance, correctness, and architecture issues. Only useful for React/JSX/TSX projects.",
|
|
201
|
+
parameters: { type: "object", properties: {} },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
185
204
|
{
|
|
186
205
|
type: "function",
|
|
187
206
|
function: {
|
|
@@ -215,7 +234,7 @@ $$
|
|
|
215
234
|
|
|
216
235
|
return {
|
|
217
236
|
"POST /api/analysis": async (req: Request) => {
|
|
218
|
-
const body = await req.json() as { pr: string };
|
|
237
|
+
const body = await req.json() as { pr: string; reuseSessionId?: string };
|
|
219
238
|
if (!body.pr) return json({ error: "Missing 'pr' field" }, 400);
|
|
220
239
|
|
|
221
240
|
const result = startAnalysis(body.pr, token, config);
|
|
@@ -223,6 +242,7 @@ $$
|
|
|
223
242
|
|
|
224
243
|
return json({
|
|
225
244
|
sessionId: result.sessionId,
|
|
245
|
+
reuseSessionId: body.reuseSessionId,
|
|
226
246
|
eventsUrl: `/api/analysis/${result.sessionId}/events`,
|
|
227
247
|
});
|
|
228
248
|
},
|
|
@@ -526,7 +546,8 @@ $$
|
|
|
526
546
|
},
|
|
527
547
|
|
|
528
548
|
"GET /api/features": () => {
|
|
529
|
-
|
|
549
|
+
const { getVersion } = require("../../version.ts");
|
|
550
|
+
return json({ cartoon: !!options.cartoon, version: getVersion() });
|
|
530
551
|
},
|
|
531
552
|
|
|
532
553
|
"POST /api/review": async (req: Request) => {
|
|
@@ -564,6 +585,10 @@ $$
|
|
|
564
585
|
return json(options.preflight ?? null);
|
|
565
586
|
},
|
|
566
587
|
|
|
588
|
+
"GET /api/active-analyses": () => {
|
|
589
|
+
return json(listActiveSessions());
|
|
590
|
+
},
|
|
591
|
+
|
|
567
592
|
"GET /api/sessions/:id/comments": async (req: Request) => {
|
|
568
593
|
const url = new URL(req.url);
|
|
569
594
|
const segments = url.pathname.split("/");
|
|
@@ -713,6 +738,107 @@ $$
|
|
|
713
738
|
return json({ ok: true });
|
|
714
739
|
},
|
|
715
740
|
|
|
741
|
+
"POST /api/sessions/:id/ask-inline": async (req: Request) => {
|
|
742
|
+
const url = new URL(req.url);
|
|
743
|
+
const segments = url.pathname.split("/");
|
|
744
|
+
const sessionId = segments[3]!;
|
|
745
|
+
|
|
746
|
+
if (!config.openrouter_api_key) {
|
|
747
|
+
return json({ error: "OpenRouter API key required" }, 400);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const body = await req.json() as { message: string };
|
|
751
|
+
if (!body.message?.trim()) return json({ error: "Missing message" }, 400);
|
|
752
|
+
|
|
753
|
+
const sessionData = await loadSession(sessionId);
|
|
754
|
+
if (!sessionData) return json({ error: "Session not found" }, 404);
|
|
755
|
+
|
|
756
|
+
const systemPrompt = buildChatSystemPrompt(sessionData);
|
|
757
|
+
const apiMessages = [
|
|
758
|
+
{ role: "system" as const, content: systemPrompt },
|
|
759
|
+
{ role: "user" as const, content: body.message.trim() },
|
|
760
|
+
];
|
|
761
|
+
|
|
762
|
+
const encoder = new TextEncoder();
|
|
763
|
+
const stream = new ReadableStream({
|
|
764
|
+
async start(controller) {
|
|
765
|
+
const send = (eventType: string, data: string) => {
|
|
766
|
+
controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${data}\n\n`));
|
|
767
|
+
};
|
|
768
|
+
try {
|
|
769
|
+
await chatWithTools(
|
|
770
|
+
{ api_key: config.openrouter_api_key, model: config.model, timeout: config.timeout },
|
|
771
|
+
apiMessages as Parameters<typeof chatWithTools>[1],
|
|
772
|
+
buildChatTools(),
|
|
773
|
+
async (name: string, args: Record<string, unknown>): Promise<string> => {
|
|
774
|
+
if (name === "get_file_diff") {
|
|
775
|
+
const filePath = args.path as string;
|
|
776
|
+
if (!filePath) return "Error: path required";
|
|
777
|
+
const patches = await loadPatchesSidecar(sessionId);
|
|
778
|
+
if (patches?.[filePath]) return patches[filePath];
|
|
779
|
+
const patch = await loadSinglePatch(sessionId, filePath);
|
|
780
|
+
if (patch) return patch;
|
|
781
|
+
return `File "${filePath}" not found`;
|
|
782
|
+
}
|
|
783
|
+
if (name === "list_files") {
|
|
784
|
+
return sessionData.files.map((f) => `${f.path} (${f.status}): ${f.summary}`).join("\n");
|
|
785
|
+
}
|
|
786
|
+
return `Tool ${name} not available in inline mode`;
|
|
787
|
+
},
|
|
788
|
+
(event: ChatStreamEvent) => {
|
|
789
|
+
if (event.type === "text") send("text", JSON.stringify({ content: event.content }));
|
|
790
|
+
else if (event.type === "error") send("chat_error", JSON.stringify({ message: event.error }));
|
|
791
|
+
else if (event.type === "done") send("done", JSON.stringify({}));
|
|
792
|
+
},
|
|
793
|
+
);
|
|
794
|
+
send("done", JSON.stringify({}));
|
|
795
|
+
} catch (err) {
|
|
796
|
+
send("chat_error", JSON.stringify({ message: err instanceof Error ? err.message : String(err) }));
|
|
797
|
+
} finally {
|
|
798
|
+
controller.close();
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
return new Response(stream, {
|
|
804
|
+
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
|
|
808
|
+
"GET /api/sessions/:id/outdated": async (req: Request) => {
|
|
809
|
+
const url = new URL(req.url);
|
|
810
|
+
const segments = url.pathname.split("/");
|
|
811
|
+
const id = segments[3]!;
|
|
812
|
+
const sessionData = await loadSession(id);
|
|
813
|
+
if (!sessionData) return json({ error: "Session not found" }, 404);
|
|
814
|
+
|
|
815
|
+
const prUrl = sessionData.meta.pr_url;
|
|
816
|
+
const analyzedUpdatedAt = sessionData.meta.pr_updated_at;
|
|
817
|
+
if (!analyzedUpdatedAt) return json({ outdated: false, reason: "no_baseline" });
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
const pr = parsePrInput(prUrl);
|
|
821
|
+
const res = await fetch(
|
|
822
|
+
`https://api.github.com/repos/${pr.owner}/${pr.repo}/pulls/${pr.number}`,
|
|
823
|
+
{ headers: ghHeaders },
|
|
824
|
+
);
|
|
825
|
+
if (!res.ok) return json({ outdated: false, reason: "api_error" });
|
|
826
|
+
const data = await res.json() as { updated_at?: string; title?: string; state?: string; merged?: boolean; draft?: boolean };
|
|
827
|
+
const currentUpdatedAt = data.updated_at ?? "";
|
|
828
|
+
const outdated = currentUpdatedAt !== analyzedUpdatedAt;
|
|
829
|
+
return json({
|
|
830
|
+
outdated,
|
|
831
|
+
analyzed_at: sessionData.meta.analyzed_at,
|
|
832
|
+
analyzed_updated_at: analyzedUpdatedAt,
|
|
833
|
+
current_updated_at: currentUpdatedAt,
|
|
834
|
+
current_title: data.title,
|
|
835
|
+
current_state: data.draft ? "draft" : data.merged ? "merged" : data.state === "closed" ? "closed" : "open",
|
|
836
|
+
});
|
|
837
|
+
} catch {
|
|
838
|
+
return json({ outdated: false, reason: "fetch_error" });
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
|
|
716
842
|
"GET /api/sessions/:id/chat": async (req: Request) => {
|
|
717
843
|
const url = new URL(req.url);
|
|
718
844
|
const segments = url.pathname.split("/");
|
|
@@ -875,6 +1001,32 @@ $$
|
|
|
875
1001
|
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
876
1002
|
}
|
|
877
1003
|
}
|
|
1004
|
+
case "run_react_doctor": {
|
|
1005
|
+
const agents = await detectAgents();
|
|
1006
|
+
if (agents.length > 0) {
|
|
1007
|
+
try {
|
|
1008
|
+
const result = await runAgent(
|
|
1009
|
+
agents[0]!,
|
|
1010
|
+
process.cwd(),
|
|
1011
|
+
"Run react-doctor on this project:\n\nnpx -y react-doctor@latest . --verbose\n\nReturn the FULL output including the score and all diagnostics.",
|
|
1012
|
+
{ timeout: 60_000 },
|
|
1013
|
+
);
|
|
1014
|
+
if (result.answer.trim()) return result.answer;
|
|
1015
|
+
} catch {}
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
const proc = Bun.spawn(["npx", "-y", "react-doctor@latest", ".", "--verbose"], {
|
|
1019
|
+
cwd: process.cwd(),
|
|
1020
|
+
stdout: "pipe",
|
|
1021
|
+
stderr: "pipe",
|
|
1022
|
+
});
|
|
1023
|
+
const output = await new Response(proc.stdout).text();
|
|
1024
|
+
const stderr = await new Response(proc.stderr).text();
|
|
1025
|
+
return output.trim() || stderr.trim() || "react-doctor produced no output";
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
return `Error running react-doctor: ${err instanceof Error ? err.message : String(err)}`;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
878
1030
|
case "web_search": {
|
|
879
1031
|
const query = args.query as string;
|
|
880
1032
|
if (!query) return "Error: query argument required";
|
|
@@ -1088,5 +1240,75 @@ $$
|
|
|
1088
1240
|
return json({ error: msg }, 500);
|
|
1089
1241
|
}
|
|
1090
1242
|
},
|
|
1243
|
+
"GET /api/sessions/:id/slides": async (req: Request) => {
|
|
1244
|
+
const url = new URL(req.url);
|
|
1245
|
+
const segments = url.pathname.split("/");
|
|
1246
|
+
const id = segments[3]!;
|
|
1247
|
+
const deck = await loadSlidesSidecar(id);
|
|
1248
|
+
if (!deck) return json(null);
|
|
1249
|
+
return json(deck);
|
|
1250
|
+
},
|
|
1251
|
+
|
|
1252
|
+
"POST /api/slides": async (req: Request) => {
|
|
1253
|
+
if (!config.openrouter_api_key) return json({ error: "OpenRouter API key required" }, 400);
|
|
1254
|
+
|
|
1255
|
+
const body = await req.json() as { sessionId?: string; language?: string; resume?: boolean };
|
|
1256
|
+
const sessionId = body.sessionId;
|
|
1257
|
+
if (!sessionId) return json({ error: "Missing sessionId" }, 400);
|
|
1258
|
+
|
|
1259
|
+
const data = await loadSession(sessionId);
|
|
1260
|
+
if (!data) return json({ error: "Session not found" }, 404);
|
|
1261
|
+
|
|
1262
|
+
if (slideJobs.has(sessionId) && slideJobs.get(sessionId)!.status === "running") {
|
|
1263
|
+
return json({ status: "already_running" });
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const existingDeck = body.resume ? await loadSlidesSidecar(sessionId) : null;
|
|
1267
|
+
const job: SlideJob = { status: "running", message: "Planning slide deck...", current: 0, total: 0 };
|
|
1268
|
+
slideJobs.set(sessionId, job);
|
|
1269
|
+
|
|
1270
|
+
(async () => {
|
|
1271
|
+
try {
|
|
1272
|
+
const deck = await generateSlides(
|
|
1273
|
+
config.openrouter_api_key,
|
|
1274
|
+
data,
|
|
1275
|
+
config.model,
|
|
1276
|
+
body.language ?? config.language,
|
|
1277
|
+
(msg, current, total) => {
|
|
1278
|
+
job.message = msg;
|
|
1279
|
+
job.current = current;
|
|
1280
|
+
job.total = total;
|
|
1281
|
+
},
|
|
1282
|
+
existingDeck,
|
|
1283
|
+
(plan, prompts) => {
|
|
1284
|
+
job.plan = plan;
|
|
1285
|
+
job.imagePrompts = prompts;
|
|
1286
|
+
},
|
|
1287
|
+
(partialDeck) => {
|
|
1288
|
+
saveSlidesSidecar(sessionId, partialDeck).catch(() => {});
|
|
1289
|
+
},
|
|
1290
|
+
);
|
|
1291
|
+
await saveSlidesSidecar(sessionId, deck);
|
|
1292
|
+
job.status = "done";
|
|
1293
|
+
job.message = `Generated ${deck.slides.length} slides`;
|
|
1294
|
+
job.total = deck.slides.length;
|
|
1295
|
+
job.current = deck.slides.length;
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
job.status = "error";
|
|
1298
|
+
job.message = err instanceof Error ? err.message : String(err);
|
|
1299
|
+
}
|
|
1300
|
+
})();
|
|
1301
|
+
|
|
1302
|
+
return json({ status: "started" });
|
|
1303
|
+
},
|
|
1304
|
+
|
|
1305
|
+
"GET /api/slides/status": async (req: Request) => {
|
|
1306
|
+
const url = new URL(req.url);
|
|
1307
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
1308
|
+
if (!sessionId) return json({ error: "Missing sessionId" }, 400);
|
|
1309
|
+
const job = slideJobs.get(sessionId);
|
|
1310
|
+
if (!job) return json({ status: "idle" });
|
|
1311
|
+
return json(job);
|
|
1312
|
+
},
|
|
1091
1313
|
};
|
|
1092
1314
|
}
|
|
@@ -9,6 +9,7 @@ type SessionStatus = "running" | "done" | "error" | "canceled";
|
|
|
9
9
|
|
|
10
10
|
interface AnalysisSession {
|
|
11
11
|
id: string;
|
|
12
|
+
prInput: string;
|
|
12
13
|
status: SessionStatus;
|
|
13
14
|
events: ProgressEvent[];
|
|
14
15
|
result?: NewprOutput;
|
|
@@ -16,6 +17,8 @@ interface AnalysisSession {
|
|
|
16
17
|
error?: string;
|
|
17
18
|
startedAt: number;
|
|
18
19
|
finishedAt?: number;
|
|
20
|
+
prTitle?: string;
|
|
21
|
+
prNumber?: number;
|
|
19
22
|
abortController: AbortController;
|
|
20
23
|
subscribers: Set<(event: ProgressEvent | { type: "done" | "error"; data?: string }) => void>;
|
|
21
24
|
}
|
|
@@ -53,6 +56,7 @@ export function startAnalysis(
|
|
|
53
56
|
|
|
54
57
|
const session: AnalysisSession = {
|
|
55
58
|
id,
|
|
59
|
+
prInput,
|
|
56
60
|
status: "running",
|
|
57
61
|
events: [],
|
|
58
62
|
startedAt: Date.now(),
|
|
@@ -84,6 +88,8 @@ async function runPipeline(
|
|
|
84
88
|
onProgress: (event: ProgressEvent) => {
|
|
85
89
|
const stamped = { ...event, timestamp: event.timestamp ?? Date.now() };
|
|
86
90
|
session.events.push(stamped);
|
|
91
|
+
if (event.pr_title) session.prTitle = event.pr_title;
|
|
92
|
+
if (event.pr_number) session.prNumber = event.pr_number;
|
|
87
93
|
for (const sub of session.subscribers) {
|
|
88
94
|
sub(stamped);
|
|
89
95
|
}
|
|
@@ -129,6 +135,34 @@ export function cancelAnalysis(id: string): boolean {
|
|
|
129
135
|
return true;
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
export function listActiveSessions(): Array<{
|
|
139
|
+
id: string;
|
|
140
|
+
prInput: string;
|
|
141
|
+
status: SessionStatus;
|
|
142
|
+
startedAt: number;
|
|
143
|
+
prTitle?: string;
|
|
144
|
+
prNumber?: number;
|
|
145
|
+
lastStage?: string;
|
|
146
|
+
lastMessage?: string;
|
|
147
|
+
}> {
|
|
148
|
+
const result: ReturnType<typeof listActiveSessions> = [];
|
|
149
|
+
for (const s of sessions.values()) {
|
|
150
|
+
if (s.status !== "running") continue;
|
|
151
|
+
const lastEvent = s.events[s.events.length - 1];
|
|
152
|
+
result.push({
|
|
153
|
+
id: s.id,
|
|
154
|
+
prInput: s.prInput,
|
|
155
|
+
status: s.status,
|
|
156
|
+
startedAt: s.startedAt,
|
|
157
|
+
prTitle: s.prTitle,
|
|
158
|
+
prNumber: s.prNumber,
|
|
159
|
+
lastStage: lastEvent?.stage,
|
|
160
|
+
lastMessage: lastEvent?.message,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
132
166
|
export function subscribe(
|
|
133
167
|
id: string,
|
|
134
168
|
callback: (event: ProgressEvent | { type: "done" | "error"; data?: string }) => void,
|
package/src/web/server.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createRoutes } from "./server/routes.ts";
|
|
|
4
4
|
import index from "./index.html";
|
|
5
5
|
|
|
6
6
|
import type { PreflightResult } from "../cli/preflight.ts";
|
|
7
|
+
import { getVersion } from "../version.ts";
|
|
7
8
|
|
|
8
9
|
interface WebServerOptions {
|
|
9
10
|
port: number;
|
|
@@ -101,6 +102,12 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
|
|
|
101
102
|
if (path.match(/^\/api\/sessions\/[^/]+\/comments$/) && req.method === "POST") {
|
|
102
103
|
return routes["POST /api/sessions/:id/comments"](req);
|
|
103
104
|
}
|
|
105
|
+
if (path.match(/^\/api\/sessions\/[^/]+\/ask-inline$/) && req.method === "POST") {
|
|
106
|
+
return routes["POST /api/sessions/:id/ask-inline"](req);
|
|
107
|
+
}
|
|
108
|
+
if (path.match(/^\/api\/sessions\/[^/]+\/outdated$/) && req.method === "GET") {
|
|
109
|
+
return routes["GET /api/sessions/:id/outdated"](req);
|
|
110
|
+
}
|
|
104
111
|
if (path.match(/^\/api\/sessions\/[^/]+\/chat\/undo$/) && req.method === "POST") {
|
|
105
112
|
return routes["POST /api/sessions/:id/chat/undo"](req);
|
|
106
113
|
}
|
|
@@ -125,9 +132,21 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
|
|
|
125
132
|
if (path === "/api/preflight" && req.method === "GET") {
|
|
126
133
|
return routes["GET /api/preflight"]();
|
|
127
134
|
}
|
|
135
|
+
if (path === "/api/active-analyses" && req.method === "GET") {
|
|
136
|
+
return routes["GET /api/active-analyses"]();
|
|
137
|
+
}
|
|
128
138
|
if (path === "/api/cartoon" && req.method === "POST") {
|
|
129
139
|
return routes["POST /api/cartoon"](req);
|
|
130
140
|
}
|
|
141
|
+
if (path.match(/^\/api\/sessions\/[^/]+\/slides$/) && req.method === "GET") {
|
|
142
|
+
return routes["GET /api/sessions/:id/slides"](req);
|
|
143
|
+
}
|
|
144
|
+
if (path === "/api/slides" && req.method === "POST") {
|
|
145
|
+
return routes["POST /api/slides"](req);
|
|
146
|
+
}
|
|
147
|
+
if (path === "/api/slides/status" && req.method === "GET") {
|
|
148
|
+
return routes["GET /api/slides/status"](req);
|
|
149
|
+
}
|
|
131
150
|
if (path === "/api/review" && req.method === "POST") {
|
|
132
151
|
return routes["POST /api/review"](req);
|
|
133
152
|
}
|
|
@@ -148,7 +167,7 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
|
|
|
148
167
|
const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
|
|
149
168
|
|
|
150
169
|
console.log("");
|
|
151
|
-
console.log(` ${bold("newpr")} ${dim(
|
|
170
|
+
console.log(` ${bold("newpr")} ${dim(`v${getVersion()}`)}`);
|
|
152
171
|
console.log("");
|
|
153
172
|
console.log(` ${dim("→")} Local ${cyan(url)}`);
|
|
154
173
|
console.log(` ${dim("→")} Model ${dim(config.model)}`);
|