mirror of
https://github.com/willchen96/mike.git
synced 2026-06-24 21:38:06 +02:00
- Refine CourtListener citation verification, bulk lookup logging, and API fallback behavior - Persist cancelled chat stream output and render cancellation as the final assistant message - Add document/version deletion safety fixes and shared warning/modal UI updates - Sync document panel, case law panel, and response UI styling refinements - Harden OSS sync script to preserve local env, dependency, and generated files
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { Router } from "express";
|
|
import { requireAuth } from "../middleware/auth";
|
|
import { getCourtlistenerCaseOpinions } from "../lib/courtlistener";
|
|
import { createServerSupabase } from "../lib/supabase";
|
|
import { getUserModelSettings } from "../lib/userSettings";
|
|
|
|
export const caseLawRouter = Router();
|
|
|
|
caseLawRouter.use(requireAuth);
|
|
|
|
const isDev = process.env.NODE_ENV !== "production";
|
|
const devLog = (...args: Parameters<typeof console.log>) => {
|
|
if (isDev) console.log(...args);
|
|
};
|
|
|
|
const sidepanelOpinionFetches = new Map<string, Promise<unknown>>();
|
|
|
|
function cleanClusterId(value: unknown): number | null {
|
|
const numeric =
|
|
typeof value === "number"
|
|
? value
|
|
: typeof value === "string"
|
|
? Number.parseInt(value, 10)
|
|
: NaN;
|
|
return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : null;
|
|
}
|
|
|
|
caseLawRouter.post("/case-opinions", async (req, res) => {
|
|
const body =
|
|
req.body && typeof req.body === "object" && !Array.isArray(req.body)
|
|
? (req.body as Record<string, unknown>)
|
|
: {};
|
|
const clusterId = cleanClusterId(body.clusterId ?? body.cluster_id);
|
|
if (!clusterId) {
|
|
return res.status(400).json({
|
|
detail: "cluster_id is required",
|
|
});
|
|
}
|
|
|
|
try {
|
|
const userId = String(res.locals.userId ?? "");
|
|
const settings = await getUserModelSettings(userId);
|
|
devLog("[case-law/case-opinions] loading sidepanel opinions", {
|
|
clusterId,
|
|
});
|
|
const db = createServerSupabase();
|
|
const fetchKey = `${userId}:${clusterId}`;
|
|
let fetchPromise = sidepanelOpinionFetches.get(fetchKey);
|
|
if (fetchPromise) {
|
|
devLog("[case-law/case-opinions] joining in-flight fetch", {
|
|
clusterId,
|
|
});
|
|
} else {
|
|
fetchPromise = getCourtlistenerCaseOpinions({
|
|
clusterId,
|
|
db,
|
|
includeFullText: true,
|
|
maxChars: 50000,
|
|
apiToken: settings.api_keys.courtlistener,
|
|
}).finally(() => {
|
|
sidepanelOpinionFetches.delete(fetchKey);
|
|
});
|
|
sidepanelOpinionFetches.set(fetchKey, fetchPromise);
|
|
}
|
|
const fetched = await fetchPromise;
|
|
const fetchedRecord =
|
|
fetched && typeof fetched === "object" && !Array.isArray(fetched)
|
|
? (fetched as Record<string, unknown>)
|
|
: {};
|
|
const opinions = Array.isArray(fetchedRecord.opinions)
|
|
? fetchedRecord.opinions
|
|
: [];
|
|
devLog("[case-law/case-opinions] returning sidepanel opinions", {
|
|
clusterId,
|
|
opinionCount: opinions.length,
|
|
});
|
|
|
|
return res.json({ opinions });
|
|
} catch (err) {
|
|
const message =
|
|
err instanceof Error ? err.message : "Failed to fetch case opinions";
|
|
return res.status(502).json({ detail: message });
|
|
}
|
|
});
|