feat: finalize local OpenAI key flow and MCP docs

- add server-side .env.local OpenAI key management for the open-source app
- route AI features through the preferred local key path and lazy-load embed recovery
- rewrite README and docs for current MCP setup, schema, and fully-local guidance

Generated with Claude Code
This commit is contained in:
“BeeRad”
2026-04-16 14:08:37 +10:00
parent c2f880d957
commit 97eeb0789f
28 changed files with 891 additions and 215 deletions
+5 -6
View File
@@ -1,6 +1,6 @@
import { generateText } from 'ai';
import { openai as openaiProvider } from '@ai-sdk/openai';
import { hasValidOpenAiKey } from '../storage/apiKeys';
import { createOpenAI } from '@ai-sdk/openai';
import { hasPreferredOpenAiKey, getPreferredOpenAiKey } from '../storage/openaiKeyServer';
import type { CanonicalNodeMetadata } from '@/types/database';
export interface DescriptionInput {
@@ -23,7 +23,7 @@ export interface DescriptionInput {
* The result must cover what the artifact is, why it is in the graph, and workflow status.
*/
export async function generateDescription(input: DescriptionInput): Promise<string> {
if (!hasValidOpenAiKey()) {
if (!hasPreferredOpenAiKey()) {
console.log(`[DescriptionService] No valid OpenAI key, using fallback for: "${input.title}"`);
return `${input.title}. Added via Quick Add with no further context yet, so the reason it belongs in the graph is not fully inferred. It has not been reviewed yet.`.slice(0, 500);
}
@@ -33,8 +33,9 @@ export async function generateDescription(input: DescriptionInput): Promise<stri
console.log(`[DescriptionService] Generating description for: "${input.title}"`);
const provider = createOpenAI({ apiKey: getPreferredOpenAiKey() });
const response = await generateText({
model: openaiProvider('gpt-4o-mini'),
model: provider('gpt-4o-mini'),
prompt,
maxOutputTokens: 100,
temperature: 0.3,
@@ -52,8 +53,6 @@ export async function generateDescription(input: DescriptionInput): Promise<stri
}
}
export { hasValidOpenAiKey } from '../storage/apiKeys';
function buildDescriptionPrompt(input: DescriptionInput): string {
const sourceMetadata = input.metadata?.source_metadata as Record<string, unknown> | undefined;
const sourceType = typeof input.metadata?.type === 'string'