fix: make paragraph refactor service use configured AI provider
The paragraph-refactor service was using OLLAMA_BASE_URL directly from environment variables instead of using the configured AI provider from the database. This caused "OLLAMA error" even when OpenAI was configured in the admin interface. Changes: - paragraph-refactor.service.ts: Now uses getSystemConfig() and getTagsProvider() from factory instead of direct Ollama calls - factory.ts: Added proper error messages when API keys are missing - .env.docker.example: Updated with new provider configuration variables (AI_PROVIDER_TAGS, AI_PROVIDER_EMBEDDING) This fixes the issue where AI reformulation features (Clarify, Shorten, Improve Style) would fail with OLLAMA errors even when OpenAI was properly configured in the admin settings. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
58e486c68e
commit
5d315a6bdd
@ -14,19 +14,37 @@ NEXTAUTH_URL="http://YOUR_SERVER_IP:3000"
|
|||||||
# ============================================
|
# ============================================
|
||||||
# AI Provider Configuration
|
# AI Provider Configuration
|
||||||
# ============================================
|
# ============================================
|
||||||
|
# You can configure separate providers for tags and embeddings
|
||||||
|
# Options: ollama, openai, custom
|
||||||
|
|
||||||
# For local Ollama in Docker (service name):
|
# For local Ollama in Docker (service name):
|
||||||
AI_PROVIDER=ollama
|
# AI_PROVIDER_TAGS=ollama
|
||||||
OLLAMA_BASE_URL="http://ollama:11434"
|
# AI_PROVIDER_EMBEDDING=ollama
|
||||||
OLLAMA_MODEL="granite4:latest"
|
# OLLAMA_BASE_URL="http://ollama:11434"
|
||||||
|
# AI_MODEL_TAGS="granite4:latest"
|
||||||
|
# AI_MODEL_EMBEDDING="embeddinggemma:latest"
|
||||||
|
|
||||||
# For external Ollama (on host or different server):
|
# For external Ollama (on host or different server):
|
||||||
# AI_PROVIDER=ollama
|
# AI_PROVIDER_TAGS=ollama
|
||||||
|
# AI_PROVIDER_EMBEDDING=ollama
|
||||||
# OLLAMA_BASE_URL="http://YOUR_SERVER_IP:11434"
|
# OLLAMA_BASE_URL="http://YOUR_SERVER_IP:11434"
|
||||||
# OLLAMA_MODEL="granite4:latest"
|
# AI_MODEL_TAGS="granite4:latest"
|
||||||
|
# AI_MODEL_EMBEDDING="embeddinggemma:latest"
|
||||||
|
|
||||||
# For OpenAI:
|
# For OpenAI (recommended for production):
|
||||||
# AI_PROVIDER=openai
|
# AI_PROVIDER_TAGS=openai
|
||||||
|
# AI_PROVIDER_EMBEDDING=openai
|
||||||
# OPENAI_API_KEY="sk-..."
|
# OPENAI_API_KEY="sk-..."
|
||||||
|
# AI_MODEL_TAGS="gpt-4o-mini"
|
||||||
|
# AI_MODEL_EMBEDDING="text-embedding-3-small"
|
||||||
|
|
||||||
|
# Mixed setup (e.g., Ollama for tags, OpenAI for embeddings):
|
||||||
|
# AI_PROVIDER_TAGS=ollama
|
||||||
|
# AI_PROVIDER_EMBEDDING=openai
|
||||||
|
# OLLAMA_BASE_URL="http://ollama:11434"
|
||||||
|
# OPENAI_API_KEY="sk-..."
|
||||||
|
# AI_MODEL_TAGS="granite4:latest"
|
||||||
|
# AI_MODEL_EMBEDDING="text-embedding-3-small"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Email Configuration (Optional)
|
# Email Configuration (Optional)
|
||||||
@ -37,8 +55,3 @@ OLLAMA_MODEL="granite4:latest"
|
|||||||
# SMTP_USER="your-email@gmail.com"
|
# SMTP_USER="your-email@gmail.com"
|
||||||
# SMTP_PASS="your-app-password"
|
# SMTP_PASS="your-app-password"
|
||||||
# SMTP_FROM="noreply@memento.app"
|
# SMTP_FROM="noreply@memento.app"
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# OpenAI (Optional - for GPT models)
|
|
||||||
# ============================================
|
|
||||||
# OPENAI_API_KEY="sk-..."
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ function createOpenAIProvider(config: Record<string, string>, modelName: string,
|
|||||||
const apiKey = config?.OPENAI_API_KEY || process.env.OPENAI_API_KEY || '';
|
const apiKey = config?.OPENAI_API_KEY || process.env.OPENAI_API_KEY || '';
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
throw new Error('OPENAI_API_KEY is required when using OpenAI provider');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OpenAIProvider(apiKey, modelName, embeddingModelName);
|
return new OpenAIProvider(apiKey, modelName, embeddingModelName);
|
||||||
@ -39,9 +40,11 @@ function createCustomOpenAIProvider(config: Record<string, string>, modelName: s
|
|||||||
const baseUrl = config?.CUSTOM_OPENAI_BASE_URL || process.env.CUSTOM_OPENAI_BASE_URL || '';
|
const baseUrl = config?.CUSTOM_OPENAI_BASE_URL || process.env.CUSTOM_OPENAI_BASE_URL || '';
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
throw new Error('CUSTOM_OPENAI_API_KEY is required when using Custom OpenAI provider');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
|
throw new Error('CUSTOM_OPENAI_BASE_URL is required when using Custom OpenAI provider');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CustomOpenAIProvider(apiKey, baseUrl, modelName, embeddingModelName);
|
return new CustomOpenAIProvider(apiKey, baseUrl, modelName, embeddingModelName);
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LanguageDetectionService } from './language-detection.service'
|
import { LanguageDetectionService } from './language-detection.service'
|
||||||
|
import { getTagsProvider } from '../factory'
|
||||||
|
import { getSystemConfig } from '@/lib/config'
|
||||||
|
|
||||||
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle'
|
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle'
|
||||||
|
|
||||||
@ -83,37 +85,13 @@ export class ParagraphRefactorService {
|
|||||||
const systemPrompt = this.getSystemPrompt(mode)
|
const systemPrompt = this.getSystemPrompt(mode)
|
||||||
const userPrompt = this.getUserPrompt(mode, content, language)
|
const userPrompt = this.getUserPrompt(mode, content, language)
|
||||||
|
|
||||||
// Get AI provider response using fetch
|
// Get AI provider from factory
|
||||||
let baseUrl = process.env.OLLAMA_BASE_URL
|
const config = await getSystemConfig()
|
||||||
|
const provider = getTagsProvider(config)
|
||||||
|
|
||||||
if (!baseUrl) {
|
// Use provider's generateText method
|
||||||
throw new Error('OLLAMA_BASE_URL environment variable is required')
|
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`
|
||||||
}
|
const refactored = await provider.generateText(fullPrompt)
|
||||||
|
|
||||||
// Remove /api suffix if present to avoid double /api/api/...
|
|
||||||
if (baseUrl.endsWith('/api')) {
|
|
||||||
baseUrl = baseUrl.slice(0, -4)
|
|
||||||
}
|
|
||||||
const modelName = process.env.OLLAMA_MODEL || 'granite4:latest'
|
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: modelName,
|
|
||||||
system: systemPrompt,
|
|
||||||
prompt: userPrompt,
|
|
||||||
stream: false,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Provider error: ${response.statusText}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
const refactored = this.extractRefactoredText(data.response)
|
|
||||||
|
|
||||||
// Calculate word count change
|
// Calculate word count change
|
||||||
const refactoredWordCount = refactored.split(/\s+/).length
|
const refactoredWordCount = refactored.split(/\s+/).length
|
||||||
@ -189,38 +167,16 @@ ${content}
|
|||||||
Original language: ${language}
|
Original language: ${language}
|
||||||
IMPORTANT: Provide all 3 versions in ${language}. No English, no explanations.`
|
IMPORTANT: Provide all 3 versions in ${language}. No English, no explanations.`
|
||||||
|
|
||||||
// Get AI provider response using fetch
|
// Get AI provider from factory
|
||||||
let baseUrl = process.env.OLLAMA_BASE_URL
|
const config = await getSystemConfig()
|
||||||
|
const provider = getTagsProvider(config)
|
||||||
|
|
||||||
if (!baseUrl) {
|
// Use provider's generateText method
|
||||||
throw new Error('OLLAMA_BASE_URL environment variable is required')
|
const fullPrompt = `${systemPrompt}\n\n${userPrompt}`
|
||||||
}
|
const response = await provider.generateText(fullPrompt)
|
||||||
|
|
||||||
// Remove /api suffix if present to avoid double /api/api/...
|
|
||||||
if (baseUrl.endsWith('/api')) {
|
|
||||||
baseUrl = baseUrl.slice(0, -4)
|
|
||||||
}
|
|
||||||
const modelName = process.env.OLLAMA_MODEL || 'granite4:latest'
|
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/api/generate`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: modelName,
|
|
||||||
system: systemPrompt,
|
|
||||||
prompt: userPrompt,
|
|
||||||
stream: false,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Provider error: ${response.statusText}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
// Parse JSON response
|
// Parse JSON response
|
||||||
const jsonResponse = JSON.parse(data.response)
|
const jsonResponse = JSON.parse(response)
|
||||||
|
|
||||||
const modes: RefactorMode[] = ['clarify', 'shorten', 'improveStyle']
|
const modes: RefactorMode[] = ['clarify', 'shorten', 'improveStyle']
|
||||||
const results: RefactorResult[] = []
|
const results: RefactorResult[] = []
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
5406
keep-notes/prisma/client-generated/index.d.ts
vendored
5406
keep-notes/prisma/client-generated/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-62aaff0ff1302a5b1470021854f34bb9a9c1219fe39a7ee39aa626bd83e22eae",
|
"name": "prisma-client-46efe72656f1c393bbd99fdd6d2d34037b30f693f014757faf08aec1d9319858",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "index-browser.js",
|
"browser": "index-browser.js",
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
output = "./client-generated"
|
output = "./client-generated"
|
||||||
@ -13,31 +10,28 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String @unique
|
email String @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
password String? // Hashed password
|
password String?
|
||||||
role String @default("USER") // "USER" or "ADMIN"
|
role String @default("USER")
|
||||||
image String?
|
image String?
|
||||||
theme String @default("light")
|
theme String @default("light")
|
||||||
resetToken String? @unique
|
resetToken String? @unique
|
||||||
resetTokenExpiry DateTime?
|
resetTokenExpiry DateTime?
|
||||||
accounts Account[]
|
createdAt DateTime @default(now())
|
||||||
sessions Session[]
|
updatedAt DateTime @updatedAt
|
||||||
notes Note[]
|
accounts Account[]
|
||||||
labels Label[]
|
|
||||||
notebooks Notebook[] // NEW: Relation to notebooks
|
|
||||||
receivedShares NoteShare[] @relation("ReceivedShares")
|
|
||||||
sentShares NoteShare[] @relation("SentShares")
|
|
||||||
|
|
||||||
// Phase 1 AI Relations
|
|
||||||
aiFeedback AiFeedback[]
|
aiFeedback AiFeedback[]
|
||||||
aiSettings UserAISettings?
|
labels Label[]
|
||||||
memoryEchoInsights MemoryEchoInsight[]
|
memoryEchoInsights MemoryEchoInsight[]
|
||||||
|
notes Note[]
|
||||||
createdAt DateTime @default(now())
|
sentShares NoteShare[] @relation("SentShares")
|
||||||
updatedAt DateTime @updatedAt
|
receivedShares NoteShare[] @relation("ReceivedShares")
|
||||||
|
notebooks Notebook[]
|
||||||
|
sessions Session[]
|
||||||
|
aiSettings UserAISettings?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
@ -52,11 +46,9 @@ model Account {
|
|||||||
scope String?
|
scope String?
|
||||||
id_token String?
|
id_token String?
|
||||||
session_state String?
|
session_state String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
createdAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
updatedAt DateTime @updatedAt
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@id([provider, providerAccountId])
|
@@id([provider, providerAccountId])
|
||||||
}
|
}
|
||||||
@ -65,10 +57,9 @@ model Session {
|
|||||||
sessionToken String @unique
|
sessionToken String @unique
|
||||||
userId String
|
userId String
|
||||||
expires DateTime
|
expires DateTime
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
@ -79,19 +70,18 @@ model VerificationToken {
|
|||||||
@@id([identifier, token])
|
@@id([identifier, token])
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Notebook model for organizing notes
|
|
||||||
model Notebook {
|
model Notebook {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
icon String? // Emoji or icon name
|
icon String?
|
||||||
color String? // Hex color for personalization
|
color String?
|
||||||
order Int // Manual order for drag & drop
|
order Int
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
notes Note[] // Notes can belong to a notebook
|
|
||||||
labels Label[] // Labels are contextual to this notebook
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
labels Label[]
|
||||||
|
notes Note[]
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([userId, order])
|
@@index([userId, order])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -101,89 +91,80 @@ model Label {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
color String @default("gray")
|
color String @default("gray")
|
||||||
notebookId String? // TEMPORARY: Optional for migration, will be required later
|
notebookId String?
|
||||||
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade)
|
userId String?
|
||||||
notes Note[] // NEW: Many-to-many relation with notes
|
|
||||||
userId String? // DEPRECATED: Kept for migration, will be removed after migration
|
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade)
|
||||||
|
notes Note[] @relation("LabelToNote")
|
||||||
|
|
||||||
@@unique([notebookId, name]) // NEW: Labels are unique within a notebook (ignored if notebookId is null)
|
@@unique([notebookId, name])
|
||||||
@@index([notebookId])
|
@@index([notebookId])
|
||||||
@@index([userId]) // DEPRECATED: Keep for now, remove after migration
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Note {
|
model Note {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
title String?
|
title String?
|
||||||
content String
|
content String
|
||||||
color String @default("default")
|
color String @default("default")
|
||||||
isPinned Boolean @default(false)
|
isPinned Boolean @default(false)
|
||||||
isArchived Boolean @default(false)
|
isArchived Boolean @default(false)
|
||||||
type String @default("text") // "text" or "checklist"
|
type String @default("text")
|
||||||
checkItems String? // For checklist items stored as JSON string
|
checkItems String?
|
||||||
labels String? // Array of label names stored as JSON string (DEPRECATED)
|
labels String?
|
||||||
images String? // Array of image URLs stored as JSON string
|
images String?
|
||||||
links String? // Array of link metadata stored as JSON string
|
links String?
|
||||||
reminder DateTime? // Reminder date and time
|
reminder DateTime?
|
||||||
isReminderDone Boolean @default(false)
|
isReminderDone Boolean @default(false)
|
||||||
reminderRecurrence String? // "none", "daily", "weekly", "monthly", "custom"
|
reminderRecurrence String?
|
||||||
reminderLocation String? // Location for location-based reminders
|
reminderLocation String?
|
||||||
isMarkdown Boolean @default(false) // Whether content uses Markdown
|
isMarkdown Boolean @default(false)
|
||||||
size String @default("small") // "small", "medium", "large"
|
size String @default("small")
|
||||||
embedding String? // Vector embeddings stored as JSON string for semantic search
|
embedding String?
|
||||||
sharedWith String? // Array of user IDs (collaborators) stored as JSON string
|
sharedWith String?
|
||||||
userId String? // Owner of the note
|
userId String?
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
order Int @default(0)
|
||||||
shares NoteShare[] // All share records for this note
|
notebookId String?
|
||||||
order Int @default(0)
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
// NEW: Notebook relation (optional - null = "Notes générales" / Inbox)
|
autoGenerated Boolean?
|
||||||
notebookId String? // NULL = note is in general notes
|
aiProvider String?
|
||||||
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: SetNull)
|
aiConfidence Int?
|
||||||
|
language String?
|
||||||
// NEW: Many-to-many relation with labels
|
languageConfidence Float?
|
||||||
labelRelations Label[] // Uses implicit _NoteToLabel junction table
|
lastAiAnalysis DateTime?
|
||||||
|
aiFeedback AiFeedback[]
|
||||||
createdAt DateTime @default(now())
|
memoryEchoAsNote2 MemoryEchoInsight[] @relation("EchoNote2")
|
||||||
updatedAt DateTime @updatedAt
|
memoryEchoAsNote1 MemoryEchoInsight[] @relation("EchoNote1")
|
||||||
|
notebook Notebook? @relation(fields: [notebookId], references: [id])
|
||||||
// Phase 1 AI Extensions
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
autoGenerated Boolean? // True if title/content was AI-generated
|
shares NoteShare[]
|
||||||
aiProvider String? // 'openai' | 'ollama'
|
labelRelations Label[] @relation("LabelToNote")
|
||||||
aiConfidence Int? // 0-100 (collected Phase 1, UI Phase 3)
|
|
||||||
language String? // ISO 639-1: 'fr', 'en', 'es', 'de', 'fa'
|
|
||||||
languageConfidence Float? // 0.0-1.0
|
|
||||||
lastAiAnalysis DateTime? // Timestamp of last AI analysis
|
|
||||||
|
|
||||||
// Relations for Phase 1 AI
|
|
||||||
aiFeedback AiFeedback[]
|
|
||||||
memoryEchoAsNote1 MemoryEchoInsight[] @relation("EchoNote1")
|
|
||||||
memoryEchoAsNote2 MemoryEchoInsight[] @relation("EchoNote2")
|
|
||||||
|
|
||||||
@@index([isPinned])
|
@@index([isPinned])
|
||||||
@@index([isArchived])
|
@@index([isArchived])
|
||||||
@@index([order])
|
@@index([order])
|
||||||
@@index([reminder])
|
@@index([reminder])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([userId, notebookId]) // NEW: For filtering notes by notebook
|
@@index([userId, notebookId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model NoteShare {
|
model NoteShare {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
noteId String
|
noteId String
|
||||||
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
|
||||||
userId String
|
userId String
|
||||||
user User @relation("ReceivedShares", fields: [userId], references: [id], onDelete: Cascade)
|
sharedBy String
|
||||||
sharedBy String // User ID who shared the note
|
status String @default("pending")
|
||||||
sharer User @relation("SentShares", fields: [sharedBy], references: [id], onDelete: Cascade)
|
permission String @default("view")
|
||||||
status String @default("pending") // "pending", "accepted", "declined", "removed"
|
|
||||||
permission String @default("view") // "view", "comment", "edit"
|
|
||||||
notifiedAt DateTime?
|
notifiedAt DateTime?
|
||||||
respondedAt DateTime?
|
respondedAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
sharer User @relation("SentShares", fields: [sharedBy], references: [id], onDelete: Cascade)
|
||||||
|
user User @relation("ReceivedShares", fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([noteId, userId])
|
@@unique([noteId, userId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -196,21 +177,18 @@ model SystemConfig {
|
|||||||
value String
|
value String
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1 MVP AI Models
|
|
||||||
|
|
||||||
model AiFeedback {
|
model AiFeedback {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
noteId String
|
noteId String
|
||||||
userId String?
|
userId String?
|
||||||
feedbackType String // 'thumbs_up' | 'thumbs_down' | 'correction'
|
feedbackType String
|
||||||
feature String // 'title_suggestion' | 'memory_echo' | 'semantic_search' | 'paragraph_refactor'
|
feature String
|
||||||
originalContent String // JSON string of AI-generated content
|
originalContent String
|
||||||
correctedContent String? // User's modified version
|
correctedContent String?
|
||||||
metadata String? // JSON string for additional data (provider, model, timestamp)
|
metadata String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@index([noteId])
|
@@index([noteId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -223,41 +201,33 @@ model MemoryEchoInsight {
|
|||||||
note1Id String
|
note1Id String
|
||||||
note2Id String
|
note2Id String
|
||||||
similarityScore Float
|
similarityScore Float
|
||||||
insight String // AI-generated explanation of the connection
|
insight String
|
||||||
insightDate DateTime @default(now())
|
insightDate DateTime @default(now())
|
||||||
viewed Boolean @default(false)
|
viewed Boolean @default(false)
|
||||||
feedback String? // 'thumbs_up' | 'thumbs_down'
|
feedback String?
|
||||||
dismissed Boolean @default(false) // User dismissed this connection
|
dismissed Boolean @default(false)
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
note1 Note @relation("EchoNote1", fields: [note1Id], references: [id], onDelete: Cascade)
|
note2 Note @relation("EchoNote2", fields: [note2Id], references: [id], onDelete: Cascade)
|
||||||
note2 Note @relation("EchoNote2", fields: [note2Id], references: [id], onDelete: Cascade)
|
note1 Note @relation("EchoNote1", fields: [note1Id], references: [id], onDelete: Cascade)
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([userId, insightDate])
|
@@unique([userId, insightDate])
|
||||||
@@index([userId, insightDate])
|
@@index([userId, insightDate])
|
||||||
@@index([userId, dismissed]) // For filtering dismissed connections
|
@@index([userId, dismissed])
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserAISettings {
|
model UserAISettings {
|
||||||
userId String @id
|
userId String @id
|
||||||
|
titleSuggestions Boolean @default(true)
|
||||||
|
semanticSearch Boolean @default(true)
|
||||||
|
paragraphRefactor Boolean @default(true)
|
||||||
|
memoryEcho Boolean @default(true)
|
||||||
|
memoryEchoFrequency String @default("daily")
|
||||||
|
aiProvider String @default("auto")
|
||||||
|
preferredLanguage String @default("auto")
|
||||||
|
fontSize String @default("medium")
|
||||||
|
demoMode Boolean @default(false)
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
// Feature Flags (granular ON/OFF)
|
|
||||||
titleSuggestions Boolean @default(true)
|
|
||||||
semanticSearch Boolean @default(true)
|
|
||||||
paragraphRefactor Boolean @default(true)
|
|
||||||
memoryEcho Boolean @default(true)
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
memoryEchoFrequency String @default("daily") // 'daily' | 'weekly' | 'custom'
|
|
||||||
aiProvider String @default("auto") // 'auto' | 'openai' | 'ollama'
|
|
||||||
preferredLanguage String @default("auto") // 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl'
|
|
||||||
fontSize String @default("medium") // 'small' | 'medium' | 'large' | 'extra-large'
|
|
||||||
demoMode Boolean @default(false) // Demo mode for testing Memory Echo
|
|
||||||
|
|
||||||
// Relation
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
// Indexes for analytics
|
|
||||||
@@index([memoryEcho])
|
@@index([memoryEcho])
|
||||||
@@index([aiProvider])
|
@@index([aiProvider])
|
||||||
@@index([memoryEchoFrequency])
|
@@index([memoryEchoFrequency])
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
output = "./client-generated"
|
output = "./client-generated"
|
||||||
binaryTargets = ["debian-openssl-1.1.x", "native"]
|
binaryTargets = ["debian-openssl-1.1.x", "native"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,31 +10,28 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String @unique
|
email String @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
password String? // Hashed password
|
password String?
|
||||||
role String @default("USER") // "USER" or "ADMIN"
|
role String @default("USER")
|
||||||
image String?
|
image String?
|
||||||
theme String @default("light")
|
theme String @default("light")
|
||||||
resetToken String? @unique
|
resetToken String? @unique
|
||||||
resetTokenExpiry DateTime?
|
resetTokenExpiry DateTime?
|
||||||
accounts Account[]
|
createdAt DateTime @default(now())
|
||||||
sessions Session[]
|
updatedAt DateTime @updatedAt
|
||||||
notes Note[]
|
accounts Account[]
|
||||||
labels Label[]
|
aiFeedback AiFeedback[]
|
||||||
notebooks Notebook[] // NEW: Relation to notebooks
|
labels Label[]
|
||||||
receivedShares NoteShare[] @relation("ReceivedShares")
|
|
||||||
sentShares NoteShare[] @relation("SentShares")
|
|
||||||
|
|
||||||
// Phase 1 AI Relations
|
|
||||||
aiFeedback AiFeedback[]
|
|
||||||
aiSettings UserAISettings?
|
|
||||||
memoryEchoInsights MemoryEchoInsight[]
|
memoryEchoInsights MemoryEchoInsight[]
|
||||||
|
notes Note[]
|
||||||
createdAt DateTime @default(now())
|
sentShares NoteShare[] @relation("SentShares")
|
||||||
updatedAt DateTime @updatedAt
|
receivedShares NoteShare[] @relation("ReceivedShares")
|
||||||
|
notebooks Notebook[]
|
||||||
|
sessions Session[]
|
||||||
|
aiSettings UserAISettings?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
@ -52,11 +46,9 @@ model Account {
|
|||||||
scope String?
|
scope String?
|
||||||
id_token String?
|
id_token String?
|
||||||
session_state String?
|
session_state String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
createdAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
updatedAt DateTime @updatedAt
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@id([provider, providerAccountId])
|
@@id([provider, providerAccountId])
|
||||||
}
|
}
|
||||||
@ -65,10 +57,9 @@ model Session {
|
|||||||
sessionToken String @unique
|
sessionToken String @unique
|
||||||
userId String
|
userId String
|
||||||
expires DateTime
|
expires DateTime
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
@ -79,111 +70,101 @@ model VerificationToken {
|
|||||||
@@id([identifier, token])
|
@@id([identifier, token])
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Notebook model for organizing notes
|
|
||||||
model Notebook {
|
model Notebook {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
icon String? // Emoji or icon name
|
icon String?
|
||||||
color String? // Hex color for personalization
|
color String?
|
||||||
order Int // Manual order for drag & drop
|
order Int
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
notes Note[] // Notes can belong to a notebook
|
|
||||||
labels Label[] // Labels are contextual to this notebook
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
labels Label[]
|
||||||
|
notes Note[]
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([userId, order])
|
@@index([userId, order])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Label {
|
model Label {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
color String @default("gray")
|
color String @default("gray")
|
||||||
notebookId String? // TEMPORARY: Optional for migration, will be required later
|
notebookId String?
|
||||||
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade)
|
userId String?
|
||||||
notes Note[] // NEW: Many-to-many relation with notes
|
createdAt DateTime @default(now())
|
||||||
userId String? // DEPRECATED: Kept for migration, will be removed after migration
|
updatedAt DateTime @updatedAt
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
createdAt DateTime @default(now())
|
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade)
|
||||||
updatedAt DateTime @updatedAt
|
notes Note[] @relation("LabelToNote")
|
||||||
|
|
||||||
@@unique([notebookId, name]) // NEW: Labels are unique within a notebook (ignored if notebookId is null)
|
@@unique([notebookId, name])
|
||||||
@@index([notebookId])
|
@@index([notebookId])
|
||||||
@@index([userId]) // DEPRECATED: Keep for now, remove after migration
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Note {
|
model Note {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
title String?
|
title String?
|
||||||
content String
|
content String
|
||||||
color String @default("default")
|
color String @default("default")
|
||||||
isPinned Boolean @default(false)
|
isPinned Boolean @default(false)
|
||||||
isArchived Boolean @default(false)
|
isArchived Boolean @default(false)
|
||||||
type String @default("text") // "text" or "checklist"
|
type String @default("text")
|
||||||
checkItems String? // For checklist items stored as JSON string
|
checkItems String?
|
||||||
labels String? // Array of label names stored as JSON string (DEPRECATED)
|
labels String?
|
||||||
images String? // Array of image URLs stored as JSON string
|
images String?
|
||||||
links String? // Array of link metadata stored as JSON string
|
links String?
|
||||||
reminder DateTime? // Reminder date and time
|
reminder DateTime?
|
||||||
isReminderDone Boolean @default(false)
|
isReminderDone Boolean @default(false)
|
||||||
reminderRecurrence String? // "none", "daily", "weekly", "monthly", "custom"
|
reminderRecurrence String?
|
||||||
reminderLocation String? // Location for location-based reminders
|
reminderLocation String?
|
||||||
isMarkdown Boolean @default(false) // Whether content uses Markdown
|
isMarkdown Boolean @default(false)
|
||||||
size String @default("small") // "small", "medium", "large"
|
size String @default("small")
|
||||||
embedding String? // Vector embeddings stored as JSON string for semantic search
|
embedding String?
|
||||||
sharedWith String? // Array of user IDs (collaborators) stored as JSON string
|
sharedWith String?
|
||||||
userId String? // Owner of the note
|
userId String?
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
order Int @default(0)
|
||||||
shares NoteShare[] // All share records for this note
|
notebookId String?
|
||||||
order Int @default(0)
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
// NEW: Notebook relation (optional - null = "Notes générales" / Inbox)
|
autoGenerated Boolean?
|
||||||
notebookId String? // NULL = note is in general notes
|
aiProvider String?
|
||||||
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: SetNull)
|
aiConfidence Int?
|
||||||
|
language String?
|
||||||
// NEW: Many-to-many relation with labels
|
languageConfidence Float?
|
||||||
labelRelations Label[] // Uses implicit _NoteToLabel junction table
|
lastAiAnalysis DateTime?
|
||||||
|
aiFeedback AiFeedback[]
|
||||||
createdAt DateTime @default(now())
|
memoryEchoAsNote2 MemoryEchoInsight[] @relation("EchoNote2")
|
||||||
updatedAt DateTime @updatedAt
|
memoryEchoAsNote1 MemoryEchoInsight[] @relation("EchoNote1")
|
||||||
|
notebook Notebook? @relation(fields: [notebookId], references: [id])
|
||||||
// Phase 1 AI Extensions
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
autoGenerated Boolean? // True if title/content was AI-generated
|
shares NoteShare[]
|
||||||
aiProvider String? // 'openai' | 'ollama'
|
labelRelations Label[] @relation("LabelToNote")
|
||||||
aiConfidence Int? // 0-100 (collected Phase 1, UI Phase 3)
|
|
||||||
language String? // ISO 639-1: 'fr', 'en', 'es', 'de', 'fa'
|
|
||||||
languageConfidence Float? // 0.0-1.0
|
|
||||||
lastAiAnalysis DateTime? // Timestamp of last AI analysis
|
|
||||||
|
|
||||||
// Relations for Phase 1 AI
|
|
||||||
aiFeedback AiFeedback[]
|
|
||||||
memoryEchoAsNote1 MemoryEchoInsight[] @relation("EchoNote1")
|
|
||||||
memoryEchoAsNote2 MemoryEchoInsight[] @relation("EchoNote2")
|
|
||||||
|
|
||||||
@@index([isPinned])
|
@@index([isPinned])
|
||||||
@@index([isArchived])
|
@@index([isArchived])
|
||||||
@@index([order])
|
@@index([order])
|
||||||
@@index([reminder])
|
@@index([reminder])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([userId, notebookId]) // NEW: For filtering notes by notebook
|
@@index([userId, notebookId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model NoteShare {
|
model NoteShare {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
noteId String
|
noteId String
|
||||||
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
|
||||||
userId String
|
userId String
|
||||||
user User @relation("ReceivedShares", fields: [userId], references: [id], onDelete: Cascade)
|
sharedBy String
|
||||||
sharedBy String // User ID who shared the note
|
status String @default("pending")
|
||||||
sharer User @relation("SentShares", fields: [sharedBy], references: [id], onDelete: Cascade)
|
permission String @default("view")
|
||||||
status String @default("pending") // "pending", "accepted", "declined", "removed"
|
|
||||||
permission String @default("view") // "view", "comment", "edit"
|
|
||||||
notifiedAt DateTime?
|
notifiedAt DateTime?
|
||||||
respondedAt DateTime?
|
respondedAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
sharer User @relation("SentShares", fields: [sharedBy], references: [id], onDelete: Cascade)
|
||||||
|
user User @relation("ReceivedShares", fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([noteId, userId])
|
@@unique([noteId, userId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -196,21 +177,18 @@ model SystemConfig {
|
|||||||
value String
|
value String
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1 MVP AI Models
|
|
||||||
|
|
||||||
model AiFeedback {
|
model AiFeedback {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
noteId String
|
noteId String
|
||||||
userId String?
|
userId String?
|
||||||
feedbackType String // 'thumbs_up' | 'thumbs_down' | 'correction'
|
feedbackType String
|
||||||
feature String // 'title_suggestion' | 'memory_echo' | 'semantic_search' | 'paragraph_refactor'
|
feature String
|
||||||
originalContent String // JSON string of AI-generated content
|
originalContent String
|
||||||
correctedContent String? // User's modified version
|
correctedContent String?
|
||||||
metadata String? // JSON string for additional data (provider, model, timestamp)
|
metadata String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@index([noteId])
|
@@index([noteId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@ -218,46 +196,38 @@ model AiFeedback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model MemoryEchoInsight {
|
model MemoryEchoInsight {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String?
|
userId String?
|
||||||
note1Id String
|
note1Id String
|
||||||
note2Id String
|
note2Id String
|
||||||
similarityScore Float
|
similarityScore Float
|
||||||
insight String // AI-generated explanation of the connection
|
insight String
|
||||||
insightDate DateTime @default(now())
|
insightDate DateTime @default(now())
|
||||||
viewed Boolean @default(false)
|
viewed Boolean @default(false)
|
||||||
feedback String? // 'thumbs_up' | 'thumbs_down'
|
feedback String?
|
||||||
dismissed Boolean @default(false) // User dismissed this connection
|
dismissed Boolean @default(false)
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
note1 Note @relation("EchoNote1", fields: [note1Id], references: [id], onDelete: Cascade)
|
note2 Note @relation("EchoNote2", fields: [note2Id], references: [id], onDelete: Cascade)
|
||||||
note2 Note @relation("EchoNote2", fields: [note2Id], references: [id], onDelete: Cascade)
|
note1 Note @relation("EchoNote1", fields: [note1Id], references: [id], onDelete: Cascade)
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([userId, insightDate])
|
@@unique([userId, insightDate])
|
||||||
@@index([userId, insightDate])
|
@@index([userId, insightDate])
|
||||||
@@index([userId, dismissed]) // For filtering dismissed connections
|
@@index([userId, dismissed])
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserAISettings {
|
model UserAISettings {
|
||||||
userId String @id
|
userId String @id
|
||||||
|
titleSuggestions Boolean @default(true)
|
||||||
|
semanticSearch Boolean @default(true)
|
||||||
|
paragraphRefactor Boolean @default(true)
|
||||||
|
memoryEcho Boolean @default(true)
|
||||||
|
memoryEchoFrequency String @default("daily")
|
||||||
|
aiProvider String @default("auto")
|
||||||
|
preferredLanguage String @default("auto")
|
||||||
|
fontSize String @default("medium")
|
||||||
|
demoMode Boolean @default(false)
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
// Feature Flags (granular ON/OFF)
|
|
||||||
titleSuggestions Boolean @default(true)
|
|
||||||
semanticSearch Boolean @default(true)
|
|
||||||
paragraphRefactor Boolean @default(true)
|
|
||||||
memoryEcho Boolean @default(true)
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
memoryEchoFrequency String @default("daily") // 'daily' | 'weekly' | 'custom'
|
|
||||||
aiProvider String @default("auto") // 'auto' | 'openai' | 'ollama'
|
|
||||||
preferredLanguage String @default("auto") // 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl'
|
|
||||||
fontSize String @default("medium") // 'small' | 'medium' | 'large' | 'extra-large'
|
|
||||||
demoMode Boolean @default(false) // Demo mode for testing Memory Echo
|
|
||||||
|
|
||||||
// Relation
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
// Indexes for analytics
|
|
||||||
@@index([memoryEcho])
|
@@index([memoryEcho])
|
||||||
@@index([aiProvider])
|
@@index([aiProvider])
|
||||||
@@index([memoryEchoFrequency])
|
@@index([memoryEchoFrequency])
|
||||||
|
|||||||
41
keep-notes/scripts/debug-config.ts
Normal file
41
keep-notes/scripts/debug-config.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import prisma from '../lib/prisma'
|
||||||
|
|
||||||
|
async function debugConfig() {
|
||||||
|
console.log('=== System Configuration Debug ===\n')
|
||||||
|
|
||||||
|
const configs = await prisma.systemConfig.findMany()
|
||||||
|
|
||||||
|
console.log(`Total configs in DB: ${configs.length}\n`)
|
||||||
|
|
||||||
|
// Group by category
|
||||||
|
const aiConfigs = configs.filter(c => c.key.startsWith('AI_'))
|
||||||
|
const ollamaConfigs = configs.filter(c => c.key.includes('OLLAMA'))
|
||||||
|
const openaiConfigs = configs.filter(c => c.key.includes('OPENAI'))
|
||||||
|
|
||||||
|
console.log('=== AI Provider Configs ===')
|
||||||
|
aiConfigs.forEach(c => {
|
||||||
|
console.log(`${c.key}: "${c.value}"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n=== Ollama Configs ===')
|
||||||
|
ollamaConfigs.forEach(c => {
|
||||||
|
console.log(`${c.key}: "${c.value}"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n=== OpenAI Configs ===')
|
||||||
|
openaiConfigs.forEach(c => {
|
||||||
|
console.log(`${c.key}: "${c.value}"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n=== All Configs ===')
|
||||||
|
configs.forEach(c => {
|
||||||
|
console.log(`${c.key}: "${c.value}"`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
debugConfig()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user