fix: resolve React Error #310 and refactor admin section
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled
- Fix React bug #33580: remove Suspense boundaries co-located with Link components - Delete settings/loading.tsx and admin/loading.tsx (root cause of race condition) - Convert all admin navigation from Next.js Link to anchor tags - Move admin pages to dedicated (admin) route group - Add AdminHeader matching main header visual design - Add AdminSidebar with anchor-based navigation - Add /api/admin/models route handler (replaces server actions for GET) - Add /api/debug/client-error for server-side browser error reporting - Add useNoteRefreshOptional() to fix crash in AdminHeader - Hide Admin Dashboard menu for non-admin users - Change app icons from yellow to blue (#3A7CA5) matching brand primary - Fix admin search bar width to match main header Made-with: Cursor
This commit is contained in:
15
.claude/settings.local.json
Normal file
15
.claude/settings.local.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(docker compose *)",
|
||||
"mcp__web-search-prime__web_search_prime",
|
||||
"WebSearch",
|
||||
"mcp__zread__search_doc",
|
||||
"mcp__zread__read_file",
|
||||
"Bash(npm ls *)",
|
||||
"mcp__zread__get_repo_structure",
|
||||
"Bash(npm install *)",
|
||||
"Bash(docker exec *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
9
.kilo/agent-manager.json
Normal file
9
.kilo/agent-manager.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"worktrees": {},
|
||||
"sessions": {},
|
||||
"tabOrder": {
|
||||
"local": [
|
||||
"pending:1"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
- "127.0.0.1:${POSTGRES_PORT:-5433}:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-memento}"]
|
||||
interval: 5s
|
||||
|
||||
@@ -51,3 +51,8 @@ Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
deploy.sh
|
||||
|
||||
# Stale service worker artifacts
|
||||
public/sw.js
|
||||
public/sw.js.map
|
||||
public/workbox-*.js
|
||||
|
||||
6
memento-note/.gitignore
vendored
6
memento-note/.gitignore
vendored
@@ -46,3 +46,9 @@ next-env.d.ts
|
||||
# generated
|
||||
/prisma/client-generated
|
||||
/_backup
|
||||
|
||||
# Service worker (generated at build time if PWA is re-enabled)
|
||||
public/sw.js
|
||||
public/sw.js.map
|
||||
public/workbox-*.js
|
||||
public/workbox-*.js.map
|
||||
|
||||
@@ -1,63 +1,72 @@
|
||||
# Multi-stage build for Next.js 16 with Webpack + Prisma
|
||||
# Using Debian 11 (bullseye) for native OpenSSL 1.1.x support
|
||||
|
||||
FROM node:22-bullseye-slim AS base
|
||||
|
||||
FROM base AS deps
|
||||
# ===========================================================================
|
||||
# Stage 1: Dependencies
|
||||
# ===========================================================================
|
||||
FROM node:22-bookworm-slim AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# Install OpenSSL (1.1.x native in Debian 11)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install --legacy-peer-deps
|
||||
COPY prisma ./prisma
|
||||
|
||||
FROM base AS builder
|
||||
RUN npm install
|
||||
RUN npx prisma generate
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 2: Build
|
||||
# ===========================================================================
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Copy Prisma schema and generate client BEFORE Next.js build
|
||||
COPY prisma ./prisma
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build Next.js with Webpack
|
||||
# PrismaClient validates DATABASE_URL format at import time.
|
||||
# No actual DB connection occurs during build (all pages are dynamic).
|
||||
ENV DATABASE_URL="postgresql://build:build@localhost:5432/build"
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runner
|
||||
# ===========================================================================
|
||||
# Stage 3: Runner
|
||||
# ===========================================================================
|
||||
FROM node:22-bookworm-slim AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd --system --gid 1001 nodejs
|
||||
RUN useradd --system --uid 1001 --gid nodejs nextjs
|
||||
|
||||
# Static assets
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/package-lock.json ./package-lock.json
|
||||
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Copy Next.js standalone output
|
||||
# Next.js standalone output
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
# Copy Prisma schema, generated client, and Query Engine binaries
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
RUN chown -R nextjs:nodejs /app/prisma
|
||||
# Prisma: schema + migrations + generated client + CLI
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/prisma ./node_modules/prisma
|
||||
|
||||
# Entrypoint
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
RUN chmod +x ./docker-entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft, TestTube } from 'lucide-react'
|
||||
import { AI_TESTER } from './ai-tester'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
@@ -14,11 +13,11 @@ export default function AITestPage() {
|
||||
<div className="container mx-auto py-10 px-4 max-w-6xl">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/admin/settings">
|
||||
<a href="/admin/settings">
|
||||
<Button variant="outline" size="icon">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</a>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold flex items-center gap-2">
|
||||
<TestTube className="h-8 w-8" />
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AdminMetrics } from '@/components/admin-metrics'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Zap, Settings, Activity, TrendingUp } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
|
||||
export default async function AdminAIPage() {
|
||||
@@ -63,12 +62,12 @@ export default async function AdminAIPage() {
|
||||
Monitor and configure AI features
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/admin/settings">
|
||||
<a href="/admin/settings">
|
||||
<Button variant="outline">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Configure
|
||||
</Button>
|
||||
</Link>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<AdminMetrics metrics={aiMetrics} />
|
||||
30
memento-note/app/(admin)/admin/error.tsx
Normal file
30
memento-note/app/(admin)/admin/error.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
export default function AdminError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.error('Admin route error:', error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl rounded-lg border border-red-200 bg-red-50 p-6 text-red-900 dark:border-red-900 dark:bg-red-950/30 dark:text-red-100">
|
||||
<h2 className="text-lg font-semibold">Une erreur est survenue dans l'administration</h2>
|
||||
<p className="mt-2 text-sm opacity-90">
|
||||
Le rendu de cette page a echoue. Vous pouvez reessayer sans recharger toute l'application.
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<Button type="button" onClick={reset}>
|
||||
Reessayer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
memento-note/app/(admin)/admin/layout.tsx
Normal file
22
memento-note/app/(admin)/admin/layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AdminHeader } from '@/components/admin-header'
|
||||
import { AdminSidebar } from '@/components/admin-sidebar'
|
||||
import { AdminContentArea } from '@/components/admin-content-area'
|
||||
|
||||
// Auth is enforced solely by middleware (auth.config.ts → authorized callback).
|
||||
// All cross-group navigation (admin ↔ main) uses <a> tags (full page reload)
|
||||
// to avoid React Error #310 caused by Next.js 16.x route-group transition bug.
|
||||
export default function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-white overflow-hidden h-screen flex flex-col">
|
||||
<AdminHeader />
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<AdminSidebar />
|
||||
<AdminContentArea>{children}</AdminContentArea>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -7,11 +7,8 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Combobox } from '@/components/ui/combobox'
|
||||
import { updateSystemConfig, testEmail } from '@/app/actions/admin-settings'
|
||||
import { getOllamaModels } from '@/app/actions/ollama'
|
||||
import { getCustomModels, getCustomEmbeddingModels } from '@/app/actions/custom-provider'
|
||||
import { toast } from 'sonner'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { TestTube, ExternalLink, RefreshCw } from 'lucide-react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
@@ -73,7 +70,8 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
const [isLoadingCustomEmbeddingsModels, setIsLoadingCustomEmbeddingsModels] = useState(false)
|
||||
const [isLoadingCustomChatModels, setIsLoadingCustomChatModels] = useState(false)
|
||||
|
||||
// Fetch Ollama models
|
||||
// Fetch Ollama models via Route API (not Server Action) to avoid App Router
|
||||
// action queue dispatch during render, which causes React Error #310.
|
||||
const fetchOllamaModels = useCallback(async (type: 'tags' | 'embeddings' | 'chat', url: string) => {
|
||||
if (!url) return
|
||||
|
||||
@@ -82,7 +80,9 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
else setIsLoadingChatModels(true)
|
||||
|
||||
try {
|
||||
const result = await getOllamaModels(url)
|
||||
const params = new URLSearchParams({ type: 'ollama', url })
|
||||
const res = await fetch(`/api/admin/models?${params}`)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.success) {
|
||||
if (type === 'tags') setOllamaTagsModels(result.models)
|
||||
@@ -101,7 +101,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Fetch Custom provider models — tags use /v1/models, embeddings use /v1/embeddings/models
|
||||
// Fetch Custom provider models via Route API (not Server Action).
|
||||
const fetchCustomModels = useCallback(async (type: 'tags' | 'embeddings' | 'chat', url: string, apiKey?: string) => {
|
||||
if (!url) return
|
||||
|
||||
@@ -110,9 +110,10 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
else setIsLoadingCustomChatModels(true)
|
||||
|
||||
try {
|
||||
const result = type === 'embeddings'
|
||||
? await getCustomEmbeddingModels(url, apiKey)
|
||||
: await getCustomModels(url, apiKey)
|
||||
const params = new URLSearchParams({ type: 'custom', url, kind: type })
|
||||
if (apiKey) params.set('key', apiKey)
|
||||
const res = await fetch(`/api/admin/models?${params}`)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.success && result.models.length > 0) {
|
||||
if (type === 'tags') setCustomTagsModels(result.models)
|
||||
@@ -131,47 +132,38 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Initial fetch for Ollama models if provider is selected
|
||||
// Single consolidated effect for initial model fetching.
|
||||
// Batching all provider checks into ONE effect prevents multiple Server Actions
|
||||
// from being dispatched simultaneously, which would cause React Error #310 by
|
||||
// flooding the App Router's action queue during an ongoing navigation transition.
|
||||
useEffect(() => {
|
||||
const fetchInitialModels = async () => {
|
||||
const ollamaBase = config.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
const customUrl = config.CUSTOM_OPENAI_BASE_URL || ''
|
||||
const customKey = config.CUSTOM_OPENAI_API_KEY || ''
|
||||
|
||||
if (tagsProvider === 'ollama') {
|
||||
const url = config.OLLAMA_BASE_URL_TAGS || config.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
fetchOllamaModels('tags', url)
|
||||
await fetchOllamaModels('tags', config.OLLAMA_BASE_URL_TAGS || ollamaBase)
|
||||
} else if (tagsProvider === 'custom' && customUrl) {
|
||||
await fetchCustomModels('tags', customUrl, customKey)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tagsProvider])
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddingsProvider === 'ollama') {
|
||||
const url = config.OLLAMA_BASE_URL_EMBEDDING || config.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
fetchOllamaModels('embeddings', url)
|
||||
} else if (embeddingsProvider === 'custom') {
|
||||
const url = config.CUSTOM_OPENAI_BASE_URL || ''
|
||||
const key = config.CUSTOM_OPENAI_API_KEY || ''
|
||||
if (url) fetchCustomModels('embeddings', url, key)
|
||||
await fetchOllamaModels('embeddings', config.OLLAMA_BASE_URL_EMBEDDING || ollamaBase)
|
||||
} else if (embeddingsProvider === 'custom' && customUrl) {
|
||||
await fetchCustomModels('embeddings', customUrl, customKey)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [embeddingsProvider])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagsProvider === 'custom') {
|
||||
const url = config.CUSTOM_OPENAI_BASE_URL || ''
|
||||
const key = config.CUSTOM_OPENAI_API_KEY || ''
|
||||
if (url) fetchCustomModels('tags', url, key)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tagsProvider])
|
||||
|
||||
useEffect(() => {
|
||||
if (chatProvider === 'ollama') {
|
||||
const url = config.OLLAMA_BASE_URL_CHAT || config.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
fetchOllamaModels('chat', url)
|
||||
} else if (chatProvider === 'custom') {
|
||||
const url = config.CUSTOM_OPENAI_BASE_URL || ''
|
||||
const key = config.CUSTOM_OPENAI_API_KEY || ''
|
||||
if (url) fetchCustomModels('chat', url, key)
|
||||
await fetchOllamaModels('chat', config.OLLAMA_BASE_URL_CHAT || ollamaBase)
|
||||
} else if (chatProvider === 'custom' && customUrl) {
|
||||
await fetchCustomModels('chat', customUrl, customKey)
|
||||
}
|
||||
}
|
||||
|
||||
fetchInitialModels()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [chatProvider])
|
||||
}, [])
|
||||
|
||||
const handleSaveSecurity = async (formData: FormData) => {
|
||||
setIsSaving(true)
|
||||
@@ -876,13 +868,13 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between pt-6">
|
||||
<Button type="submit" disabled={isSaving}>{isSaving ? t('admin.ai.saving') : t('admin.ai.saveSettings')}</Button>
|
||||
<Link href="/admin/ai-test">
|
||||
<a href="/admin/ai-test">
|
||||
<Button type="button" variant="outline" className="gap-2">
|
||||
<TestTube className="h-4 w-4" />
|
||||
{t('admin.ai.openTestPanel')}
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Button>
|
||||
</Link>
|
||||
</a>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
24
memento-note/app/(admin)/layout.tsx
Normal file
24
memento-note/app/(admin)/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { AdminProvidersWrapper } from '@/components/admin-providers-wrapper'
|
||||
import { detectUserLanguage } from '@/lib/i18n/detect-user-language'
|
||||
import { loadTranslations } from '@/lib/i18n/load-translations'
|
||||
|
||||
// No <Suspense> here intentionally: combining a Suspense boundary with <Link>
|
||||
// components inside the subtree triggers a React production-only bug (React
|
||||
// issue #33580, Next.js issue #63388) causing Error #310 "too many re-renders".
|
||||
export default async function AdminGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const initialLanguage = await detectUserLanguage()
|
||||
const initialTranslations = await loadTranslations(initialLanguage)
|
||||
|
||||
return (
|
||||
<AdminProvidersWrapper
|
||||
initialLanguage={initialLanguage}
|
||||
initialTranslations={initialTranslations}
|
||||
>
|
||||
{children}
|
||||
</AdminProvidersWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { AdminSidebar } from '@/components/admin-sidebar'
|
||||
import { AdminContentArea } from '@/components/admin-content-area'
|
||||
import { auth } from '@/auth'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default async function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const session = await auth()
|
||||
|
||||
if ((session?.user as any)?.role !== 'ADMIN') {
|
||||
redirect('/')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full bg-gray-50 dark:bg-zinc-950">
|
||||
<AdminSidebar />
|
||||
<AdminContentArea>{children}</AdminContentArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export default function AdminLoading() {
|
||||
return (
|
||||
<div className="space-y-6 animate-pulse">
|
||||
<div>
|
||||
<div className="h-9 w-48 bg-muted rounded-md mb-2" />
|
||||
<div className="h-4 w-72 bg-muted rounded-md" />
|
||||
</div>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="rounded-lg border border-border bg-white dark:bg-zinc-900 p-6 space-y-4">
|
||||
<div className="h-5 w-40 bg-muted rounded" />
|
||||
<div className="h-px bg-border" />
|
||||
<div className="space-y-3">
|
||||
<div className="h-4 w-full bg-muted rounded" />
|
||||
<div className="h-4 w-3/4 bg-muted rounded" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Suspense } from "react";
|
||||
import { HeaderWrapper } from "@/components/header-wrapper";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { ProvidersWrapper } from "@/components/providers-wrapper";
|
||||
@@ -28,7 +29,9 @@ export default async function MainLayout({
|
||||
{/* Main Layout */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Sidebar Navigation - Style Keep */}
|
||||
<Suspense fallback={<div className="w-64 flex-none hidden md:flex" />}>
|
||||
<Sidebar className="w-64 flex-none flex-col bg-white dark:bg-[#1e2128] border-e border-slate-200 dark:border-slate-800 overflow-y-auto hidden md:flex" user={session?.user} />
|
||||
</Suspense>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main className="flex min-h-0 flex-1 flex-col overflow-y-auto bg-background-light dark:bg-background-dark p-4 scroll-smooth">
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
export default function SettingsLoading() {
|
||||
return (
|
||||
<div className="space-y-6 animate-pulse">
|
||||
<div>
|
||||
<div className="h-9 w-64 bg-muted rounded-md mb-2" />
|
||||
<div className="h-4 w-96 bg-muted rounded-md" />
|
||||
</div>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="rounded-lg border border-border p-6 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-8 w-8 bg-muted rounded-full" />
|
||||
<div className="h-5 w-40 bg-muted rounded-md" />
|
||||
</div>
|
||||
<div className="h-px bg-border" />
|
||||
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/30">
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 w-32 bg-muted rounded" />
|
||||
<div className="h-3 w-56 bg-muted rounded" />
|
||||
</div>
|
||||
<div className="h-6 w-11 bg-muted rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -41,11 +41,15 @@ export async function register(prevState: string | undefined, formData: FormData
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const adminEmail = process.env.ADMIN_EMAIL?.toLowerCase();
|
||||
const role = adminEmail && email.toLowerCase() === adminEmail ? 'ADMIN' : 'USER';
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: email.toLowerCase(),
|
||||
password: hashedPassword,
|
||||
name,
|
||||
role,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
107
memento-note/app/api/admin/models/route.ts
Normal file
107
memento-note/app/api/admin/models/route.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
|
||||
async function requireAdmin() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id || (session.user as any).role !== 'ADMIN') return null
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/admin/models?type=ollama&url=<base_url>
|
||||
* GET /api/admin/models?type=custom&url=<base_url>&key=<api_key>&kind=tags|embeddings
|
||||
*
|
||||
* Route API (not a Server Action) for fetching AI model lists from Ollama or
|
||||
* OpenAI-compatible providers. Using a Route Handler instead of a Server Action
|
||||
* is the correct architecture for client-side GET requests: Server Actions are
|
||||
* for data mutations, and calling them from useEffect pushes items into the
|
||||
* App Router's internal action queue, which is drained during render (inside
|
||||
* AppRouter's useMemo), triggering React Error #310 when multiple calls stack up.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!(await requireAdmin())) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = request.nextUrl
|
||||
const type = searchParams.get('type')
|
||||
const rawUrl = searchParams.get('url') ?? ''
|
||||
const apiKey = searchParams.get('key') ?? undefined
|
||||
const kind = searchParams.get('kind') ?? 'tags'
|
||||
|
||||
if (!rawUrl) {
|
||||
return NextResponse.json({ success: false, models: [], error: 'url parameter is required' })
|
||||
}
|
||||
|
||||
const baseUrl = rawUrl.replace(/\/$/, '').replace(/\/v1$/, '')
|
||||
|
||||
try {
|
||||
if (type === 'ollama') {
|
||||
const res = await fetch(`${baseUrl}/api/tags`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
if (!res.ok) {
|
||||
return NextResponse.json({ success: false, models: [], error: `Ollama ${res.status}` })
|
||||
}
|
||||
const data = await res.json()
|
||||
const models: string[] = (data.models ?? []).map((m: { name: string }) => m.name)
|
||||
return NextResponse.json({ success: true, models })
|
||||
}
|
||||
|
||||
if (type === 'custom') {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
|
||||
|
||||
if (kind === 'embeddings') {
|
||||
// Try provider-specific embeddings endpoint first (e.g. OpenRouter)
|
||||
try {
|
||||
const embRes = await fetch(`${baseUrl}/v1/embeddings/models`, {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
})
|
||||
if (embRes.ok) {
|
||||
const embData = await embRes.json()
|
||||
const embModels: string[] = (embData.data ?? [])
|
||||
.map((m: { id: string }) => m.id)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
if (embModels.length > 0) {
|
||||
return NextResponse.json({ success: true, models: embModels })
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fall through to /v1/models with keyword filter
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(`${baseUrl}/v1/models`, {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
})
|
||||
if (!res.ok) {
|
||||
return NextResponse.json({ success: false, models: [], error: `Provider ${res.status}` })
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
let models: string[] = (data.data ?? [])
|
||||
.map((m: { id: string }) => m.id)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
|
||||
if (kind === 'embeddings') {
|
||||
const keywords = ['embed', 'embedding', 'ada', 'e5', 'bge', 'gte', 'minilm']
|
||||
const filtered = models.filter((id) =>
|
||||
keywords.some((kw) => id.toLowerCase().includes(kw))
|
||||
)
|
||||
if (filtered.length > 0) models = filtered
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, models })
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: false, models: [], error: `Unknown type: ${type}` })
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ success: false, models: [], error: err.message ?? 'Request failed' })
|
||||
}
|
||||
}
|
||||
12
memento-note/app/api/debug/client-error/route.ts
Normal file
12
memento-note/app/api/debug/client-error/route.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
// Visible directement dans `docker logs memento-web`
|
||||
console.error('[CLIENT-ERROR-REPORT]', JSON.stringify(body, null, 2))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { getAISettings } from "@/app/actions/ai-settings";
|
||||
import { getUserSettings } from "@/app/actions/user-settings";
|
||||
import { ThemeInitializer } from "@/components/theme-initializer";
|
||||
import { DirectionInitializer } from "@/components/direction-initializer";
|
||||
import { ErrorReporter } from "@/components/error-reporter";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
const inter = Inter({
|
||||
@@ -29,7 +30,7 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#f59e0b",
|
||||
themeColor: "#3A7CA5",
|
||||
};
|
||||
|
||||
function getHtmlClass(theme?: string): string {
|
||||
@@ -70,9 +71,12 @@ export default async function RootLayout({
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning className={getHtmlClass(userSettings.theme)}>
|
||||
<head />
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: `if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then(function(rs){rs.forEach(function(r){r.unregister()})})}` }} />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<SessionProviderWrapper>
|
||||
<ErrorReporter />
|
||||
<DirectionInitializer />
|
||||
<ThemeInitializer theme={userSettings.theme} fontSize={aiSettings.fontSize} />
|
||||
{children}
|
||||
|
||||
141
memento-note/components/admin-header.tsx
Normal file
141
memento-note/components/admin-header.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
|
||||
import { signOut, useSession } from 'next-auth/react'
|
||||
import { Shield, Search, Settings, LogOut, User, StickyNote, MessageSquare, FlaskConical, Bot } from 'lucide-react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { NotificationPanel } from './notification-panel'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
/**
|
||||
* Admin header — visuellement identique au Header principal.
|
||||
* Utilise exclusivement des <a> (rechargement complet) au lieu de <Link>
|
||||
* pour éviter React Error #310 (bug React #33580 / Next.js #63388).
|
||||
*/
|
||||
export function AdminHeader() {
|
||||
const { data: session } = useSession()
|
||||
const { t } = useLanguage()
|
||||
|
||||
const user = session?.user
|
||||
const initial = user?.name
|
||||
? user.name.charAt(0).toUpperCase()
|
||||
: user?.email?.[0]?.toUpperCase() ?? '?'
|
||||
|
||||
return (
|
||||
<header className="flex-none flex items-center justify-between whitespace-nowrap border-b border-solid border-slate-200 dark:border-slate-800 bg-white dark:bg-[#1e2128] px-6 py-3 z-20">
|
||||
{/* ── Logo + Search ── */}
|
||||
<div className="flex items-center gap-8">
|
||||
<a href="/" className="flex items-center gap-3 text-slate-900 dark:text-white group no-underline">
|
||||
<div className="size-8 bg-primary rounded-lg flex items-center justify-center text-primary-foreground shadow-sm group-hover:shadow-md transition-all">
|
||||
<StickyNote className="w-5 h-5" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold leading-tight tracking-tight">MEMENTO</h2>
|
||||
</a>
|
||||
|
||||
{/* Badge Admin */}
|
||||
<span className="hidden sm:flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-primary/10 text-primary text-xs font-semibold">
|
||||
<Shield className="h-3 w-3" />
|
||||
Admin
|
||||
</span>
|
||||
|
||||
{/* Search (décoratif en mode admin) — même taille que l'entête principale */}
|
||||
<label className="hidden md:flex flex-col min-w-40 w-96 !h-10">
|
||||
<div className="flex w-full flex-1 items-stretch rounded-full h-full bg-slate-100 dark:bg-slate-800 border border-transparent">
|
||||
<div className="text-slate-400 dark:text-slate-400 flex items-center justify-center pl-4">
|
||||
<Search className="w-4 h-4" />
|
||||
</div>
|
||||
<input
|
||||
className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden bg-transparent border-none text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 text-sm focus:ring-0 focus:outline-none"
|
||||
placeholder={t('search.placeholder') || 'Rechercher…'}
|
||||
type="text"
|
||||
disabled
|
||||
aria-label="Recherche désactivée en mode admin"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* ── Droite : nav + notifs + settings + avatar ── */}
|
||||
<div className="flex flex-1 justify-end gap-2 items-center">
|
||||
{/* Nav pills — toutes en <a> pour éviter la RSC race condition */}
|
||||
<div className="hidden md:flex items-center gap-1 bg-slate-100 dark:bg-slate-800/60 rounded-full px-1.5 py-1">
|
||||
<a
|
||||
href="/chat"
|
||||
className="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<MessageSquare className="h-3.5 w-3.5" />
|
||||
<span>{t('nav.chat') || 'AI Chat'}</span>
|
||||
</a>
|
||||
<a
|
||||
href="/agents"
|
||||
className="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<Bot className="h-3.5 w-3.5" />
|
||||
<span>{t('nav.agents') || 'Agents'}</span>
|
||||
</a>
|
||||
<a
|
||||
href="/lab"
|
||||
className="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<FlaskConical className="h-3.5 w-3.5" />
|
||||
<span>{t('nav.lab') || 'The Lab'}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<NotificationPanel />
|
||||
|
||||
{/* Settings */}
|
||||
<a
|
||||
href="/settings"
|
||||
className="flex items-center justify-center size-10 rounded-full hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 transition-colors"
|
||||
aria-label="Paramètres"
|
||||
>
|
||||
<Settings className="w-5 h-5" />
|
||||
</a>
|
||||
|
||||
{/* Avatar + menu */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
className="flex items-center justify-center bg-center bg-no-repeat bg-cover rounded-full size-10 ring-2 ring-white dark:ring-slate-700 cursor-pointer shadow-sm hover:shadow-md transition-shadow bg-primary/10 dark:bg-primary/20 text-primary dark:text-primary-foreground"
|
||||
style={user?.image ? { backgroundImage: `url(${(user as any).image})` } : undefined}
|
||||
>
|
||||
{!user?.image && (
|
||||
<span className="text-sm font-semibold">{initial}</span>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<div className="flex items-center justify-start gap-2 p-2">
|
||||
<div className="flex flex-col space-y-1 leading-none">
|
||||
{user?.name && <p className="font-medium">{user.name}</p>}
|
||||
{user?.email && <p className="w-[200px] truncate text-sm text-muted-foreground">{user.email}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<a href="/settings/profile">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>{t('settings.profile') || 'Profile'}</span>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
className="cursor-pointer text-red-600 focus:text-red-600"
|
||||
>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>{t('auth.signOut') || 'Se déconnecter'}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
26
memento-note/components/admin-providers-wrapper.tsx
Normal file
26
memento-note/components/admin-providers-wrapper.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import { LanguageProvider } from '@/lib/i18n/LanguageProvider'
|
||||
import type { Translations } from '@/lib/i18n/load-translations'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface AdminProvidersWrapperProps {
|
||||
children: ReactNode
|
||||
initialLanguage?: string
|
||||
initialTranslations?: Translations
|
||||
}
|
||||
|
||||
export function AdminProvidersWrapper({
|
||||
children,
|
||||
initialLanguage = 'en',
|
||||
initialTranslations,
|
||||
}: AdminProvidersWrapperProps) {
|
||||
return (
|
||||
<LanguageProvider
|
||||
initialLanguage={initialLanguage as any}
|
||||
initialTranslations={initialTranslations}
|
||||
>
|
||||
{children}
|
||||
</LanguageProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { LayoutDashboard, Users, Brain, Settings } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -55,7 +54,10 @@ export function AdminSidebar({ className }: AdminSidebarProps) {
|
||||
const isActive = pathname === item.href || (item.href !== '/admin' && pathname?.startsWith(item.href))
|
||||
|
||||
return (
|
||||
<Link
|
||||
// <a> instead of <Link>: avoids Next.js RSC navigation transitions
|
||||
// that trigger React Error #310 (React bug #33580) in production.
|
||||
// Full-page reloads are acceptable for admin navigation.
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
@@ -68,7 +70,7 @@ export function AdminSidebar({ className }: AdminSidebarProps) {
|
||||
>
|
||||
{item.icon}
|
||||
<span>{t(item.titleKey)}</span>
|
||||
</Link>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
53
memento-note/components/error-reporter.tsx
Normal file
53
memento-note/components/error-reporter.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* Captures unhandled client-side errors and forwards them to the server
|
||||
* so they appear in `docker logs memento-web`. Loaded in the root layout.
|
||||
*/
|
||||
export function ErrorReporter() {
|
||||
useEffect(() => {
|
||||
function report(payload: object) {
|
||||
fetch('/api/debug/client-error', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
keepalive: true,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function onError(event: ErrorEvent) {
|
||||
report({
|
||||
type: 'uncaught-error',
|
||||
message: event.message,
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno,
|
||||
stack: event.error?.stack ?? null,
|
||||
url: window.location.href,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
function onUnhandledRejection(event: PromiseRejectionEvent) {
|
||||
const reason = event.reason
|
||||
report({
|
||||
type: 'unhandled-rejection',
|
||||
message: reason?.message ?? String(reason),
|
||||
stack: reason?.stack ?? null,
|
||||
url: window.location.href,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('error', onError)
|
||||
window.addEventListener('unhandledrejection', onUnhandledRejection)
|
||||
return () => {
|
||||
window.removeEventListener('error', onError)
|
||||
window.removeEventListener('unhandledrejection', onUnhandledRejection)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export function Header({
|
||||
|
||||
const noSidebarMode = ['/agents', '/chat', '/lab'].some(r => pathname.startsWith(r))
|
||||
|
||||
|
||||
// Track last pushed search to avoid infinite loops
|
||||
const lastPushedSearch = useRef<string | null>(null)
|
||||
|
||||
@@ -421,12 +422,16 @@ export function Header({
|
||||
<span>{t('settings.profile') || 'Profile'}</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{(currentUser as any)?.role === 'ADMIN' && (
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link href="/admin">
|
||||
{/* Force hard reload: client-side navigation between (main) and (admin)
|
||||
route groups triggers React #310 in Next.js 16.x (framework bug). */}
|
||||
<a href="/admin">
|
||||
<Shield className="mr-2 h-4 w-4" />
|
||||
<span>{t('nav.adminDashboard')}</span>
|
||||
</Link>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => signOut()} className="cursor-pointer text-red-600 focus:text-red-600">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>{t('auth.signOut') || 'Sign out'}</span>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@/components/ui/popover'
|
||||
import { getPendingShareRequests, respondToShareRequest } from '@/app/actions/notes'
|
||||
import { toast } from 'sonner'
|
||||
import { useNoteRefresh } from '@/context/NoteRefreshContext'
|
||||
import { useNoteRefreshOptional } from '@/context/NoteRefreshContext'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
@@ -36,7 +36,7 @@ interface ShareRequest {
|
||||
}
|
||||
|
||||
export function NotificationPanel() {
|
||||
const { triggerRefresh } = useNoteRefresh()
|
||||
const { triggerRefresh } = useNoteRefreshOptional()
|
||||
const { t } = useLanguage()
|
||||
const [requests, setRequests] = useState<ShareRequest[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useHomeViewOptional } from '@/context/home-view-context'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getTrashCount } from '@/app/actions/notes'
|
||||
|
||||
const HIDDEN_ROUTES = ['/agents', '/chat', '/lab']
|
||||
const HIDDEN_ROUTES = ['/agents', '/chat', '/lab', '/admin']
|
||||
|
||||
export function Sidebar({ className, user }: { className?: string, user?: any }) {
|
||||
const pathname = usePathname()
|
||||
@@ -36,10 +36,15 @@ export function Sidebar({ className, user }: { className?: string, user?: any })
|
||||
const homeBridge = useHomeViewOptional()
|
||||
const [trashCount, setTrashCount] = useState(0)
|
||||
|
||||
// Fetch trash count
|
||||
const searchKey = searchParams.toString()
|
||||
|
||||
// Fetch trash count — skip for hidden/admin routes to avoid dispatching a
|
||||
// Server Action during an ongoing App Router navigation transition, which
|
||||
// would increment React's nested-update counter and trigger Error #310.
|
||||
useEffect(() => {
|
||||
if (HIDDEN_ROUTES.some(r => pathname.startsWith(r))) return
|
||||
getTrashCount().then(setTrashCount)
|
||||
}, [pathname, searchParams])
|
||||
}, [pathname, searchKey])
|
||||
|
||||
// Hide sidebar on Agents, Chat IA and Lab routes
|
||||
if (HIDDEN_ROUTES.some(r => pathname.startsWith(r))) return null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
||||
import { createContext, useContext, useState, useEffect, useCallback, useMemo, ReactNode } from 'react'
|
||||
import { LabelColorName, LABEL_COLORS } from '@/lib/types'
|
||||
import { getHashColor } from '@/lib/utils'
|
||||
|
||||
@@ -32,13 +32,12 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [notebookId, setNotebookId] = useState<string | null>(null)
|
||||
|
||||
const fetchLabels = async () => {
|
||||
const fetchLabels = useCallback(async (nbId: string | null) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
// Build URL with notebookId filter
|
||||
const url = new URL('/api/labels', window.location.origin)
|
||||
if (notebookId) {
|
||||
url.searchParams.set('notebookId', notebookId)
|
||||
if (nbId) {
|
||||
url.searchParams.set('notebookId', nbId)
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
@@ -54,14 +53,13 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Re-fetch labels when notebookId changes
|
||||
useEffect(() => {
|
||||
fetchLabels()
|
||||
}, [notebookId])
|
||||
fetchLabels(notebookId)
|
||||
}, [notebookId, fetchLabels])
|
||||
|
||||
const addLabel = async (name: string, color?: LabelColorName, labelNotebookId?: string | null) => {
|
||||
const addLabel = useCallback(async (name: string, color?: LabelColorName, labelNotebookId?: string | null) => {
|
||||
try {
|
||||
const labelColor = color || getHashColor(name);
|
||||
const finalNotebookId = labelNotebookId || notebookId
|
||||
@@ -79,9 +77,9 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
console.error('Failed to add label:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}, [notebookId])
|
||||
|
||||
const updateLabel = async (id: string, updates: Partial<Pick<Label, 'name' | 'color'>>) => {
|
||||
const updateLabel = useCallback(async (id: string, updates: Partial<Pick<Label, 'name' | 'color'>>) => {
|
||||
try {
|
||||
const response = await fetch(`/api/labels/${id}`, {
|
||||
method: 'PUT',
|
||||
@@ -98,9 +96,9 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
console.error('Failed to update label:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const deleteLabel = async (id: string) => {
|
||||
const deleteLabel = useCallback(async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/labels/${id}`, {
|
||||
method: 'DELETE',
|
||||
@@ -112,18 +110,18 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
console.error('Failed to delete label:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getLabelColorHelper = (name: string): LabelColorName => {
|
||||
const getLabelColor = useCallback((name: string): LabelColorName => {
|
||||
const label = labels.find(l => l.name.toLowerCase() === name.toLowerCase())
|
||||
return label?.color || 'gray'
|
||||
}
|
||||
}, [labels])
|
||||
|
||||
const refreshLabels = async () => {
|
||||
await fetchLabels()
|
||||
}
|
||||
const refreshLabels = useCallback(async () => {
|
||||
await fetchLabels(notebookId)
|
||||
}, [fetchLabels, notebookId])
|
||||
|
||||
const value: LabelContextType = {
|
||||
const value = useMemo<LabelContextType>(() => ({
|
||||
labels,
|
||||
loading,
|
||||
notebookId,
|
||||
@@ -131,9 +129,9 @@ export function LabelProvider({ children }: { children: ReactNode }) {
|
||||
addLabel,
|
||||
updateLabel,
|
||||
deleteLabel,
|
||||
getLabelColor: getLabelColorHelper,
|
||||
getLabelColor,
|
||||
refreshLabels,
|
||||
}
|
||||
}), [labels, loading, notebookId, addLabel, updateLabel, deleteLabel, getLabelColor, refreshLabels])
|
||||
|
||||
return <LabelContext.Provider value={value}>{children}</LabelContext.Provider>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, useCallback } from 'react'
|
||||
import { createContext, useContext, useState, useCallback, useMemo } from 'react'
|
||||
|
||||
interface NoteRefreshContextType {
|
||||
refreshKey: number
|
||||
@@ -16,8 +16,10 @@ export function NoteRefreshProvider({ children }: { children: React.ReactNode })
|
||||
setRefreshKey(prev => prev + 1)
|
||||
}, [])
|
||||
|
||||
const value = useMemo(() => ({ refreshKey, triggerRefresh }), [refreshKey, triggerRefresh])
|
||||
|
||||
return (
|
||||
<NoteRefreshContext.Provider value={{ refreshKey, triggerRefresh }}>
|
||||
<NoteRefreshContext.Provider value={value}>
|
||||
{children}
|
||||
</NoteRefreshContext.Provider>
|
||||
)
|
||||
@@ -30,3 +32,12 @@ export function useNoteRefresh() {
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as useNoteRefresh but tolerates being called outside the provider
|
||||
* (e.g. shared header rendered in admin pages). Returns a no-op when absent.
|
||||
*/
|
||||
export function useNoteRefreshOptional(): NoteRefreshContextType {
|
||||
const context = useContext(NoteRefreshContext)
|
||||
return context ?? { refreshKey: 0, triggerRefresh: () => {} }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
|
||||
import { createContext, useContext, useState, useCallback, useMemo, ReactNode } from 'react'
|
||||
|
||||
interface NotebookDragContextValue {
|
||||
draggedNoteId: string | null
|
||||
@@ -46,9 +46,7 @@ export function NotebookDragProvider({ children }: NotebookDragProviderProps) {
|
||||
const isDragging = draggedNoteId !== null
|
||||
const isDragOver = dragOverNotebookId !== null
|
||||
|
||||
return (
|
||||
<NotebookDragContext.Provider
|
||||
value={{
|
||||
const value = useMemo(() => ({
|
||||
draggedNoteId,
|
||||
dragOverNotebookId,
|
||||
startDrag,
|
||||
@@ -56,8 +54,10 @@ export function NotebookDragProvider({ children }: NotebookDragProviderProps) {
|
||||
dragOver,
|
||||
isDragging,
|
||||
isDragOver,
|
||||
}}
|
||||
>
|
||||
}), [draggedNoteId, dragOverNotebookId, startDrag, endDrag, dragOver, isDragging, isDragOver])
|
||||
|
||||
return (
|
||||
<NotebookDragContext.Provider value={value}>
|
||||
{children}
|
||||
</NotebookDragContext.Provider>
|
||||
)
|
||||
|
||||
8
memento-note/docker-entrypoint.sh
Normal file
8
memento-note/docker-entrypoint.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Running Prisma migrations..."
|
||||
node ./node_modules/prisma/build/index.js migrate deploy
|
||||
|
||||
echo "Starting server..."
|
||||
exec node server.js
|
||||
@@ -1,16 +1,36 @@
|
||||
import prisma from './prisma'
|
||||
|
||||
// Environment variable fallbacks for system config keys
|
||||
const ENV_FALLBACKS: Record<string, string> = {
|
||||
AI_PROVIDER_TAGS: process.env.AI_PROVIDER_TAGS || '',
|
||||
AI_MODEL_TAGS: process.env.AI_MODEL_TAGS || '',
|
||||
AI_PROVIDER_EMBEDDING: process.env.AI_PROVIDER_EMBEDDING || '',
|
||||
AI_MODEL_EMBEDDING: process.env.AI_MODEL_EMBEDDING || '',
|
||||
AI_PROVIDER_CHAT: process.env.AI_PROVIDER_CHAT || '',
|
||||
AI_MODEL_CHAT: process.env.AI_MODEL_CHAT || '',
|
||||
OLLAMA_BASE_URL: process.env.OLLAMA_BASE_URL || '',
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
|
||||
CUSTOM_OPENAI_API_KEY: process.env.CUSTOM_OPENAI_API_KEY || '',
|
||||
CUSTOM_OPENAI_BASE_URL: process.env.CUSTOM_OPENAI_BASE_URL || '',
|
||||
ALLOW_REGISTRATION: process.env.ALLOW_REGISTRATION || '',
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL || '',
|
||||
}
|
||||
|
||||
export async function getSystemConfig() {
|
||||
let dbConfig: Record<string, string> = {}
|
||||
try {
|
||||
const configs = await prisma.systemConfig.findMany()
|
||||
return configs.reduce((acc, conf) => {
|
||||
dbConfig = configs.reduce((acc, conf) => {
|
||||
acc[conf.key] = conf.value
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
} catch (e) {
|
||||
console.error('Failed to load system config from DB:', e)
|
||||
return {}
|
||||
}
|
||||
|
||||
// Merge: DB values take precedence, env vars as fallback
|
||||
const merged = { ...ENV_FALLBACKS, ...dbConfig }
|
||||
return merged
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useEffect, useState, useCallback, useRef } from 'react'
|
||||
import { createContext, useContext, useEffect, useState, useCallback, useRef, useMemo } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { SupportedLanguage, loadTranslations, getTranslationValue, Translations } from './load-translations'
|
||||
|
||||
@@ -97,8 +97,10 @@ export function LanguageProvider({ children, initialLanguage = 'en', initialTran
|
||||
return typeof value === 'string' ? value : key
|
||||
}, [translations])
|
||||
|
||||
const value = useMemo(() => ({ language, setLanguage, t, translations }), [language, setLanguage, t, translations])
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={{ language, setLanguage, t, translations }}>
|
||||
<LanguageContext.Provider value={value}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
)
|
||||
|
||||
@@ -4,9 +4,6 @@ const nextConfig: NextConfig = {
|
||||
// Enable standalone output for Docker
|
||||
output: 'standalone',
|
||||
|
||||
// Optimize for production
|
||||
reactStrictMode: true,
|
||||
|
||||
// Image optimization (enabled for better performance)
|
||||
images: {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
@@ -15,7 +12,12 @@ const nextConfig: NextConfig = {
|
||||
// Hide the "compiling" indicator
|
||||
devIndicators: false,
|
||||
|
||||
turbopack: {},
|
||||
// Disable strict mode: React 19 strict mode can cause double-invocation of render
|
||||
// functions during concurrent transitions, amplifying timing issues.
|
||||
reactStrictMode: false,
|
||||
|
||||
// TEMP: disable Turbopack due React #310 loop on /admin routes in production builds.
|
||||
// We keep webpack pipeline until upstream fix is confirmed.
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
543
memento-note/package-lock.json
generated
543
memento-note/package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@excalidraw/excalidraw": "^0.18.0",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
@@ -54,6 +55,7 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"resend": "^6.12.0",
|
||||
"rss-parser": "^3.13.0",
|
||||
"sharp": "^0.34.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tinyld": "^1.3.4",
|
||||
@@ -62,7 +64,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
@@ -474,6 +475,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
@@ -520,6 +522,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
@@ -541,6 +544,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -577,28 +581,6 @@
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
@@ -1606,7 +1588,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -1694,9 +1675,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1713,9 +1691,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1732,9 +1707,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1751,9 +1723,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1770,9 +1739,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1789,9 +1755,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1808,9 +1771,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1827,9 +1787,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1846,9 +1803,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1871,9 +1825,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1896,9 +1847,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1921,9 +1869,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1946,9 +1891,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1971,9 +1913,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1996,9 +1935,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2021,9 +1957,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2247,9 +2180,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2266,9 +2196,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2285,9 +2212,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2304,9 +2228,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2376,12 +2297,337 @@
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"node-addon-api": "^7.0.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||
"@parcel/watcher-win32-x64": "2.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher/node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
|
||||
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.59.1"
|
||||
},
|
||||
@@ -2398,6 +2644,7 @@
|
||||
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
},
|
||||
@@ -5360,9 +5607,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5380,9 +5624,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5400,9 +5641,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5420,9 +5658,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5440,9 +5675,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5460,9 +5692,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5508,6 +5737,18 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
||||
@@ -5714,9 +5955,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5734,9 +5972,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5754,9 +5989,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5774,9 +6006,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -6268,6 +6497,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -6278,6 +6508,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -6332,6 +6563,7 @@
|
||||
"integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.1.4",
|
||||
@@ -6671,6 +6903,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
@@ -6842,6 +7075,7 @@
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
|
||||
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "11.0.3",
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
@@ -7077,6 +7311,7 @@
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz",
|
||||
"integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
@@ -7486,6 +7721,7 @@
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -7676,7 +7912,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -8540,6 +8775,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.11.0.tgz",
|
||||
"integrity": "sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
@@ -8838,9 +9074,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8862,9 +9095,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8886,9 +9116,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8910,9 +9137,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9455,6 +9679,7 @@
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz",
|
||||
"integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "12.0.0",
|
||||
"@chevrotain/gast": "12.0.0",
|
||||
@@ -10256,6 +10481,14 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.37",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
|
||||
@@ -10267,6 +10500,7 @@
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
|
||||
"integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
|
||||
"license": "MIT-0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@@ -10586,6 +10820,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -10620,6 +10855,7 @@
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
@@ -10641,6 +10877,7 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.22.0"
|
||||
},
|
||||
@@ -10658,6 +10895,7 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -10863,6 +11101,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -10872,6 +11111,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -11265,7 +11505,6 @@
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -11280,7 +11519,6 @@
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.0.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
@@ -11537,7 +11775,8 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.3.tgz",
|
||||
"integrity": "sha512-fA/NX5gMf0ooCLISgB0wScaWgaj6rjTN2SVAwleURjiya7ITNkV+VMmoHtKkldP6CIZoYCZyxb8zP/e2TWoEtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.2",
|
||||
@@ -11622,6 +11861,7 @@
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -12104,6 +12344,7 @@
|
||||
"integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.4",
|
||||
"@vitest/mocker": "4.1.4",
|
||||
@@ -12215,6 +12456,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -12230,6 +12488,14 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/immutable": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
|
||||
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/vitest/node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
@@ -12243,12 +12509,28 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/vite": {
|
||||
"version": "8.0.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
|
||||
"integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
@@ -12517,6 +12799,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -71,14 +71,15 @@
|
||||
"resend": "^6.12.0",
|
||||
"rss-parser": "^3.13.0",
|
||||
"sonner": "^2.0.7",
|
||||
"sharp": "^0.34.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"vazirmatn": "^33.0.3",
|
||||
"zod": "^4.3.5"
|
||||
"zod": "^4.3.5",
|
||||
"@prisma/client": "^5.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
-- AlterTable: User
|
||||
ALTER TABLE "User" ADD COLUMN "cardSizeMode" TEXT NOT NULL DEFAULT 'variable';
|
||||
|
||||
-- AlterTable: Note
|
||||
ALTER TABLE "Note" ADD COLUMN "trashedAt" TIMESTAMP(3);
|
||||
ALTER TABLE "Note" ALTER COLUMN "checkItems" DROP DEFAULT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "checkItems" TYPE TEXT USING "checkItems"::TEXT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "labels" DROP DEFAULT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "labels" TYPE TEXT USING "labels"::TEXT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "images" DROP DEFAULT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "images" TYPE TEXT USING "images"::TEXT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "links" DROP DEFAULT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "links" TYPE TEXT USING "links"::TEXT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "sharedWith" DROP DEFAULT;
|
||||
ALTER TABLE "Note" ALTER COLUMN "sharedWith" TYPE TEXT USING "sharedWith"::TEXT;
|
||||
ALTER TABLE "Note" DROP COLUMN "embedding";
|
||||
|
||||
-- AlterTable: AiFeedback
|
||||
ALTER TABLE "AiFeedback" ALTER COLUMN "metadata" DROP DEFAULT;
|
||||
ALTER TABLE "AiFeedback" ALTER COLUMN "metadata" TYPE TEXT USING "metadata"::TEXT;
|
||||
|
||||
-- CreateTable: NoteEmbedding
|
||||
CREATE TABLE "NoteEmbedding" (
|
||||
"id" TEXT NOT NULL,
|
||||
"noteId" TEXT NOT NULL,
|
||||
"embedding" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "NoteEmbedding_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "NoteEmbedding_noteId_key" ON "NoteEmbedding"("noteId");
|
||||
CREATE INDEX "NoteEmbedding_noteId_idx" ON "NoteEmbedding"("noteId");
|
||||
|
||||
-- CreateTable: Agent
|
||||
CREATE TABLE "Agent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" TEXT DEFAULT 'scraper',
|
||||
"role" TEXT NOT NULL,
|
||||
"sourceUrls" TEXT,
|
||||
"frequency" TEXT NOT NULL DEFAULT 'manual',
|
||||
"lastRun" TIMESTAMP(3),
|
||||
"nextRun" TIMESTAMP(3),
|
||||
"isEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"targetNotebookId" TEXT,
|
||||
"sourceNotebookId" TEXT,
|
||||
"tools" TEXT DEFAULT '[]',
|
||||
"maxSteps" INTEGER NOT NULL DEFAULT 10,
|
||||
"notifyEmail" BOOLEAN NOT NULL DEFAULT false,
|
||||
"includeImages" BOOLEAN NOT NULL DEFAULT false,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Agent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "Agent_userId_idx" ON "Agent"("userId");
|
||||
CREATE INDEX "Agent_isEnabled_idx" ON "Agent"("isEnabled");
|
||||
|
||||
-- CreateTable: AgentAction
|
||||
CREATE TABLE "AgentAction" (
|
||||
"id" TEXT NOT NULL,
|
||||
"agentId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"result" TEXT,
|
||||
"log" TEXT,
|
||||
"input" TEXT,
|
||||
"toolLog" TEXT,
|
||||
"tokensUsed" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AgentAction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "AgentAction_agentId_idx" ON "AgentAction"("agentId");
|
||||
|
||||
-- CreateTable: Conversation
|
||||
CREATE TABLE "Conversation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT,
|
||||
"userId" TEXT NOT NULL,
|
||||
"notebookId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Conversation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "Conversation_userId_idx" ON "Conversation"("userId");
|
||||
CREATE INDEX "Conversation_notebookId_idx" ON "Conversation"("notebookId");
|
||||
|
||||
-- CreateTable: ChatMessage
|
||||
CREATE TABLE "ChatMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"conversationId" TEXT NOT NULL,
|
||||
"role" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ChatMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "ChatMessage_conversationId_idx" ON "ChatMessage"("conversationId");
|
||||
|
||||
-- CreateTable: Canvas
|
||||
CREATE TABLE "Canvas" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"data" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Canvas_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "Canvas_userId_idx" ON "Canvas"("userId");
|
||||
|
||||
-- CreateTable: Workflow
|
||||
CREATE TABLE "Workflow" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"graph" TEXT NOT NULL DEFAULT '{"nodes":[],"edges":[]}',
|
||||
"isEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"userId" TEXT NOT NULL,
|
||||
"notebookId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Workflow_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "Workflow_userId_idx" ON "Workflow"("userId");
|
||||
CREATE INDEX "Workflow_isEnabled_idx" ON "Workflow"("isEnabled");
|
||||
|
||||
-- CreateTable: WorkflowRun
|
||||
CREATE TABLE "WorkflowRun" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workflowId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'running',
|
||||
"log" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "WorkflowRun_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "WorkflowRun_workflowId_idx" ON "WorkflowRun"("workflowId");
|
||||
CREATE INDEX "WorkflowRun_status_idx" ON "WorkflowRun"("status");
|
||||
|
||||
-- AddForeignKey: NoteEmbedding
|
||||
ALTER TABLE "NoteEmbedding" ADD CONSTRAINT "NoteEmbedding_noteId_fkey" FOREIGN KEY ("noteId") REFERENCES "Note"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: Agent
|
||||
ALTER TABLE "Agent" ADD CONSTRAINT "Agent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Agent" ADD CONSTRAINT "Agent_targetNotebookId_fkey" FOREIGN KEY ("targetNotebookId") REFERENCES "Notebook"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: AgentAction
|
||||
ALTER TABLE "AgentAction" ADD CONSTRAINT "AgentAction_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: Conversation
|
||||
ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_notebookId_fkey" FOREIGN KEY ("notebookId") REFERENCES "Notebook"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: ChatMessage
|
||||
ALTER TABLE "ChatMessage" ADD CONSTRAINT "ChatMessage_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: Canvas
|
||||
ALTER TABLE "Canvas" ADD CONSTRAINT "Canvas_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: Workflow
|
||||
ALTER TABLE "Workflow" ADD CONSTRAINT "Workflow_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Workflow" ADD CONSTRAINT "Workflow_notebookId_fkey" FOREIGN KEY ("notebookId") REFERENCES "Notebook"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey: WorkflowRun
|
||||
ALTER TABLE "WorkflowRun" ADD CONSTRAINT "WorkflowRun_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Add missing Note index
|
||||
CREATE INDEX "Note_trashedAt_idx" ON "Note"("trashedAt");
|
||||
@@ -1,6 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["debian-openssl-1.1.x", "native"]
|
||||
binaryTargets = ["debian-openssl-3.0.x", "native"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192">
|
||||
<rect width="192" height="192" fill="#f59e0b" rx="40"/>
|
||||
<path d="M50 50 h92 v92 h-92 z" fill="#fff"/>
|
||||
<rect width="192" height="192" fill="#3A7CA5" rx="40"/>
|
||||
<g transform="translate(96,96)">
|
||||
<rect x="-34" y="-34" width="68" height="68" rx="5" fill="white"/>
|
||||
<path d="M34,34 L34,11 L11,34 Z" fill="#3A7CA5" opacity="0.3"/>
|
||||
<line x1="-23" y1="-17" x2="23" y2="-17" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="-23" y1="-4" x2="23" y2="-4" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="-23" y1="9" x2="11" y2="9" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 615 B |
@@ -1,5 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" fill="#f59e0b" rx="100"/>
|
||||
<path d="M150 150 h212 v212 h-212 z" fill="#fff"/>
|
||||
<path d="M150 150 h212" stroke="#fff3cd" stroke-width="20"/>
|
||||
<rect width="512" height="512" fill="#3A7CA5" rx="100"/>
|
||||
<g transform="translate(256,256)">
|
||||
<rect x="-90" y="-90" width="180" height="180" rx="12" fill="white"/>
|
||||
<path d="M90,90 L90,30 L30,90 Z" fill="#3A7CA5" opacity="0.3"/>
|
||||
<line x1="-60" y1="-45" x2="60" y2="-45" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="-60" y1="-10" x2="60" y2="-10" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="-60" y1="25" x2="30" y2="25" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 625 B |
@@ -5,7 +5,7 @@
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#f59e0b",
|
||||
"theme_color": "#3A7CA5",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192.svg",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user