Add complete end-to-end AI-powered quote extraction workflow: - Disable PB SDK auto-cancellation to support parallel batch requests - Add required runtime dependencies for AI processing and document parsing - Implement AI settings backend, operator UI, and REST API routes - Add quote ingestion, insight population, and manual re-run AI endpoints - Build service layers for quote items, AI settings, and structured item insights - Fix attachment upload handling and quote item history blob parsing - Add supporting migrations, smoke tests, and shared utility functions - Export previously private helper functions for cross-service reuse
109 lines
No EOL
3.6 KiB
TypeScript
109 lines
No EOL
3.6 KiB
TypeScript
/**
|
|
* Migration: add `system_prompt` text field to `ai_settings`.
|
|
*
|
|
* Background
|
|
* ──────────
|
|
* The quotes AI processor (`src/lib/server/quotes-ai-processor.ts`)
|
|
* ships a hardcoded SYSTEM_PROMPT constant. Operators have wanted to
|
|
* inspect and tweak the prompt without redeploying since the first
|
|
* extraction went sideways. This migration makes it editable from
|
|
* `/settings/ai`.
|
|
*
|
|
* Schema
|
|
* ──────
|
|
* system_prompt text The prompt sent as `system:` to the Anthropic
|
|
* API. Empty string means "use the hardcoded
|
|
* default in the source" — that's the safe
|
|
* behaviour because the field is added AFTER
|
|
* the row already exists (so no operator
|
|
* action is required and the processor keeps
|
|
* working through the migration).
|
|
*
|
|
* options.max is generous (50 000 chars) so
|
|
* multi-rule prompts fit comfortably; the
|
|
* current hardcoded default is ~3 KB.
|
|
*
|
|
* viewRule
|
|
* ────────
|
|
* The ai_settings viewRule already strips api_key from non-superuser
|
|
* reads at the service layer; system_prompt is intentionally NOT
|
|
* gated — anyone who can see /settings/ai should be able to read and
|
|
* edit the prompt.
|
|
*
|
|
* Idempotent — safe to re-run.
|
|
*
|
|
* Run with:
|
|
* npx tsx scripts/migrations/2026-08-09-add-ai-system-prompt.ts
|
|
*/
|
|
import PocketBase from "pocketbase";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
function loadEnvFile(name: string) {
|
|
try {
|
|
const raw = readFileSync(resolve(process.cwd(), name), "utf8");
|
|
for (const line of raw.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
const m = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(.+)$/i);
|
|
if (m && !process.env[m[1]]) {
|
|
process.env[m[1]] = m[2].replace(/^["']|["']$/g, "").trim();
|
|
}
|
|
}
|
|
} catch {
|
|
/* missing file — fine */
|
|
}
|
|
}
|
|
loadEnvFile(".env");
|
|
loadEnvFile(".env.local");
|
|
|
|
async function main() {
|
|
const url = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
|
const email = process.env.PB_ADMIN_EMAIL;
|
|
const password = process.env.PB_ADMIN_PASSWORD;
|
|
if (!url || !email || !password) {
|
|
throw new Error(
|
|
"Set NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD.",
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(url);
|
|
await pb.admins.authWithPassword(email, password);
|
|
|
|
const existing = await pb.collections.getOne("ai_settings").catch(() => null);
|
|
if (!existing) {
|
|
throw new Error(
|
|
"ai_settings collection missing — run scripts/migrations/2026-08-09-add-ai-settings.ts first.",
|
|
);
|
|
}
|
|
|
|
const existingFields = (existing.fields ?? []) as Array<{
|
|
name?: string;
|
|
}>;
|
|
if (existingFields.some((f) => f.name === "system_prompt")) {
|
|
console.log("system_prompt field already present on ai_settings, skipping.");
|
|
return;
|
|
}
|
|
|
|
const allFields = [
|
|
...(existingFields as Array<Record<string, unknown>>),
|
|
{
|
|
name: "system_prompt",
|
|
type: "text",
|
|
required: false,
|
|
presentable: false,
|
|
system: false,
|
|
hidden: false,
|
|
help: "System prompt sent to the Anthropic API. Empty = use the hardcoded default in the source. Max 50000 chars.",
|
|
options: { max: 50000, min: 0, pattern: "" },
|
|
},
|
|
];
|
|
|
|
await pb.collections.update(existing.id, { fields: allFields });
|
|
console.log("Added system_prompt field to ai_settings.");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}); |