diff --git a/.env.example b/.env.example
index 9cf18b6..4374db3 100644
--- a/.env.example
+++ b/.env.example
@@ -10,7 +10,6 @@ OPENAI_API_KEY=your-openai-api-key-here
# Optional overrides specifically for ra-h orchestration/delegation
RAH_ORCHESTRATOR_ANTHROPIC_API_KEY=
RAH_DELEGATE_OPENAI_API_KEY=
-RAH_WISE_RAH_OPENAI_API_KEY= # Optional: GPT-5 for wise ra-h (falls back to OPENAI_API_KEY)
# Voice TTS defaults
RAH_TTS_MODEL=gpt-4o-mini-tts
diff --git a/app/api/analytics/usage/route.ts b/app/api/analytics/usage/route.ts
deleted file mode 100644
index 98fce2a..0000000
--- a/app/api/analytics/usage/route.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { CostAnalytics } from '@/services/analytics/costs';
-
-export async function GET(request: NextRequest) {
- try {
- const searchParams = request.nextUrl.searchParams;
- const period = searchParams.get('period') || 'week';
- const agent = searchParams.get('agent');
- const traceId = searchParams.get('trace_id');
- const startDate = searchParams.get('start_date');
- const endDate = searchParams.get('end_date');
-
- if (traceId) {
- const traceCosts = CostAnalytics.getCostsByTrace(traceId);
- return NextResponse.json(traceCosts);
- }
-
- if (agent) {
- const agentCosts = CostAnalytics.getCostsByAgent(
- agent,
- startDate || undefined,
- endDate || undefined
- );
- return NextResponse.json(agentCosts);
- }
-
- const days = period === 'day' ? 1 : period === 'week' ? 7 : period === 'month' ? 30 : 7;
-
- if (startDate && endDate) {
- const dateRangeCosts = CostAnalytics.getCostsByDateRange(startDate, endDate);
- return NextResponse.json(dateRangeCosts);
- }
-
- const now = new Date();
- const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
- const periodCosts = CostAnalytics.getCostsByDateRange(
- start.toISOString(),
- now.toISOString()
- );
-
- const cacheStats = CostAnalytics.getCacheEffectiveness(
- start.toISOString(),
- now.toISOString()
- );
-
- const dailyBreakdown = CostAnalytics.getDailyBreakdown(days);
-
- return NextResponse.json({
- period: period,
- summary: periodCosts,
- cache: cacheStats,
- daily: dailyBreakdown,
- });
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- console.error('❌ [Analytics] Error:', errorMessage);
-
- return NextResponse.json(
- { error: errorMessage },
- { status: 500 }
- );
- }
-}
diff --git a/app/api/cache-stats/route.ts b/app/api/cache-stats/route.ts
deleted file mode 100644
index 5677fd2..0000000
--- a/app/api/cache-stats/route.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { NextResponse } from 'next/server';
-import type { CacheStats } from '@/types/prompts';
-
-declare global {
- // eslint-disable-next-line no-var
- var lastCacheStats: CacheStats | undefined;
-}
-
-export async function GET() {
- try {
- const stats = global.lastCacheStats;
-
- if (!stats) {
- return NextResponse.json({
- error: 'No cache statistics available yet',
- message: 'Send a message to ra-h to generate cache stats'
- }, { status: 404 });
- }
-
- const hitRate = stats.cacheReadInputTokens > 0 ? 'HIT' : 'MISS';
- const totalInputTokens = stats.inputTokens + stats.cacheCreationInputTokens + stats.cacheReadInputTokens;
-
- // Cost calculation (Sonnet 4.5 pricing)
- const baseCost = 3.0; // $3 per million input tokens
- const writeCost = baseCost * 1.25; // $3.75 per million
- const readCost = baseCost * 0.1; // $0.30 per million
-
- const actualCost = (
- (stats.inputTokens * baseCost) +
- (stats.cacheCreationInputTokens * writeCost) +
- (stats.cacheReadInputTokens * readCost)
- ) / 1_000_000;
-
- const noCacheCost = (totalInputTokens * baseCost) / 1_000_000;
- const costSavingsPercentage = noCacheCost > 0
- ? Math.round(((noCacheCost - actualCost) / noCacheCost) * 100)
- : 0;
-
- return NextResponse.json({
- status: 'success',
- timestamp: new Date().toISOString(),
- lastRequest: {
- hitRate,
- tokens: {
- cacheWrite: stats.cacheCreationInputTokens,
- cacheRead: stats.cacheReadInputTokens,
- regular: stats.inputTokens,
- totalInput: totalInputTokens,
- output: stats.outputTokens
- },
- savings: {
- tokenPercentage: stats.savingsPercentage,
- costPercentage: costSavingsPercentage,
- actualCostUSD: actualCost.toFixed(6),
- noCacheCostUSD: noCacheCost.toFixed(6),
- savedUSD: (noCacheCost - actualCost).toFixed(6)
- }
- }
- });
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- return NextResponse.json(
- { error: 'Failed to fetch cache stats', details: errorMessage },
- { status: 500 }
- );
- }
-}
diff --git a/app/api/tool-results/[id]/route.ts b/app/api/tool-results/[id]/route.ts
deleted file mode 100644
index 09ecb64..0000000
--- a/app/api/tool-results/[id]/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NextRequest } from 'next/server';
-import { resultCache } from '@/services/tools/resultCache';
-
-export async function GET(
- request: NextRequest,
- ctx: { params: Promise<{ id: string }> }
-) {
- try {
- const { id } = await ctx.params; // Next.js 15: params is a Promise
- const data = resultCache.get(id);
- if (!data) {
- return new Response(JSON.stringify({ success: false, error: 'Not found' }), {
- status: 404,
- headers: { 'Content-Type': 'application/json' }
- });
- }
- return new Response(JSON.stringify({ success: true, data }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' }
- });
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (e: any) {
- return new Response(JSON.stringify({ success: false, error: e?.message || 'Failed' }), {
- status: 500,
- headers: { 'Content-Type': 'application/json' }
- });
- }
-}
diff --git a/app/page.tsx b/app/page.tsx
index 4b8f611..ab919a7 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,10 +1,5 @@
import ThreePanelLayout from '@/components/layout/ThreePanelLayout';
-import { LocalKeyGate } from '@/components/auth/LocalKeyGate';
export default function Home() {
- return (
-
-
-
- );
+ return ;
}
diff --git a/apps/mcp-server/server.js b/apps/mcp-server/server.js
index 6de2348..eb6af05 100644
--- a/apps/mcp-server/server.js
+++ b/apps/mcp-server/server.js
@@ -253,47 +253,6 @@ const searchEmbeddingsOutputSchema = {
)
};
-// rah_list_workflows schemas
-const listWorkflowsOutputSchema = {
- count: z.number(),
- workflows: z.array(
- z.object({
- key: z.string(),
- displayName: z.string(),
- description: z.string().nullable(),
- enabled: z.boolean()
- })
- )
-};
-
-// rah_get_workflow schemas
-const getWorkflowInputSchema = {
- workflowKey: z.string().describe('Workflow key to fetch (e.g., "integrate", "prep")')
-};
-
-const getWorkflowOutputSchema = {
- success: z.boolean(),
- workflow: z.object({
- key: z.string(),
- displayName: z.string(),
- description: z.string().nullable(),
- instructions: z.string(),
- enabled: z.boolean()
- }).nullable(),
- message: z.string()
-};
-
-// rah_execute_workflow schemas
-const executeWorkflowInputSchema = {
- workflowKey: z.string().describe('Workflow key to execute (e.g., "integrate")'),
- nodeId: z.number().int().positive().optional().describe('Target node ID for the workflow')
-};
-
-const executeWorkflowOutputSchema = {
- success: z.boolean(),
- message: z.string()
-};
-
// rah_extract_url schemas
const extractUrlInputSchema = {
url: z.string().url().describe('URL of the webpage to extract content from')
@@ -763,102 +722,6 @@ mcpServer.registerTool(
}
);
-mcpServer.registerTool(
- 'rah_list_workflows',
- {
- title: 'List RA-H workflows',
- description: 'List all available workflows.',
- inputSchema: {},
- outputSchema: listWorkflowsOutputSchema
- },
- async () => {
- const result = await callRaHApi('/api/workflows', { method: 'GET' });
- const workflows = Array.isArray(result.data) ? result.data : [];
-
- return {
- content: [{ type: 'text', text: `Found ${workflows.length} workflow(s).` }],
- structuredContent: {
- count: workflows.length,
- workflows: workflows.map(w => ({
- key: w.key,
- displayName: w.displayName,
- description: w.description || null,
- enabled: w.enabled
- }))
- }
- };
- }
-);
-
-mcpServer.registerTool(
- 'rah_get_workflow',
- {
- title: 'Get RA-H workflow',
- description: 'Fetch a workflow definition by key (e.g., "integrate", "prep"). Returns instructions that can be followed.',
- inputSchema: getWorkflowInputSchema,
- outputSchema: getWorkflowOutputSchema
- },
- async ({ workflowKey }) => {
- try {
- const result = await callRaHApi(`/api/workflows/${encodeURIComponent(workflowKey)}`, {
- method: 'GET'
- });
-
- const workflow = result.data;
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}": ${workflow?.displayName || 'Unknown'}` }],
- structuredContent: {
- success: true,
- workflow: workflow ? {
- key: workflow.key,
- displayName: workflow.displayName,
- description: workflow.description || null,
- instructions: workflow.instructions,
- enabled: workflow.enabled
- } : null,
- message: `Fetched workflow "${workflowKey}"`
- }
- };
- } catch (e) {
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}" not found.` }],
- structuredContent: {
- success: false,
- workflow: null,
- message: `Workflow "${workflowKey}" not found`
- }
- };
- }
- }
-);
-
-mcpServer.registerTool(
- 'rah_execute_workflow',
- {
- title: 'Execute RA-H workflow',
- description: 'Execute a predefined workflow (e.g., "integrate" for connection discovery).',
- inputSchema: executeWorkflowInputSchema,
- outputSchema: executeWorkflowOutputSchema
- },
- async ({ workflowKey, nodeId }) => {
- const payload = { workflowKey };
- if (nodeId) payload.nodeId = nodeId;
-
- const result = await callRaHApi('/api/workflows/execute', {
- method: 'POST',
- body: JSON.stringify(payload)
- });
-
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}" initiated.` }],
- structuredContent: {
- success: true,
- message: result.message || `Workflow "${workflowKey}" initiated.`
- }
- };
- }
-);
-
mcpServer.registerTool(
'rah_extract_url',
{
diff --git a/apps/mcp-server/stdio-server.js b/apps/mcp-server/stdio-server.js
index f0d4ccc..a6128cf 100644
--- a/apps/mcp-server/stdio-server.js
+++ b/apps/mcp-server/stdio-server.js
@@ -202,47 +202,6 @@ const searchEmbeddingsOutputSchema = {
)
};
-// rah_list_workflows schemas
-const listWorkflowsOutputSchema = {
- count: z.number(),
- workflows: z.array(
- z.object({
- key: z.string(),
- displayName: z.string(),
- description: z.string().nullable(),
- enabled: z.boolean()
- })
- )
-};
-
-// rah_get_workflow schemas
-const getWorkflowInputSchema = {
- workflowKey: z.string().describe('Workflow key to fetch (e.g., "integrate", "prep")')
-};
-
-const getWorkflowOutputSchema = {
- success: z.boolean(),
- workflow: z.object({
- key: z.string(),
- displayName: z.string(),
- description: z.string().nullable(),
- instructions: z.string(),
- enabled: z.boolean()
- }).nullable(),
- message: z.string()
-};
-
-// rah_execute_workflow schemas
-const executeWorkflowInputSchema = {
- workflowKey: z.string().describe('Workflow key to execute (e.g., "integrate")'),
- nodeId: z.number().int().positive().optional().describe('Target node ID for the workflow')
-};
-
-const executeWorkflowOutputSchema = {
- success: z.boolean(),
- message: z.string()
-};
-
// rah_extract_url schemas
const extractUrlInputSchema = {
url: z.string().url().describe('URL of the webpage to extract content from')
@@ -735,102 +694,6 @@ server.registerTool(
}
);
-server.registerTool(
- 'rah_list_workflows',
- {
- title: 'List RA-H workflows',
- description: 'List all available workflows.',
- inputSchema: {},
- outputSchema: listWorkflowsOutputSchema
- },
- async () => {
- const result = await callRaHApi('/api/workflows', { method: 'GET' });
- const workflows = Array.isArray(result.data) ? result.data : [];
-
- return {
- content: [{ type: 'text', text: `Found ${workflows.length} workflow(s).` }],
- structuredContent: {
- count: workflows.length,
- workflows: workflows.map(w => ({
- key: w.key,
- displayName: w.displayName,
- description: w.description || null,
- enabled: w.enabled
- }))
- }
- };
- }
-);
-
-server.registerTool(
- 'rah_get_workflow',
- {
- title: 'Get RA-H workflow',
- description: 'Fetch a workflow definition by key (e.g., "integrate", "prep"). Returns instructions that can be followed.',
- inputSchema: getWorkflowInputSchema,
- outputSchema: getWorkflowOutputSchema
- },
- async ({ workflowKey }) => {
- try {
- const result = await callRaHApi(`/api/workflows/${encodeURIComponent(workflowKey)}`, {
- method: 'GET'
- });
-
- const workflow = result.data;
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}": ${workflow?.displayName || 'Unknown'}` }],
- structuredContent: {
- success: true,
- workflow: workflow ? {
- key: workflow.key,
- displayName: workflow.displayName,
- description: workflow.description || null,
- instructions: workflow.instructions,
- enabled: workflow.enabled
- } : null,
- message: `Fetched workflow "${workflowKey}"`
- }
- };
- } catch (e) {
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}" not found.` }],
- structuredContent: {
- success: false,
- workflow: null,
- message: `Workflow "${workflowKey}" not found`
- }
- };
- }
- }
-);
-
-server.registerTool(
- 'rah_execute_workflow',
- {
- title: 'Execute RA-H workflow',
- description: 'Execute a predefined workflow (e.g., "integrate" for connection discovery).',
- inputSchema: executeWorkflowInputSchema,
- outputSchema: executeWorkflowOutputSchema
- },
- async ({ workflowKey, nodeId }) => {
- const payload = { workflowKey };
- if (nodeId) payload.nodeId = nodeId;
-
- const result = await callRaHApi('/api/workflows/execute', {
- method: 'POST',
- body: JSON.stringify(payload)
- });
-
- return {
- content: [{ type: 'text', text: `Workflow "${workflowKey}" initiated.` }],
- structuredContent: {
- success: true,
- message: result.message || `Workflow "${workflowKey}" initiated.`
- }
- };
- }
-);
-
server.registerTool(
'rah_extract_url',
{
diff --git a/package-lock.json b/package-lock.json
index 79fe106..988822a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,19 +14,12 @@
"@ai-sdk/react": "^3.0.29",
"@langchain/core": "^1.0.1",
"@langchain/textsplitters": "^1.0.1",
- "@radix-ui/react-checkbox": "^1.1.1",
- "@radix-ui/react-dropdown-menu": "^2.1.1",
- "@radix-ui/react-label": "^2.1.0",
- "@radix-ui/react-slot": "^1.1.0",
"@xyflow/react": "^12.10.0",
"ai": "^6.0.27",
"better-sqlite3": "^12.2.0",
"cheerio": "^1.1.2",
- "class-variance-authority": "^0.7.0",
- "clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"next": "15.1.3",
- "next-themes": "^0.4.3",
"openai": "^4.103.0",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^5.4.296",
@@ -34,12 +27,8 @@
"react-dom": "^19.0.1",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
- "tailwind-merge": "^2.5.2",
- "uuid": "^11.1.0",
- "youtube-captions-scraper": "^2.0.3",
"youtube-transcript": "^1.2.1",
"youtube-transcript-plus": "^1.1.1",
- "ytdl-core": "^4.11.5",
"zod": "^3.25.76"
},
"devDependencies": {
@@ -47,7 +36,6 @@
"@types/node": "22.10.2",
"@types/react": "^19.0.2",
"@types/react-dom": "19.0.2",
- "@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"esbuild": "^0.23.1",
"eslint": "^8.57.0",
@@ -55,7 +43,6 @@
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.17",
- "tailwindcss-animate": "^1.0.7",
"tsx": "^4.19.4",
"typescript": "^5.7.2",
"vitest": "^3.2.4"
@@ -714,44 +701,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
- "node_modules/@floating-ui/core": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
- "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/utils": "^0.2.10"
- }
- },
- "node_modules/@floating-ui/dom": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
- "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/core": "^1.7.3",
- "@floating-ui/utils": "^0.2.10"
- }
- },
- "node_modules/@floating-ui/react-dom": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
- "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/dom": "^1.7.4"
- },
- "peerDependencies": {
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0"
- }
- },
- "node_modules/@floating-ui/utils": {
- "version": "0.2.10",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
- "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
- "license": "MIT"
- },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -1703,684 +1652,6 @@
"node": ">=8.0.0"
}
},
- "node_modules/@radix-ui/primitive": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
- "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
- "license": "MIT"
- },
- "node_modules/@radix-ui/react-arrow": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
- "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.3"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-checkbox": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
- "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-controllable-state": "1.2.2",
- "@radix-ui/react-use-previous": "1.1.1",
- "@radix-ui/react-use-size": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-collection": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
- "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-slot": "1.2.3"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
- "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
- "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-context": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
- "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-direction": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
- "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
- "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-escape-keydown": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-dropdown-menu": {
- "version": "2.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
- "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-menu": "2.1.16",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-controllable-state": "1.2.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-focus-guards": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
- "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-focus-scope": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
- "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-id": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
- "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-label": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
- "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.4"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
- "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-slot": "1.2.4"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-menu": {
- "version": "2.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
- "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-collection": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-direction": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.11",
- "@radix-ui/react-focus-guards": "1.1.3",
- "@radix-ui/react-focus-scope": "1.1.7",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-popper": "1.2.8",
- "@radix-ui/react-portal": "1.1.9",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-roving-focus": "1.1.11",
- "@radix-ui/react-slot": "1.2.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
- "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-popper": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
- "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/react-dom": "^2.0.0",
- "@radix-ui/react-arrow": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.1",
- "@radix-ui/react-use-rect": "1.1.1",
- "@radix-ui/react-use-size": "1.1.1",
- "@radix-ui/rect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-portal": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
- "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-presence": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
- "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-primitive": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
- "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-slot": "1.2.3"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
- "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-roving-focus": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
- "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-collection": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-direction": "1.1.1",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.2.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-slot": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
- "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-callback-ref": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
- "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-controllable-state": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
- "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-effect-event": "0.0.2",
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-effect-event": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
- "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-escape-keydown": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
- "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-layout-effect": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
- "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-previous": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
- "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-rect": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
- "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/rect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
- "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/rect": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
- "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
- "license": "MIT"
- },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
@@ -2941,7 +2212,7 @@
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz",
"integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
@@ -3771,18 +3042,6 @@
"dev": true,
"license": "Python-2.0"
},
- "node_modules/aria-hidden": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
- "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -4049,17 +3308,6 @@
"node": ">=4"
}
},
- "node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
- }
- },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -4568,18 +3816,6 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
- "node_modules/class-variance-authority": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
- "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
- "license": "Apache-2.0",
- "dependencies": {
- "clsx": "^2.1.1"
- },
- "funding": {
- "url": "https://polar.sh/cva"
- }
- },
"node_modules/classcat": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
@@ -4592,15 +3828,6 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -5063,12 +4290,6 @@
"node": ">=8"
}
},
- "node_modules/detect-node-es": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
- "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
- "license": "MIT"
- },
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
@@ -6118,26 +5339,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -6305,15 +5506,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-nonce": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
- "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -6581,15 +5773,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
"node_modules/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -7496,12 +6679,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "license": "MIT"
- },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -7548,19 +6725,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
- "node_modules/m3u8stream": {
- "version": "0.8.6",
- "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
- "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
- "license": "MIT",
- "dependencies": {
- "miniget": "^4.2.2",
- "sax": "^1.2.4"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -8492,15 +7656,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/miniget": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
- "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -8658,16 +7813,6 @@
}
}
},
- "node_modules/next-themes": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
- "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
- }
- },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -9530,12 +8675,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -9656,75 +8795,6 @@
"react": ">=18"
}
},
- "node_modules/react-remove-scroll": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
- "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
- "license": "MIT",
- "dependencies": {
- "react-remove-scroll-bar": "^2.3.7",
- "react-style-singleton": "^2.2.3",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.3",
- "use-sidecar": "^1.1.3"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-remove-scroll-bar": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
- "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
- "license": "MIT",
- "dependencies": {
- "react-style-singleton": "^2.2.2",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-style-singleton": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
- "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
- "license": "MIT",
- "dependencies": {
- "get-nonce": "^1.0.0",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -10091,15 +9161,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
- "node_modules/sax": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
- "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=11.0.0"
- }
- },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -10628,12 +9689,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/striptags": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
- "integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==",
- "license": "MIT"
- },
"node_modules/style-to-js": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
@@ -10736,16 +9791,6 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/tailwind-merge": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
- "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
- }
- },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -10784,16 +9829,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/tailwindcss-animate": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
- "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || insiders"
- }
- },
"node_modules/tailwindcss/node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -11861,49 +10896,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/use-callback-ref": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
- "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/use-sidecar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
- "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
- "license": "MIT",
- "dependencies": {
- "detect-node-es": "^1.1.0",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
@@ -11919,19 +10911,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/uuid": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
- "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/esm/bin/uuid"
- }
- },
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
@@ -13393,18 +12372,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/youtube-captions-scraper": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/youtube-captions-scraper/-/youtube-captions-scraper-2.0.3.tgz",
- "integrity": "sha512-XT6U5wFq35P5G99M+2uwNzJFkmGUrb38u8lMMQkv9PpfTOtG8Lpotka7jEqbDnQBshDw6/hgLQV597/tldiyUg==",
- "license": "MIT",
- "dependencies": {
- "axios": "^1.6.7",
- "he": "^1.1.1",
- "lodash": "^4.17.4",
- "striptags": "^3.1.0"
- }
- },
"node_modules/youtube-transcript": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/youtube-transcript/-/youtube-transcript-1.2.1.tgz",
@@ -13423,20 +12390,6 @@
"node": ">=18.0.0"
}
},
- "node_modules/ytdl-core": {
- "version": "4.11.5",
- "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz",
- "integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==",
- "license": "MIT",
- "dependencies": {
- "m3u8stream": "^0.8.6",
- "miniget": "^4.2.2",
- "sax": "^1.1.3"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
diff --git a/package.json b/package.json
index bb0a85a..26849ce 100644
--- a/package.json
+++ b/package.json
@@ -26,19 +26,12 @@
"@ai-sdk/react": "^3.0.29",
"@langchain/core": "^1.0.1",
"@langchain/textsplitters": "^1.0.1",
- "@radix-ui/react-checkbox": "^1.1.1",
- "@radix-ui/react-dropdown-menu": "^2.1.1",
- "@radix-ui/react-label": "^2.1.0",
- "@radix-ui/react-slot": "^1.1.0",
"@xyflow/react": "^12.10.0",
"ai": "^6.0.27",
"better-sqlite3": "^12.2.0",
"cheerio": "^1.1.2",
- "class-variance-authority": "^0.7.0",
- "clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"next": "15.1.3",
- "next-themes": "^0.4.3",
"openai": "^4.103.0",
"pdf-parse": "^1.1.1",
"pdfjs-dist": "^5.4.296",
@@ -46,12 +39,8 @@
"react-dom": "^19.0.1",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
- "tailwind-merge": "^2.5.2",
- "uuid": "^11.1.0",
- "youtube-captions-scraper": "^2.0.3",
"youtube-transcript": "^1.2.1",
"youtube-transcript-plus": "^1.1.1",
- "ytdl-core": "^4.11.5",
"zod": "^3.25.76"
},
"devDependencies": {
@@ -59,7 +48,6 @@
"@types/node": "22.10.2",
"@types/react": "^19.0.2",
"@types/react-dom": "19.0.2",
- "@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"esbuild": "^0.23.1",
"eslint": "^8.57.0",
@@ -67,7 +55,6 @@
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.17",
- "tailwindcss-animate": "^1.0.7",
"tsx": "^4.19.4",
"typescript": "^5.7.2",
"vitest": "^3.2.4"
diff --git a/scripts/database/sqlite-ensure-app-schema.sh b/scripts/database/sqlite-ensure-app-schema.sh
index 618da04..ac15325 100755
--- a/scripts/database/sqlite-ensure-app-schema.sh
+++ b/scripts/database/sqlite-ensure-app-schema.sh
@@ -264,20 +264,8 @@ CREATE INDEX IF NOT EXISTS idx_chat_memory_thread ON chat_memory_state(thread_id
SQL
fi
-echo "Ensuring agent_delegations table exists..."
-"$SQLITE_BIN" "$DB_PATH" <<'SQL'
-CREATE TABLE IF NOT EXISTS agent_delegations (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- session_id TEXT UNIQUE NOT NULL,
- task TEXT NOT NULL,
- context TEXT,
- expected_outcome TEXT,
- status TEXT NOT NULL DEFAULT 'queued',
- summary TEXT,
- created_at TEXT DEFAULT CURRENT_TIMESTAMP,
- updated_at TEXT DEFAULT CURRENT_TIMESTAMP
-);
-SQL
+echo "Dropping legacy agent_delegations table if present..."
+"$SQLITE_BIN" "$DB_PATH" "DROP TABLE IF EXISTS agent_delegations;"
if ! has_table node_dimensions; then
"$SQLITE_BIN" "$DB_PATH" <<'SQL'
@@ -573,6 +561,12 @@ if has_table helpers && has_col helpers fluid_context; then
"$SQLITE_BIN" "$DB_PATH" "ALTER TABLE helpers DROP COLUMN fluid_context;" || true
fi
+echo "Ensuring performance indexes exist..."
+"$SQLITE_BIN" "$DB_PATH" <<'SQL'
+CREATE INDEX IF NOT EXISTS idx_nodes_updated_at ON nodes(updated_at DESC);
+CREATE INDEX IF NOT EXISTS idx_chats_created_at ON chats(created_at DESC);
+SQL
+
echo "Running VACUUM and ANALYZE..."
"$SQLITE_BIN" "$DB_PATH" "VACUUM; ANALYZE;"
diff --git a/scripts/test-ts-migration.js b/scripts/test-ts-migration.js
deleted file mode 100755
index a0fee78..0000000
--- a/scripts/test-ts-migration.js
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Test script for Python to TypeScript migration
- * Tests extractors and embeddings using TypeScript modules
- */
-const fs = require('fs');
-const path = require('path');
-const Module = require('module');
-const ts = require('typescript');
-const { getSubtitles } = require('youtube-captions-scraper');
-
-function isNetworkError(error) {
- if (!error) return false;
- const message = typeof error === 'string' ? error : error.message || '';
- const code = error.code || '';
- return [
- 'fetch failed',
- 'ENOTFOUND',
- 'ECONNRESET',
- 'EAI_AGAIN',
- 'ECONNREFUSED',
- 'ETIMEDOUT'
- ].some(token => message.includes(token)) || ['ENOTFOUND', 'ECONNRESET', 'EAI_AGAIN', 'ECONNREFUSED', 'ETIMEDOUT'].includes(code);
-}
-
-function requireTs(modulePath) {
- const resolved = path.resolve(__dirname, '..', modulePath);
- const source = fs.readFileSync(resolved, 'utf8');
- const transpiled = ts.transpileModule(source, {
- compilerOptions: {
- module: ts.ModuleKind.CommonJS,
- target: ts.ScriptTarget.ES2020,
- esModuleInterop: true,
- moduleResolution: ts.ModuleResolutionKind.Node16,
- resolveJsonModule: true,
- skipLibCheck: true
- },
- fileName: resolved
- });
-
- const tempModule = new Module(resolved, module);
- tempModule.filename = resolved;
- tempModule.paths = Module._nodeModulePaths(path.dirname(resolved));
- tempModule._compile(transpiled.outputText, resolved);
- return tempModule.exports;
-}
-
-async function testTranscriptLibrary() {
- console.log('🔍 Testing youtube-captions-scraper library directly...');
-
- const testUrls = [
- 'dQw4w9WgXcQ', // Rick Roll
- 'MnrJzXM7a6o', // TED Talk
- 'jNQXAC9IVRw' // Popular video
- ];
-
- let sawNetworkError = false;
- let sawEmptyTranscript = false;
-
- for (const videoId of testUrls) {
- console.log(`\nTesting video: ${videoId}`);
- try {
- const transcript = await getSubtitles({ videoID: videoId });
- if (!transcript || transcript.length === 0) {
- sawEmptyTranscript = true;
- console.log('⚠️ Library returned 0 segments (likely geo-blocked). Trying next video...');
- continue;
- }
- console.log(`✅ Success! Got ${transcript.length} segments`);
- console.log('First segment:', transcript[0]);
- console.log('Sample formatted:', `[${transcript[0].start.toFixed(1)}s] ${transcript[0].text}`);
- return 'passed'; // Exit on first success
- } catch (error) {
- if (isNetworkError(error)) {
- sawNetworkError = true;
- console.log('⚠️ Network unavailable, skipping this video');
- } else {
- console.log('❌ Failed:', error.message);
- }
- }
- }
-
- if (sawNetworkError) {
- console.log('\n⚠️ Network unavailable for transcript tests, marking as SKIPPED');
- return 'skipped';
- }
-
- if (sawEmptyTranscript) {
- console.log('\n⚠️ Library could not fetch transcripts for any sample video (likely YouTube changes). Marking as SKIPPED.');
- return 'skipped';
- }
-
- console.log('\n❌ No transcripts could be extracted from any test video');
- return 'failed';
-}
-
-console.log('🧪 Testing TypeScript Migration...\n');
-
-async function testYouTubeExtractor() {
- console.log('📺 Testing YouTube Extractor...');
- try {
- const { extractYouTube } = requireTs('src/services/typescript/extractors/youtube.ts');
- const result = await extractYouTube('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
- console.log('✅ YouTube extraction successful');
- console.log(` Title: ${result.metadata.title}`);
- console.log(` Channel: ${result.metadata.author_name}`);
- console.log(` Transcript length: ${result.chunk.length} chars\n`);
- return 'passed';
- } catch (error) {
- if (isNetworkError(error)) {
- console.warn('⚠️ Network unavailable, skipping YouTube extractor test\n');
- return 'skipped';
- }
- console.error('❌ YouTube extraction failed:', error.message, '\n');
- return 'failed';
- }
-}
-
-async function testWebsiteExtractor() {
- console.log('🌐 Testing Website Extractor...');
- try {
- const { extractWebsite } = requireTs('src/services/typescript/extractors/website.ts');
- const result = await extractWebsite('https://example.com');
- console.log('✅ Website extraction successful');
- console.log(` Title: ${result.metadata.title}`);
- console.log(` Content length: ${result.chunk.length} chars\n`);
- return 'passed';
- } catch (error) {
- if (isNetworkError(error)) {
- console.warn('⚠️ Network unavailable, skipping website extractor test\n');
- return 'skipped';
- }
- console.error('❌ Website extraction failed:', error.message, '\n');
- return 'failed';
- }
-}
-
-async function testPDFExtractor() {
- console.log('📄 Testing PDF Extractor...');
- try {
- const { extractPaper } = requireTs('src/services/typescript/extractors/paper.ts');
- // Using a sample PDF URL
- const samplePdf = 'https://raw.githubusercontent.com/mozilla/pdf.js/master/web/compressed.tracemonkey-pldi-09.pdf';
- const result = await extractPaper(samplePdf);
- console.log('✅ PDF extraction successful');
- console.log(` Pages: ${result.metadata.pages}`);
- console.log(` Text length: ${result.metadata.text_length} chars\n`);
- return 'passed';
- } catch (error) {
- if (isNetworkError(error)) {
- console.warn('⚠️ Network unavailable, skipping PDF extractor test\n');
- return 'skipped';
- }
- console.error('❌ PDF extraction failed:', error.message, '\n');
- return 'failed';
- }
-}
-
-async function testNodeEmbedding() {
- console.log('🔮 Testing Node Embedding...');
- try {
- // This would require a real node ID and database connection
- console.log('⚠️ Skipping node embedding test (requires database and API key)\n');
- return 'skipped';
- } catch (error) {
- console.error('❌ Node embedding failed:', error.message, '\n');
- return 'failed';
- }
-}
-
-async function runTests() {
- console.log('Environment:');
- console.log(` Node version: ${process.version}\n`);
-
- // First test the library directly
- const libraryStatus = await testTranscriptLibrary();
-
- const results = {
- library: libraryStatus,
- youtube: await testYouTubeExtractor(),
- website: await testWebsiteExtractor(),
- pdf: await testPDFExtractor(),
- embedding: await testNodeEmbedding()
- };
-
- console.log('📊 Test Results:');
- console.log('─────────────────');
-
- let passed = 0;
- let failed = 0;
- let skipped = 0;
-
- for (const [test, result] of Object.entries(results)) {
- if (result === 'passed') {
- console.log(` ${test}: ✅ PASSED`);
- passed++;
- } else if (result === 'skipped') {
- console.log(` ${test}: ⚠️ SKIPPED`);
- skipped++;
- } else {
- console.log(` ${test}: ❌ FAILED`);
- failed++;
- }
- }
-
- console.log('\n📈 Summary:');
- console.log(` Passed: ${passed}`);
- console.log(` Failed: ${failed}`);
- console.log(` Skipped: ${skipped}`);
-
- if (failed === 0) {
- console.log('\n🎉 No hard failures. Re-run skipped tests when network/database access is available.');
- } else {
- console.log('\n⚠️ Some tests failed. Check the errors above.');
- }
-
- process.exit(failed > 0 ? 1 : 0);
-}
-
-// Run tests
-runTests().catch(error => {
- console.error('Fatal error:', error);
- process.exit(1);
-});
diff --git a/src/components/auth/LocalKeyGate.tsx b/src/components/auth/LocalKeyGate.tsx
deleted file mode 100644
index 202e1a4..0000000
--- a/src/components/auth/LocalKeyGate.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-"use client";
-
-import { useEffect, useMemo, useState } from 'react';
-import { createPortal } from 'react-dom';
-import { isLocalMode } from '@/config/runtime';
-import { apiKeyService } from '@/services/storage/apiKeys';
-
-interface LocalKeyGateProps {
- children: React.ReactNode;
-}
-
-const panelStyle: React.CSSProperties = {
- maxWidth: 420,
- background: 'rgba(15, 15, 15, 0.92)',
- border: '1px solid #2a2a2a',
- borderRadius: 16,
- padding: '28px 32px',
- boxShadow: '0 18px 40px rgba(0,0,0,0.45)'
-};
-
-const buttonStyle: React.CSSProperties = {
- background: '#22c55e',
- color: '#0b1113',
- border: 'none',
- borderRadius: 6,
- padding: '12px 18px',
- fontWeight: 600,
- cursor: 'pointer'
-};
-
-export function LocalKeyGate({ children }: LocalKeyGateProps) {
- const isLocal = useMemo(() => isLocalMode(), []);
- const [hasKeys, setHasKeys] = useState(() => (!isLocal) || apiKeyService.hasUserKeys());
- const [mounted, setMounted] = useState(false);
-
- useEffect(() => {
- setMounted(true);
- }, []);
-
- useEffect(() => {
- if (!isLocal) return;
- const handleUpdate = () => {
- setHasKeys(apiKeyService.hasUserKeys());
- };
- handleUpdate();
- const listener = () => handleUpdate();
- window.addEventListener('api-keys:updated', listener);
- return () => window.removeEventListener('api-keys:updated', listener);
- }, [isLocal]);
-
- if (!isLocal || hasKeys) {
- return <>{children}>;
- }
-
- const openApiKeySettings = () => {
- window.dispatchEvent(new CustomEvent('settings:open', { detail: { tab: 'apikeys' } }));
- };
-
- const popupContent = (
-
-
-
-
Connect your AI keys
-
- Local mode needs an OpenAI or Anthropic key. Add one under Settings → API Keys
- to unlock the workspace.
-
-
-
-
-
-
-
-
- );
-
- return (
- <>
- {children}
- {mounted && createPortal(popupContent, document.body)}
- >
- );
-}
diff --git a/src/components/common/EditableSection.tsx b/src/components/common/EditableSection.tsx
deleted file mode 100644
index afc82d7..0000000
--- a/src/components/common/EditableSection.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-"use client";
-
-import { ReactNode, CSSProperties } from 'react';
-
-interface EditableSectionProps {
- label: string;
- summary: ReactNode;
- expanded: boolean;
- onToggle: () => void;
- children: ReactNode;
- metadata?: string;
- disabled?: boolean;
-}
-
-export default function EditableSection({
- label,
- summary,
- expanded,
- onToggle,
- children,
- metadata,
- disabled = false
-}: EditableSectionProps) {
- return (
-
-
{
- if (!disabled && !expanded) {
- e.currentTarget.style.background = '#1f1f1f';
- }
- }}
- onMouseLeave={(e) => {
- if (!disabled && !expanded) {
- e.currentTarget.style.background = 'transparent';
- }
- }}
- >
-
- {label}
-
-
-
- {summary}
-
-
- {metadata && (
-
- {metadata}
-
- )}
-
- {!disabled && (
-
- )}
-
- {disabled && (
-
- locked
-
- )}
-
-
- {expanded && (
-
- {children}
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/components/common/PanelHeader.tsx b/src/components/common/PanelHeader.tsx
deleted file mode 100644
index e62aec7..0000000
--- a/src/components/common/PanelHeader.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-"use client";
-
-import { ReactNode, CSSProperties } from 'react';
-
-interface PanelHeaderProps {
- title: string;
- leftContent?: ReactNode;
- rightContent?: ReactNode;
- className?: string;
- style?: CSSProperties;
-}
-
-export default function PanelHeader({
- title,
- leftContent,
- rightContent,
- className = '',
- style = {}
-}: PanelHeaderProps) {
- return (
-
-
-
- {title}
-
- {leftContent}
-
-
- {rightContent && (
-
- {rightContent}
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/components/focus/edges/EdgeSearchModal.tsx b/src/components/focus/edges/EdgeSearchModal.tsx
deleted file mode 100644
index 75ed16d..0000000
--- a/src/components/focus/edges/EdgeSearchModal.tsx
+++ /dev/null
@@ -1,400 +0,0 @@
-"use client";
-
-import { useState, useEffect, useRef } from 'react';
-import { createPortal } from 'react-dom';
-
-interface EdgeSearchModalProps {
- isOpen: boolean;
- onClose: () => void;
- onEdgeCreate: (targetNodeId: number, targetNodeTitle: string) => void;
- sourceNodeId: number;
-}
-
-interface NodeSuggestion {
- id: number;
- title: string;
- dimensions?: string[];
-}
-
-export default function EdgeSearchModal({
- isOpen,
- onClose,
- onEdgeCreate,
- sourceNodeId
-}: EdgeSearchModalProps) {
- const [searchQuery, setSearchQuery] = useState('');
- const [suggestions, setSuggestions] = useState([]);
- const [selectedIndex, setSelectedIndex] = useState(0);
- const inputRef = useRef(null);
- const modalRef = useRef(null);
- const returnFocusRef = useRef(null);
-
- // Store the element that triggered the modal for return focus
- useEffect(() => {
- if (isOpen && document.activeElement instanceof HTMLElement) {
- returnFocusRef.current = document.activeElement;
- }
- }, [isOpen]);
-
- // Focus trap and accessibility
- useEffect(() => {
- if (!isOpen) return;
-
- // Autofocus input
- inputRef.current?.focus();
-
- // Lock body scroll
- document.body.style.overflow = 'hidden';
-
- // Handle Escape key
- const handleEscape = (e: KeyboardEvent) => {
- if (e.key === 'Escape') {
- e.preventDefault();
- onClose();
- }
- };
-
- // Focus trap: keep focus within modal
- const handleTab = (e: KeyboardEvent) => {
- if (e.key !== 'Tab') return;
-
- const focusableElements = modalRef.current?.querySelectorAll(
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
- );
-
- if (!focusableElements || focusableElements.length === 0) return;
-
- const firstElement = focusableElements[0] as HTMLElement;
- const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
-
- if (e.shiftKey) {
- if (document.activeElement === firstElement) {
- e.preventDefault();
- lastElement.focus();
- }
- } else {
- if (document.activeElement === lastElement) {
- e.preventDefault();
- firstElement.focus();
- }
- }
- };
-
- document.addEventListener('keydown', handleEscape);
- document.addEventListener('keydown', handleTab);
-
- return () => {
- document.body.style.overflow = '';
- document.removeEventListener('keydown', handleEscape);
- document.removeEventListener('keydown', handleTab);
-
- // Return focus to trigger element
- if (returnFocusRef.current) {
- returnFocusRef.current.focus();
- }
- };
- }, [isOpen, onClose]);
-
- // Generate suggestions based on search query
- useEffect(() => {
- if (!searchQuery.trim()) {
- setSuggestions([]);
- return;
- }
-
- const fetchSuggestions = async () => {
- try {
- const response = await fetch(`/api/nodes/search?q=${encodeURIComponent(searchQuery)}&limit=10`);
- const result = await response.json();
-
- if (result.success) {
- const nodeSuggestions: NodeSuggestion[] = result.data
- .filter((node: any) => node.id !== sourceNodeId) // Exclude source node
- .map((node: any) => ({
- id: node.id,
- title: node.title,
- dimensions: node.dimensions || []
- }));
-
- setSuggestions(nodeSuggestions);
- setSelectedIndex(0);
- }
- } catch (error) {
- console.error('Error fetching suggestions:', error);
- setSuggestions([]);
- }
- };
-
- const timeoutId = setTimeout(fetchSuggestions, 200);
- return () => clearTimeout(timeoutId);
- }, [searchQuery, sourceNodeId]);
-
- // Handle keyboard navigation
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'ArrowDown') {
- e.preventDefault();
- setSelectedIndex(prev => Math.min(prev + 1, suggestions.length - 1));
- } else if (e.key === 'ArrowUp') {
- e.preventDefault();
- setSelectedIndex(prev => Math.max(prev - 1, 0));
- } else if (e.key === 'Enter' && suggestions[selectedIndex]) {
- e.preventDefault();
- handleSelectSuggestion(suggestions[selectedIndex]);
- }
- };
-
- const handleSelectSuggestion = (suggestion: NodeSuggestion) => {
- onEdgeCreate(suggestion.id, suggestion.title);
- setSearchQuery('');
- setSuggestions([]);
- onClose();
- };
-
- const handleBackdropClick = (e: React.MouseEvent) => {
- if (e.target === e.currentTarget) {
- onClose();
- }
- };
-
- if (!isOpen) return null;
-
- const modalContent = (
-
-
- {/* Search Input */}
-
-
-
-
setSearchQuery(e.target.value)}
- onKeyDown={handleKeyDown}
- placeholder="Connect to node..."
- className="search-input"
- />
-
-
- esc
-
-
-
- {/* Results */}
- {suggestions.length > 0 && (
-
- {suggestions.map((suggestion, index) => (
-
- ))}
-
- )}
-
- {/* Empty state */}
- {searchQuery && suggestions.length === 0 && (
-
- No results for "{searchQuery}"
-
- )}
-
-
-
-
- );
-
- return typeof window !== 'undefined' ? createPortal(modalContent, document.body) : null;
-}
diff --git a/src/components/layout/CollapsedRail.tsx b/src/components/layout/CollapsedRail.tsx
deleted file mode 100644
index 5e6e433..0000000
--- a/src/components/layout/CollapsedRail.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-"use client";
-
-import { ChevronLeft, ChevronRight, FileText, MessageSquare, Workflow, FolderOpen, Map, LayoutList } from 'lucide-react';
-
-type RailPosition = 'left' | 'right';
-type PaneType = 'nodes' | 'node' | 'chat' | 'workflows' | 'dimensions' | 'map' | 'views';
-
-interface CollapsedRailProps {
- position: RailPosition;
- paneType: PaneType;
- onExpand: () => void;
- shortcut?: string;
-}
-
-const PANE_ICONS: Record = {
- nodes: ,
- node: ,
- chat: ,
- workflows: ,
- dimensions: ,
- map: ,
- views: ,
-};
-
-const PANE_LABELS: Record = {
- nodes: 'Nodes',
- node: 'Focus',
- chat: 'Chat',
- workflows: 'Workflows',
- dimensions: 'Dimensions',
- map: 'Map',
- views: 'Feed',
-};
-
-export default function CollapsedRail({ position, paneType, onExpand, shortcut }: CollapsedRailProps) {
- const isLeft = position === 'left';
- const ChevronIcon = isLeft ? ChevronRight : ChevronLeft;
-
- return (
- {
- e.currentTarget.style.background = '#141414';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.background = '#0c0c0c';
- }}
- title={`Expand ${PANE_LABELS[paneType]}${shortcut ? ` (${shortcut})` : ''}`}
- >
- {/* Icon representing the collapsed panel */}
-
- {PANE_ICONS[paneType]}
-
-
- {/* Chevron indicator */}
-
-
-
-
- {/* Vertical label */}
-
- {PANE_LABELS[paneType]}
-
-
- );
-}
diff --git a/src/components/nodes/AddNodeButton.tsx b/src/components/nodes/AddNodeButton.tsx
deleted file mode 100644
index 3924776..0000000
--- a/src/components/nodes/AddNodeButton.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-"use client";
-
-interface AddNodeButtonProps {
- onAddNode: () => void;
- loading?: boolean;
-}
-
-export default function AddNodeButton({ onAddNode, loading = false }: AddNodeButtonProps) {
- return (
-
- );
-}
\ No newline at end of file
diff --git a/src/components/nodes/NodesPanel.tsx b/src/components/nodes/NodesPanel.tsx
deleted file mode 100644
index 44ab5d1..0000000
--- a/src/components/nodes/NodesPanel.tsx
+++ /dev/null
@@ -1,892 +0,0 @@
-"use client";
-
-import { useState, useEffect, useRef, type DragEvent } from 'react';
-import { ChevronRight, ChevronDown, Folder, FolderOpen, Layers, List, Maximize2, Minimize2 } from 'lucide-react';
-import { Node } from '@/types/database';
-import AddNodeButton from './AddNodeButton';
-import Chip from '../common/Chip';
-import { getNodeIcon } from '@/utils/nodeIcons';
-import SearchModal from './SearchModal';
-import { DynamicIcon } from '../common/LucideIconPicker';
-import { usePersistentState } from '@/hooks/usePersistentState';
-
-interface NodesPanelProps {
- selectedNodes: Set;
- onNodeSelect: (nodeId: number, multiSelect: boolean) => void;
- onNodeCreated?: (node: Node) => void;
- onNodeDeleted?: (nodeId: number) => void;
- refreshTrigger?: number;
- onToggleFolderView?: (isOpen?: boolean) => void;
- folderViewOpen?: boolean;
-}
-
-interface LockedDimension {
- dimension: string;
- count: number;
- isPriority: boolean;
-}
-
-
-export default function NodesPanel({ selectedNodes, onNodeSelect, onNodeCreated, onNodeDeleted, refreshTrigger, onToggleFolderView, folderViewOpen = false }: NodesPanelProps) {
- const [nodes, setNodes] = useState([]);
- const [allNodes, setAllNodes] = useState([]);
- const [loading, setLoading] = useState(true);
- const [creating, setCreating] = useState(false);
- const [deletingNode, setDeletingNode] = useState(null);
- const [priorityDimensions, setPriorityDimensions] = useState([]);
- const [lockedDimensions, setLockedDimensions] = useState([]);
- const [expandedDimensions, setExpandedDimensions] = useState>(() => {
- if (typeof window === 'undefined') return new Set();
- const saved = window.localStorage.getItem('expandedDimensions');
- return saved ? new Set(JSON.parse(saved)) : new Set();
- });
- const [dimensionNodes, setDimensionNodes] = useState>({});
- const [selectedFilters, setSelectedFilters] = useState<{type: 'dimension' | 'title', value: string}[]>([]);
- const [dimensionsSectionCollapsed, setDimensionsSectionCollapsed] = useState(true); // Default: collapsed
- const [allNodesSectionCollapsed, setAllNodesSectionCollapsed] = useState(false);
- const [showSearchModal, setShowSearchModal] = useState(false);
- const [dropFeedback, setDropFeedback] = useState(null);
- const [dragHoverDimension, setDragHoverDimension] = useState(null);
- const draggedNodeRef = useRef<{ id: number; dimensions?: string[] } | null>(null);
-
- // Dimension icons (shared with FolderViewOverlay via localStorage)
- const [dimensionIcons] = usePersistentState>('ui.dimensionIcons', {});
-
- useEffect(() => {
- fetchNodes();
- fetchLockedDimensions();
- }, []);
-
- // Save expanded dimensions to localStorage
- useEffect(() => {
- if (typeof window === 'undefined') return;
- window.localStorage.setItem('expandedDimensions', JSON.stringify([...expandedDimensions]));
- }, [expandedDimensions]);
-
- // Refresh nodes and locked dimensions when SSE events trigger updates
- useEffect(() => {
- if (refreshTrigger !== undefined && refreshTrigger > 0) {
- console.log('🔄 Refreshing nodes and locked dimensions due to SSE event');
- fetchNodes();
- fetchLockedDimensions();
- }
- }, [refreshTrigger]);
-
- // Global Cmd+K / Ctrl+K keyboard shortcut for search
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
- e.preventDefault();
- setShowSearchModal(true);
- }
- };
-
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, []);
-
- // Re-apply filters when allNodes changes
- useEffect(() => {
- if (selectedFilters.length > 0) {
- applyFiltersWithSelection(selectedFilters);
- } else {
- setNodes(allNodes);
- }
- }, [allNodes, priorityDimensions]);
-
- const fetchNodes = async () => {
- try {
- const response = await fetch('/api/nodes?limit=100');
- const result = await response.json();
- const fetchedNodes = result.data || [];
- setAllNodes(fetchedNodes);
- setNodes(fetchedNodes);
- } catch (error) {
- console.error('Error fetching nodes:', error);
- } finally {
- setLoading(false);
- }
- };
-
- const fetchLockedDimensions = async () => {
- try {
- const response = await fetch('/api/dimensions/popular');
- const result = await response.json();
- if (result.success) {
- const locked = result.data.filter((d: any) => d.isPriority);
- setLockedDimensions(locked);
- const priority = locked.map((d: any) => d.dimension);
- setPriorityDimensions(priority);
- }
- } catch (error) {
- console.error('Error fetching locked dimensions:', error);
- }
- };
-
- const toggleDimension = async (dimension: string) => {
- const isCurrentlyExpanded = expandedDimensions.has(dimension);
-
- setExpandedDimensions(prev => {
- const next = new Set(prev);
- if (next.has(dimension)) {
- next.delete(dimension);
- } else {
- next.add(dimension);
- }
- return next;
- });
-
- // Fetch nodes for this dimension when expanding (if not already fetched)
- if (!isCurrentlyExpanded && !dimensionNodes[dimension]) {
- await fetchNodesForDimension(dimension);
- }
- };
-
- const fetchNodesForDimension = async (dimension: string) => {
- try {
- // Sort by edge count (most connected first), then updated_at
- const response = await fetch(`/api/nodes?dimensions=${encodeURIComponent(dimension)}&limit=200&sortBy=edges`);
- const result = await response.json();
- if (result.success) {
- setDimensionNodes(prev => ({
- ...prev,
- [dimension]: result.data || []
- }));
- }
- } catch (error) {
- console.error(`Error fetching nodes for dimension ${dimension}:`, error);
- }
- };
-
- const handleDimensionDragOver = (event: DragEvent) => {
- if (event.dataTransfer.types.includes('application/node-info') || event.dataTransfer.types.includes('text/plain')) {
- event.preventDefault();
- event.dataTransfer.dropEffect = 'copy';
- }
- };
-
- const handleDimensionDragEnter = (event: DragEvent, dimension: string) => {
- if (event.dataTransfer.types.includes('application/node-info') || event.dataTransfer.types.includes('text/plain')) {
- setDragHoverDimension(dimension);
- }
- };
-
- const handleDimensionDragLeave = (event: DragEvent, dimension: string) => {
- if (dragHoverDimension === dimension) {
- setDragHoverDimension(null);
- }
- };
-
- const handleNodeDropOnDimension = async (event: DragEvent, dimension: string) => {
- event.preventDefault();
- event.stopPropagation();
-
- // Try to get data from ref first (works in Electron/Tauri webviews)
- let payload: { id: number; dimensions?: string[] } | null = draggedNodeRef.current;
-
- // Fallback to dataTransfer for browser compatibility
- if (!payload) {
- const raw = event.dataTransfer.getData('application/node-info') || event.dataTransfer.getData('text/plain');
- if (raw) {
- try {
- payload = JSON.parse(raw);
- } catch (e) {
- console.error('Failed to parse drag data:', e);
- }
- }
- }
-
- // Clear the ref
- draggedNodeRef.current = null;
-
- if (!payload?.id) {
- console.warn('No valid node data in drop event');
- return;
- }
-
- try {
- const currentDimensions = payload.dimensions || [];
- if (currentDimensions.some((dim) => dim.toLowerCase() === dimension.toLowerCase())) {
- setDropFeedback(`Node already in ${dimension}`);
- setTimeout(() => setDropFeedback(null), 2500);
- return;
- }
-
- const updatedDimensions = Array.from(new Set([...currentDimensions, dimension]));
- const response = await fetch(`/api/nodes/${payload.id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ dimensions: updatedDimensions })
- });
-
- if (!response.ok) {
- throw new Error('Failed to update node dimensions');
- }
-
- setDropFeedback(`Added to ${dimension}`);
- setTimeout(() => setDropFeedback(null), 2500);
- if (expandedDimensions.has(dimension)) {
- fetchNodesForDimension(dimension);
- }
- fetchLockedDimensions();
- setDragHoverDimension(null);
- } catch (error) {
- console.error('Error handling node drop:', error);
- setDropFeedback('Failed to add dimension');
- setTimeout(() => setDropFeedback(null), 2500);
- setDragHoverDimension(null);
- }
- };
-
- const getNodesForDimension = (dimension: string): Node[] => {
- // Use fetched dimension-specific nodes if available, otherwise filter from loaded nodes
- return dimensionNodes[dimension] || nodes.filter(node =>
- node.dimensions?.some(d => d.toLowerCase() === dimension.toLowerCase())
- );
- };
-
- const applyFilters = () => {
- if (selectedFilters.length === 0) {
- setNodes(allNodes);
- return;
- }
-
- const dimensionFilters = selectedFilters.filter(f => f.type === 'dimension').map(f => f.value);
- const titleFilters = selectedFilters.filter(f => f.type === 'title').map(f => f.value);
-
- const filtered = allNodes.filter(node => {
- // Must match ALL selected dimension filters
- const dimensionMatch = dimensionFilters.length === 0 ||
- dimensionFilters.every(dim =>
- node.dimensions?.some(nodeDim => nodeDim.toLowerCase() === dim.toLowerCase())
- );
-
- // Must match ANY selected title filter
- const titleMatch = titleFilters.length === 0 ||
- titleFilters.some(title =>
- node.title?.toLowerCase().includes(title.toLowerCase())
- );
-
- return dimensionMatch && titleMatch;
- });
-
- // Sort by priority dimensions first
- const sorted = filtered.sort((a, b) => {
- const aHasPriority = a.dimensions?.some(dim => priorityDimensions.includes(dim)) || false;
- const bHasPriority = b.dimensions?.some(dim => priorityDimensions.includes(dim)) || false;
- if (aHasPriority && !bHasPriority) return -1;
- if (!aHasPriority && bHasPriority) return 1;
- return 0;
- });
-
- setNodes(sorted);
- };
-
- const addFilter = (type: 'dimension' | 'title', value: string) => {
- const newFilter = { type, value };
- const newFilters = [...selectedFilters, newFilter];
- setSelectedFilters(newFilters);
-
- // Apply filters immediately
- setTimeout(() => {
- if (newFilters.length === 0) {
- setNodes(allNodes);
- } else {
- applyFiltersWithSelection(newFilters);
- }
- }, 0);
- };
-
- const removeFilter = (index: number) => {
- const newFilters = selectedFilters.filter((_, i) => i !== index);
- setSelectedFilters(newFilters);
-
- // Apply filters immediately
- setTimeout(() => {
- if (newFilters.length === 0) {
- setNodes(allNodes);
- } else {
- applyFiltersWithSelection(newFilters);
- }
- }, 0);
- };
-
- const applyFiltersWithSelection = async (filters: {type: 'dimension' | 'title', value: string}[]) => {
- const dimensionFilters = filters.filter(f => f.type === 'dimension').map(f => f.value);
- const titleFilters = filters.filter(f => f.type === 'title').map(f => f.value);
-
- try {
- // Build API query params
- const params = new URLSearchParams();
-
- if (dimensionFilters.length > 0) {
- params.append('dimensions', dimensionFilters.join(','));
- }
-
- if (titleFilters.length > 0) {
- // For title filters, we'll search each one and combine results
- const titleResults: Node[] = [];
-
- for (const title of titleFilters) {
- const response = await fetch(`/api/nodes?search=${encodeURIComponent(title)}&limit=50`);
- const result = await response.json();
- if (result.success) {
- titleResults.push(...result.data);
- }
- }
-
- // If we have dimension filters too, intersect the results
- if (dimensionFilters.length > 0) {
- const dimensionResponse = await fetch(`/api/nodes?${params.toString()}&limit=200`);
- const dimensionResult = await dimensionResponse.json();
-
- if (dimensionResult.success) {
- // Find intersection: nodes that match dimensions AND titles
- const dimensionNodeIds = new Set(dimensionResult.data.map((n: Node) => n.id));
- const intersectedNodes = titleResults.filter(node => dimensionNodeIds.has(node.id));
- setNodes(intersectedNodes);
- }
- } else {
- // Only title filters
- setNodes(titleResults);
- }
- } else if (dimensionFilters.length > 0) {
- // Only dimension filters
- const response = await fetch(`/api/nodes?${params.toString()}&limit=200`);
- const result = await response.json();
-
- if (result.success) {
- setNodes(result.data);
- }
- }
- } catch (error) {
- console.error('Error applying filters:', error);
- // Fallback to local filtering
- const filtered = allNodes.filter(node => {
- const dimensionMatch = dimensionFilters.length === 0 ||
- dimensionFilters.every(dim =>
- node.dimensions?.some(nodeDim => nodeDim.toLowerCase() === dim.toLowerCase())
- );
-
- const titleMatch = titleFilters.length === 0 ||
- titleFilters.some(title =>
- node.title?.toLowerCase().includes(title.toLowerCase())
- );
-
- return dimensionMatch && titleMatch;
- });
- setNodes(filtered);
- }
- };
-
- const clearSearch = () => {
- setSelectedFilters([]);
- setNodes(allNodes);
- };
-
- const collapseAllDimensions = () => {
- setExpandedDimensions(new Set());
- };
-
- const handleNodeDragStart = (event: DragEvent, node: Node) => {
- event.dataTransfer.effectAllowed = 'copyMove';
- const nodeData = {
- id: node.id,
- title: node.title || 'Untitled',
- dimensions: node.dimensions || []
- };
- // Store in ref for webview compatibility (dataTransfer.getData can fail in Electron/Tauri)
- draggedNodeRef.current = nodeData;
- // Set multiple MIME types for different drop targets
- event.dataTransfer.setData('application/node-info', JSON.stringify(nodeData));
- // For chat input drops - includes title for [NODE:id:"title"] token
- event.dataTransfer.setData('application/x-rah-node', JSON.stringify({ id: node.id, title: node.title || 'Untitled' }));
- // Fallback for browsers/webviews that only support text/plain
- event.dataTransfer.setData('text/plain', `[NODE:${node.id}:"${node.title || 'Untitled'}"]`);
-
- const preview = document.createElement('div');
- preview.textContent = node.title || `Node #${node.id}`;
- preview.style.position = 'fixed';
- preview.style.top = '-1000px';
- preview.style.left = '-1000px';
- preview.style.padding = '4px 8px';
- preview.style.background = '#0f0f0f';
- preview.style.color = '#f8fafc';
- preview.style.fontSize = '11px';
- preview.style.fontWeight = '600';
- preview.style.borderRadius = '6px';
- preview.style.border = '1px solid #1f1f1f';
- document.body.appendChild(preview);
- event.dataTransfer.setDragImage(preview, 6, 6);
- setTimeout(() => {
- if (preview.parentNode) {
- preview.parentNode.removeChild(preview);
- }
- }, 0);
- };
-
- const handleNodeDragEnd = () => {
- // Clear ref if drag ends without a drop
- draggedNodeRef.current = null;
- };
-
- const renderNodeItem = (node: Node) => (
-
- );
-
- const handleAddNode = async () => {
- setCreating(true);
- try {
- const response = await fetch('/api/nodes', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- title: 'New Node',
- content: '',
- link: null,
- dimensions: []
- }),
- });
-
- const result = await response.json();
-
- if (result.success) {
- // Add new node to both lists immediately
- setAllNodes(prevNodes => [result.data, ...prevNodes]);
- setNodes(prevNodes => [result.data, ...prevNodes]);
-
- // Notify parent component about new node creation
- if (onNodeCreated) {
- onNodeCreated(result.data);
- }
- } else {
- console.error('Failed to create node:', result.message);
- }
- } catch (error) {
- console.error('Error creating node:', error);
- } finally {
- setCreating(false);
- }
- };
-
- // handleDeleteNode removed - deletion only available in focus panel
-
- const handleNodeSelectFromSearch = (nodeId: number) => {
- // Open node in focus panel without filtering the nodes panel
- onNodeSelect(nodeId, false);
- };
-
- return (
-
- {/* Search Modal */}
-
setShowSearchModal(false)}
- onNodeSelect={handleNodeSelectFromSearch}
- existingFilters={selectedFilters}
- />
-
- {/* Nodes List */}
-
- {loading ? (
-
- Loading nodes...
-
- ) : (
-
- {/* + ADD NODE Button & folder expand */}
-
-
-
-
-
- {dropFeedback && (
-
- {dropFeedback}
-
- )}
-
- {/* SEARCH Button */}
-
-
- {/* DIMENSIONS Section Header */}
- {lockedDimensions.length > 0 && (
-
- )}
-
- {/* Locked Dimension Sections */}
- {!dimensionsSectionCollapsed && lockedDimensions.map((lockedDim) => {
- const dimNodes = getNodesForDimension(lockedDim.dimension);
- const isExpanded = expandedDimensions.has(lockedDim.dimension);
-
- return (
-
- {/* Dimension Header */}
-
-
- {/* Dimension Nodes (when expanded) */}
- {isExpanded && (
-
- {dimNodes.length === 0 ? (
-
- No nodes in this dimension
-
- ) : (
- dimNodes.map((node) => renderNodeItem(node))
- )}
-
- )}
-
- );
- })}
-
- {/* ALL NODES Section Header */}
-
-
- {/* All Nodes List */}
- {!allNodesSectionCollapsed && (
- nodes.length === 0 ? (
-
- {selectedFilters.length > 0 ? 'No nodes match filters' : 'No nodes yet - click "Add Node" above to get started'}
-
- ) : (
- nodes.map((node) => renderNodeItem(node))
- )
- )}
-
- )}
-
-
-
- );
-}
diff --git a/src/components/settings/MapViewer.tsx b/src/components/settings/MapViewer.tsx
deleted file mode 100644
index a1a849b..0000000
--- a/src/components/settings/MapViewer.tsx
+++ /dev/null
@@ -1,425 +0,0 @@
-"use client";
-
-import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
-import type { Edge, Node } from '@/types/database';
-
-interface GraphNode extends Node {
- edge_count?: number;
- x: number;
- y: number;
- radius: number;
-}
-
-interface LockedDimension {
- name: string;
-}
-
-const NODE_LIMIT = 200;
-const LABEL_THRESHOLD = 15; // Top N nodes get labels
-
-export default function MapViewer() {
- const containerRef = useRef(null);
- const [containerSize, setContainerSize] = useState({ width: 800, height: 600 });
- const [nodes, setNodes] = useState([]);
- const [edges, setEdges] = useState([]);
- const [lockedDimensions, setLockedDimensions] = useState>(new Set());
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [selectedNode, setSelectedNode] = useState(null);
- const [transform, setTransform] = useState({ x: 0, y: 0, scale: 1 });
-
- // Resize observer
- useEffect(() => {
- const observer = new ResizeObserver(entries => {
- const entry = entries[0];
- if (entry?.contentRect) {
- setContainerSize({
- width: entry.contentRect.width,
- height: entry.contentRect.height,
- });
- }
- });
-
- if (containerRef.current) {
- observer.observe(containerRef.current);
- }
-
- return () => observer.disconnect();
- }, []);
-
- // Fetch data
- useEffect(() => {
- const fetchData = async () => {
- setLoading(true);
- setError(null);
- try {
- const [nodesRes, edgesRes, dimsRes] = await Promise.all([
- fetch(`/api/nodes?limit=${NODE_LIMIT}&sortBy=edges`),
- fetch('/api/edges'),
- fetch('/api/dimensions'),
- ]);
-
- if (!nodesRes.ok || !edgesRes.ok) {
- throw new Error('Failed to load data');
- }
-
- const nodesPayload = await nodesRes.json();
- const edgesPayload = await edgesRes.json();
-
- setNodes(nodesPayload.data || []);
- setEdges(edgesPayload.data || []);
-
- // Get locked dimensions
- if (dimsRes.ok) {
- const dimsPayload = await dimsRes.json();
- if (dimsPayload.success && dimsPayload.data) {
- const locked = (dimsPayload.data as LockedDimension[])
- .filter((d: LockedDimension & { is_locked?: boolean }) => d.is_locked)
- .map((d: LockedDimension) => d.name);
- setLockedDimensions(new Set(locked));
- }
- }
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Unknown error');
- } finally {
- setLoading(false);
- }
- };
-
- fetchData();
- }, []);
-
- // Position nodes in a cluster layout
- const graphNodes = useMemo(() => {
- if (nodes.length === 0) return [];
-
- const { width, height } = containerSize;
- const centerX = width / 2;
- const centerY = height / 2;
-
- // Sort by edge count (highest first)
- const sorted = [...nodes].sort((a, b) => (b.edge_count ?? 0) - (a.edge_count ?? 0));
-
- // Find max edge count for scaling
- const maxEdges = Math.max(...sorted.map(n => n.edge_count ?? 0), 1);
-
- // Position nodes using a spiral/cluster approach
- // High-edge nodes get placed more centrally with more space
- return sorted.map((node, index) => {
- const edgeCount = node.edge_count ?? 0;
- const edgeRatio = edgeCount / maxEdges;
-
- // Radius from center - higher edge count = closer to center
- // Use golden angle for nice distribution
- const goldenAngle = Math.PI * (3 - Math.sqrt(5));
- const angle = index * goldenAngle;
-
- // Distance from center inversely proportional to edge count
- // Top nodes cluster in center, others spread out
- // Extra spacing for labeled nodes (top 15) to prevent label overlap
- const isLabeled = index < LABEL_THRESHOLD;
- const labelSpacing = isLabeled ? 60 : 0;
- const baseDistance = 80 + labelSpacing + (1 - edgeRatio) * Math.min(width, height) * 0.35;
- const distance = baseDistance + (index * 4); // More spread between nodes
-
- const x = centerX + Math.cos(angle) * distance;
- const y = centerY + Math.sin(angle) * distance;
-
- // Node size based on edge count
- // Min 3px for tiny dots, max ~20px for top nodes
- const minRadius = 3;
- const maxRadius = 18;
- const radius = minRadius + edgeRatio * (maxRadius - minRadius);
-
- return {
- ...node,
- x,
- y,
- radius,
- };
- });
- }, [nodes, containerSize]);
-
- // Get edges between visible nodes
- const graphEdges = useMemo(() => {
- if (graphNodes.length === 0 || edges.length === 0) return [];
-
- const nodeMap = new Map();
- graphNodes.forEach(node => nodeMap.set(node.id, node));
-
- return edges
- .map(edge => {
- const source = nodeMap.get(edge.from_node_id);
- const target = nodeMap.get(edge.to_node_id);
- if (!source || !target) return null;
- return { id: edge.id, source, target };
- })
- .filter(Boolean) as Array<{ id: number; source: GraphNode; target: GraphNode }>;
- }, [edges, graphNodes]);
-
- // Get connected node IDs for selected node
- const connectedNodeIds = useMemo(() => {
- if (!selectedNode) return new Set();
- const connected = new Set();
- edges.forEach(edge => {
- if (edge.from_node_id === selectedNode.id) connected.add(edge.to_node_id);
- if (edge.to_node_id === selectedNode.id) connected.add(edge.from_node_id);
- });
- return connected;
- }, [selectedNode, edges]);
-
- // Pan handling
- const handlePanStart = (event: React.PointerEvent) => {
- const startX = event.clientX;
- const startY = event.clientY;
- const originX = transform.x;
- const originY = transform.y;
-
- const handleMove = (moveEvent: PointerEvent) => {
- setTransform(prev => ({
- ...prev,
- x: originX + (moveEvent.clientX - startX),
- y: originY + (moveEvent.clientY - startY),
- }));
- };
-
- const handleUp = () => {
- window.removeEventListener('pointermove', handleMove);
- window.removeEventListener('pointerup', handleUp);
- };
-
- window.addEventListener('pointermove', handleMove);
- window.addEventListener('pointerup', handleUp);
- };
-
- const handleZoom = (direction: 'in' | 'out' | 'reset') => {
- if (direction === 'reset') {
- setTransform({ x: 0, y: 0, scale: 1 });
- return;
- }
- setTransform(prev => ({
- ...prev,
- scale: direction === 'in'
- ? Math.min(prev.scale + 0.2, 3)
- : Math.max(prev.scale - 0.2, 0.5),
- }));
- };
-
- if (loading) {
- return (
-
- Loading map...
-
- );
- }
-
- if (error) {
- return (
-
- {error}
-
- );
- }
-
- if (graphNodes.length === 0) {
- return (
-
- No nodes to display
-
- );
- }
-
- return (
-
- {/* Zoom controls */}
-
-
-
-
-
-
- {/* Selected node info */}
- {selectedNode && (
-
-
-
- {selectedNode.title || 'Untitled'}
-
-
-
-
- {connectedNodeIds.size} connected nodes · {selectedNode.edge_count ?? 0} total edges
-
-
- Click a highlighted node to explore
-
- {selectedNode.dimensions && selectedNode.dimensions.length > 0 && (
-
- {selectedNode.dimensions.slice(0, 5).map(dim => (
-
- {dim}
-
- ))}
-
- )}
-
- )}
-
- {/* SVG Graph */}
-
-
- );
-}
-
-const controlBtn: CSSProperties = {
- width: 32,
- height: 32,
- borderRadius: 6,
- border: '1px solid #262626',
- background: '#141414',
- color: '#888',
- fontSize: 16,
- cursor: 'pointer',
-};
-
-const infoPanel: CSSProperties = {
- position: 'absolute',
- bottom: 16,
- left: 16,
- width: 260,
- background: '#0a0a0a',
- border: '1px solid #1f1f1f',
- borderRadius: 8,
- padding: 14,
- zIndex: 10,
-};
diff --git a/src/components/settings/SettingsCog.tsx b/src/components/settings/SettingsCog.tsx
deleted file mode 100644
index 468eb8e..0000000
--- a/src/components/settings/SettingsCog.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-"use client";
-
-import { User } from 'lucide-react';
-
-interface SettingsCogProps {
- onClick: () => void;
-}
-
-export default function SettingsCog({ onClick }: SettingsCogProps) {
- return (
-
- );
-}
diff --git a/src/components/settings/SettingsModal.tsx b/src/components/settings/SettingsModal.tsx
index 336868a..57fc06f 100644
--- a/src/components/settings/SettingsModal.tsx
+++ b/src/components/settings/SettingsModal.tsx
@@ -7,7 +7,6 @@ import ToolsViewer from './ToolsViewer';
import WorkflowsViewer from './WorkflowsViewer';
import ApiKeysViewer from './ApiKeysViewer';
import DatabaseViewer from './DatabaseViewer';
-import MapViewer from './MapViewer';
import ExternalAgentsPanel from './ExternalAgentsPanel';
import ContextViewer from './ContextViewer';
import { apiKeyService } from '@/services/storage/apiKeys';
@@ -18,7 +17,6 @@ export type SettingsTab =
| 'workflows'
| 'apikeys'
| 'database'
- | 'map'
| 'context'
| 'agents';
@@ -197,20 +195,6 @@ export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsM
>
Context
- setActiveTab('map')}
- style={{
- padding: '12px 24px',
- fontSize: '14px',
- color: activeTab === 'map' ? '#fff' : '#888',
- background: activeTab === 'map' ? '#1a3a2a' : 'transparent',
- borderLeft: activeTab === 'map' ? '3px solid #22c55e' : '3px solid transparent',
- cursor: 'pointer',
- transition: 'all 0.2s'
- }}
- >
- Map
-
setActiveTab('agents')}
style={{
@@ -315,7 +299,6 @@ export default function SettingsModal({ isOpen, onClose, initialTab }: SettingsM
{activeTab === 'apikeys' && 'API Keys'}
{activeTab === 'database' && 'Knowledge Database'}
{activeTab === 'context' && 'Auto-Context'}
- {activeTab === 'map' && 'Knowledge Map'}
{activeTab === 'agents' && 'External Agents'}
}
{activeTab === 'database' && }
{activeTab === 'context' && }
- {activeTab === 'map' && }
{activeTab === 'agents' && }
diff --git a/src/components/views/ViewPanel.tsx b/src/components/views/ViewPanel.tsx
deleted file mode 100644
index 1e67f85..0000000
--- a/src/components/views/ViewPanel.tsx
+++ /dev/null
@@ -1,286 +0,0 @@
-"use client";
-
-import { useState, useEffect, useCallback } from 'react';
-import { Eye, List, Columns, LayoutGrid } from 'lucide-react';
-import { ViewType, ViewConfig, ViewFilter, DEFAULT_VIEW_CONFIG, KanbanColumn } from '@/types/views';
-import { Node } from '@/types/database';
-import ListView from './ListView';
-import KanbanView from './KanbanView';
-import GridView from './GridView';
-import ViewFilters from './ViewFilters';
-
-interface ViewPanelProps {
- viewMode: ViewType;
- onViewModeChange: (mode: ViewType) => void;
- onNodeClick: (nodeId: number) => void;
- refreshTrigger?: number;
-}
-
-export default function ViewPanel({
- viewMode,
- onViewModeChange,
- onNodeClick,
- refreshTrigger
-}: ViewPanelProps) {
- const [nodes, setNodes] = useState([]);
- const [loading, setLoading] = useState(true);
- const [config, setConfig] = useState(DEFAULT_VIEW_CONFIG);
- const [dimensions, setDimensions] = useState([]);
-
- // Fetch all nodes
- const fetchNodes = useCallback(async () => {
- setLoading(true);
- try {
- // Build query params from filters
- const params = new URLSearchParams();
- params.set('limit', '500');
-
- if (config.filters.length > 0) {
- const includeDimensions = config.filters
- .filter(f => f.operator === 'includes')
- .map(f => f.dimension);
- if (includeDimensions.length > 0) {
- params.set('dimensions', includeDimensions.join(','));
- }
- }
-
- if (config.sort) {
- params.set('sortBy', config.sort.field);
- params.set('sortOrder', config.sort.direction);
- }
-
- const response = await fetch(`/api/nodes?${params.toString()}`);
- if (response.ok) {
- const data = await response.json();
- let filteredNodes = data.nodes || [];
-
- // Apply exclude filters client-side
- const excludeDimensions = config.filters
- .filter(f => f.operator === 'excludes')
- .map(f => f.dimension);
- if (excludeDimensions.length > 0) {
- filteredNodes = filteredNodes.filter((node: Node) =>
- !excludeDimensions.some(dim => node.dimensions?.includes(dim))
- );
- }
-
- setNodes(filteredNodes);
- }
- } catch (error) {
- console.error('Failed to fetch nodes:', error);
- } finally {
- setLoading(false);
- }
- }, [config.filters, config.sort]);
-
- // Fetch available dimensions
- const fetchDimensions = useCallback(async () => {
- try {
- const response = await fetch('/api/dimensions/popular');
- if (response.ok) {
- const data = await response.json();
- const dims = data.data?.map((d: { dimension: string }) => d.dimension) || [];
- setDimensions(dims);
- }
- } catch (error) {
- console.error('Failed to fetch dimensions:', error);
- }
- }, []);
-
- useEffect(() => {
- fetchNodes();
- }, [fetchNodes, refreshTrigger]);
-
- useEffect(() => {
- fetchDimensions();
- }, [fetchDimensions]);
-
- const handleFilterChange = (filters: ViewFilter[]) => {
- setConfig(prev => ({ ...prev, filters }));
- };
-
- const handleFilterLogicChange = (logic: 'and' | 'or') => {
- setConfig(prev => ({ ...prev, filterLogic: logic }));
- };
-
- const handleColumnChange = (columns: KanbanColumn[]) => {
- setConfig(prev => ({ ...prev, columns }));
- };
-
- const handleNodeDimensionUpdate = async (nodeId: number, newDimension: string, oldDimension?: string) => {
- // Update node's dimensions (add new, remove old if kanban move)
- try {
- const node = nodes.find(n => n.id === nodeId);
- if (!node) return;
-
- let newDimensions = [...(node.dimensions || [])];
-
- // Remove old dimension if moving in kanban
- if (oldDimension) {
- newDimensions = newDimensions.filter(d => d !== oldDimension);
- }
-
- // Add new dimension if not already present
- if (!newDimensions.includes(newDimension)) {
- newDimensions.push(newDimension);
- }
-
- const response = await fetch(`/api/nodes/${nodeId}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ dimensions: newDimensions })
- });
-
- if (response.ok) {
- // Refresh nodes to get updated data
- fetchNodes();
- }
- } catch (error) {
- console.error('Failed to update node dimensions:', error);
- }
- };
-
- return (
-
- {/* Header with View Mode Selector */}
-
-
- {nodes.length} nodes
-
-
- {/* View Mode Buttons */}
-
-
-
-
-
-
-
-
- {/* Filters Bar */}
-
-
- {/* Content Area */}
-
- {loading ? (
-
- Loading...
-
- ) : (
- <>
- {viewMode === 'list' && (
-
- )}
- {viewMode === 'kanban' && (
-
- )}
- {viewMode === 'grid' && (
-
- )}
- >
- )}
-
-
- );
-}
diff --git a/src/services/analytics/costs.ts b/src/services/analytics/costs.ts
deleted file mode 100644
index bc87d08..0000000
--- a/src/services/analytics/costs.ts
+++ /dev/null
@@ -1,313 +0,0 @@
-import { getSQLiteClient } from '@/services/database/sqlite-client';
-import { CostReport, TraceCostSummary, CacheEffectiveness } from '@/types/analytics';
-
-export class CostAnalytics {
- static getCostsByDateRange(startDate: string, endDate: string): CostReport {
- const db = getSQLiteClient();
-
- const chats = db.prepare(`
- SELECT
- id,
- helper_name,
- metadata,
- created_at
- FROM chats
- WHERE created_at >= ? AND created_at <= ?
- `).all(startDate, endDate) as Array<{
- id: number;
- helper_name: string;
- metadata: string;
- created_at: string;
- }>;
-
- let totalCostUsd = 0;
- let totalTokens = 0;
- let inputTokens = 0;
- let outputTokens = 0;
- let cacheReadTokens = 0;
- let cacheWriteTokens = 0;
- let cacheHitCount = 0;
- let cacheSavingsUsd = 0;
-
- const costByAgent: Record = {};
- const costByModel: Record = {};
-
- for (const chat of chats) {
- let metadata: any = {};
- try {
- metadata = JSON.parse(chat.metadata || '{}');
- } catch (e) {
- continue;
- }
-
- const costUsd = metadata.estimated_cost_usd || 0;
- const tokens = metadata.total_tokens || 0;
- const inputToks = metadata.input_tokens || 0;
- const outputToks = metadata.output_tokens || 0;
- const cacheRead = metadata.cache_read_tokens || 0;
- const cacheWrite = metadata.cache_write_tokens || 0;
- const cacheHit = metadata.cache_hit || false;
- const model = metadata.model_used || 'unknown';
-
- totalCostUsd += costUsd;
- totalTokens += tokens;
- inputTokens += inputToks;
- outputTokens += outputToks;
- cacheReadTokens += cacheRead;
- cacheWriteTokens += cacheWrite;
- if (cacheHit) cacheHitCount++;
-
- if (metadata.cache_savings_pct && cacheHit) {
- const fullCost = costUsd / (1 - (metadata.cache_savings_pct / 100));
- cacheSavingsUsd += (fullCost - costUsd);
- }
-
- if (!costByAgent[chat.helper_name]) {
- costByAgent[chat.helper_name] = { costUsd: 0, chats: 0, tokens: 0 };
- }
- costByAgent[chat.helper_name].costUsd += costUsd;
- costByAgent[chat.helper_name].chats += 1;
- costByAgent[chat.helper_name].tokens += tokens;
-
- if (!costByModel[model]) {
- costByModel[model] = { costUsd: 0, chats: 0, tokens: 0 };
- }
- costByModel[model].costUsd += costUsd;
- costByModel[model].chats += 1;
- costByModel[model].tokens += tokens;
- }
-
- const cacheRequests = chats.filter(c => {
- try {
- const meta = JSON.parse(c.metadata || '{}');
- return meta.provider === 'anthropic';
- } catch {
- return false;
- }
- }).length;
-
- return {
- periodStart: startDate,
- periodEnd: endDate,
- totalCostUsd: parseFloat(totalCostUsd.toFixed(6)),
- totalChats: chats.length,
- totalTokens,
- inputTokens,
- outputTokens,
- cacheReadTokens,
- cacheWriteTokens,
- cacheHitRate: cacheRequests > 0 ? cacheHitCount / cacheRequests : 0,
- cacheSavingsUsd: parseFloat(cacheSavingsUsd.toFixed(6)),
- avgCostPerChat: chats.length > 0 ? parseFloat((totalCostUsd / chats.length).toFixed(6)) : 0,
- avgTokensPerChat: chats.length > 0 ? Math.round(totalTokens / chats.length) : 0,
- costByAgent,
- costByModel,
- };
- }
-
- static getCostsByAgent(agentName: string, startDate?: string, endDate?: string): CostReport {
- const db = getSQLiteClient();
-
- let query = `
- SELECT
- id,
- helper_name,
- metadata,
- created_at
- FROM chats
- WHERE helper_name = ?
- `;
- const params: any[] = [agentName];
-
- if (startDate && endDate) {
- query += ` AND created_at >= ? AND created_at <= ?`;
- params.push(startDate, endDate);
- }
-
- const chats = db.prepare(query).all(...params) as Array<{
- id: number;
- helper_name: string;
- metadata: string;
- created_at: string;
- }>;
-
- if (chats.length === 0) {
- return {
- periodStart: startDate || '',
- periodEnd: endDate || '',
- totalCostUsd: 0,
- totalChats: 0,
- totalTokens: 0,
- inputTokens: 0,
- outputTokens: 0,
- cacheReadTokens: 0,
- cacheWriteTokens: 0,
- cacheHitRate: 0,
- cacheSavingsUsd: 0,
- avgCostPerChat: 0,
- avgTokensPerChat: 0,
- costByAgent: {},
- costByModel: {},
- };
- }
-
- const start = startDate || chats[chats.length - 1].created_at;
- const end = endDate || chats[0].created_at;
-
- return this.getCostsByDateRange(start, end);
- }
-
- static getCostsByTrace(traceId: string): TraceCostSummary {
- const db = getSQLiteClient();
-
- const chats = db.prepare(`
- SELECT
- id,
- helper_name,
- agent_type,
- metadata,
- created_at
- FROM chats
- WHERE json_extract(metadata, '$.trace_id') = ?
- ORDER BY created_at ASC
- `).all(traceId) as Array<{
- id: number;
- helper_name: string;
- agent_type: string;
- metadata: string;
- created_at: string;
- }>;
-
- let totalCostUsd = 0;
- let orchestratorCost = 0;
- let executorCost = 0;
- let plannerCost = 0;
- let totalTokens = 0;
-
- const interactions: TraceCostSummary['interactions'] = [];
-
- for (const chat of chats) {
- let metadata: any = {};
- try {
- metadata = JSON.parse(chat.metadata || '{}');
- } catch (e) {
- continue;
- }
-
- const costUsd = metadata.estimated_cost_usd || 0;
- const tokens = metadata.total_tokens || 0;
-
- totalCostUsd += costUsd;
- totalTokens += tokens;
-
- if (chat.agent_type === 'orchestrator') {
- orchestratorCost += costUsd;
- } else if (chat.agent_type === 'executor') {
- executorCost += costUsd;
- } else if (chat.agent_type === 'planner') {
- plannerCost += costUsd;
- }
-
- interactions.push({
- chatId: chat.id,
- agentName: chat.helper_name,
- costUsd: parseFloat(costUsd.toFixed(6)),
- tokens,
- createdAt: chat.created_at,
- });
- }
-
- return {
- traceId,
- totalCostUsd: parseFloat(totalCostUsd.toFixed(6)),
- chatCount: chats.length,
- orchestratorCost: parseFloat(orchestratorCost.toFixed(6)),
- executorCost: parseFloat(executorCost.toFixed(6)),
- plannerCost: parseFloat(plannerCost.toFixed(6)),
- totalTokens,
- interactions,
- };
- }
-
- static getCacheEffectiveness(startDate?: string, endDate?: string): CacheEffectiveness {
- const db = getSQLiteClient();
-
- let query = `
- SELECT
- metadata
- FROM chats
- WHERE json_extract(metadata, '$.provider') = 'anthropic'
- `;
- const params: any[] = [];
-
- if (startDate && endDate) {
- query += ` AND created_at >= ? AND created_at <= ?`;
- params.push(startDate, endDate);
- }
-
- const chats = db.prepare(query).all(...params) as Array<{ metadata: string }>;
-
- let totalRequests = 0;
- let cacheHits = 0;
- let cacheMisses = 0;
- let totalCacheSavingsUsd = 0;
- let totalTokensSaved = 0;
-
- for (const chat of chats) {
- let metadata: any = {};
- try {
- metadata = JSON.parse(chat.metadata || '{}');
- } catch (e) {
- continue;
- }
-
- totalRequests++;
-
- if (metadata.cache_hit) {
- cacheHits++;
- const cacheReadTokens = metadata.cache_read_tokens || 0;
- totalTokensSaved += cacheReadTokens;
-
- if (metadata.cache_savings_pct && metadata.estimated_cost_usd) {
- const fullCost = metadata.estimated_cost_usd / (1 - (metadata.cache_savings_pct / 100));
- totalCacheSavingsUsd += (fullCost - metadata.estimated_cost_usd);
- }
- } else {
- cacheMisses++;
- }
- }
-
- return {
- totalRequests,
- cacheHits,
- cacheMisses,
- hitRate: totalRequests > 0 ? parseFloat((cacheHits / totalRequests).toFixed(4)) : 0,
- totalCacheSavingsUsd: parseFloat(totalCacheSavingsUsd.toFixed(6)),
- avgSavingsPerHit: cacheHits > 0 ? parseFloat((totalCacheSavingsUsd / cacheHits).toFixed(6)) : 0,
- totalTokensSaved,
- };
- }
-
- static getDailyBreakdown(days: number = 7): Array<{ date: string; cost: number; chats: number; tokens: number }> {
- const db = getSQLiteClient();
-
- const result = db.prepare(`
- SELECT
- DATE(created_at) as date,
- SUM(COALESCE(json_extract(metadata, '$.estimated_cost_usd'), 0)) as cost,
- COUNT(*) as chats,
- SUM(COALESCE(json_extract(metadata, '$.total_tokens'), 0)) as tokens
- FROM chats
- WHERE created_at >= DATE('now', '-' || ? || ' days')
- GROUP BY DATE(created_at)
- ORDER BY date DESC
- `).all(days) as Array<{ date: string; cost: number; chats: number; tokens: number }>;
-
- return result.map(row => ({
- date: row.date,
- cost: parseFloat(Number(row.cost).toFixed(6)),
- chats: Number(row.chats),
- tokens: Number(row.tokens),
- }));
- }
-}
diff --git a/src/services/tools/resultCache.ts b/src/services/tools/resultCache.ts
deleted file mode 100644
index bcb87fa..0000000
--- a/src/services/tools/resultCache.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-type Entry = { data: any; ts: number };
-
-class ResultCache {
- private store = new Map();
- private ttlMs = 10 * 60 * 1000; // 10 minutes
-
- set(id: string, data: any) {
- if (!id) return;
- this.store.set(id, { data, ts: Date.now() });
- this.gc();
- }
-
- get(id: string): any | null {
- const e = this.store.get(id);
- if (!e) return null;
- if (Date.now() - e.ts > this.ttlMs) {
- this.store.delete(id);
- return null;
- }
- return e.data;
- }
-
- private gc() {
- const now = Date.now();
- for (const [k, v] of this.store.entries()) {
- if (now - v.ts > this.ttlMs) this.store.delete(k);
- }
- }
-}
-
-export const resultCache = new ResultCache();
-
diff --git a/src/styles/terminal-theme.css b/src/styles/terminal-theme.css
deleted file mode 100644
index 171cd7f..0000000
--- a/src/styles/terminal-theme.css
+++ /dev/null
@@ -1,398 +0,0 @@
-/* RA-H Terminal Theme Design System */
-
-:root {
- /* Core Backgrounds - Ultra minimal dark */
- --bg-primary: #0a0a0a; /* Main app background */
- --bg-secondary: #0f0f0f; /* Panel backgrounds */
- --bg-surface: #151515; /* Elevated surfaces */
- --bg-hover: #1a1a1a; /* Hover states */
-
- /* Borders & Dividers - Subtle definition */
- --border-subtle: #1f1f1f; /* Very subtle borders */
- --border-default: #2a2a2a; /* Default borders */
- --border-strong: #353535; /* Strong emphasis borders */
-
- /* Text Hierarchy - High contrast */
- --text-primary: #e5e5e5; /* Primary text */
- --text-secondary: #a8a8a8; /* Secondary text */
- --text-muted: #6b6b6b; /* Muted/disabled text */
- --text-subtle: #4a4a4a; /* Very subtle text */
-
- /* Terminal Accent Colors - Distinctive but minimal */
- --accent-blue: #5c9aff; /* Primary actions / User messages */
- --accent-green: #52d97a; /* Success / Assistant messages */
- --accent-yellow: #ffcc66; /* Warning / Tool usage */
- --accent-red: #ff6b6b; /* Error states */
- --accent-purple: #b794f6; /* Thinking / Processing */
- --accent-cyan: #69d2e7; /* Special highlights */
-
- /* Message Status Indicators */
- --dot-user: #5c9aff; /* User message indicator */
- --dot-assistant: #52d97a; /* Assistant indicator */
- --dot-tool: #ffcc66; /* Tool usage indicator */
- --dot-thinking: #b794f6; /* Thinking indicator */
- --dot-system: #69d2e7; /* System messages */
-
- /* Semantic Colors */
- --color-success: #52d97a;
- --color-warning: #ffcc66;
- --color-error: #ff6b6b;
- --color-info: #5c9aff;
-
- /* Typography */
- --font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', ui-monospace, Consolas, monospace;
- --font-size-xs: 11px;
- --font-size-sm: 12px;
- --font-size-base: 13px;
- --font-size-md: 14px;
- --font-size-lg: 16px;
-
- /* Spacing */
- --spacing-xs: 4px;
- --spacing-sm: 8px;
- --spacing-md: 12px;
- --spacing-lg: 16px;
- --spacing-xl: 24px;
-
- /* Animations */
- --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
- --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
- --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-/* Base Terminal Styles */
-.terminal-container {
- background: var(--bg-primary);
- color: var(--text-primary);
- font-family: var(--font-mono);
- font-size: var(--font-size-base);
- line-height: 1.6;
- letter-spacing: 0.025em;
- font-feature-settings: 'liga' 1, 'calt' 1;
-}
-
-/* Message Display Styles */
-.message-container {
- display: flex;
- gap: var(--spacing-md);
- padding: var(--spacing-sm) 0;
- font-size: var(--font-size-base);
- line-height: 1.6;
-}
-
-.message-indicator {
- flex-shrink: 0;
- width: 6px;
- height: 6px;
- margin-top: 7px;
- border-radius: 50%;
- transition: all var(--transition-fast);
-}
-
-.message-indicator.user {
- background: var(--dot-user);
-}
-
-.message-indicator.assistant {
- background: var(--dot-assistant);
-}
-
-.message-indicator.tool {
- background: var(--dot-tool);
-}
-
-.message-indicator.thinking {
- background: var(--dot-thinking);
- animation: pulse 1.5s infinite;
-}
-
-.message-indicator.system {
- background: var(--dot-system);
-}
-
-@keyframes pulse {
- 0%, 100% {
- opacity: 1;
- transform: scale(1);
- }
- 50% {
- opacity: 0.6;
- transform: scale(1.2);
- }
-}
-
-.message-content {
- flex: 1;
- min-width: 0;
-}
-
-.message-text {
- color: var(--text-primary);
- white-space: pre-wrap;
- word-break: break-word;
-}
-
-.message-timestamp {
- display: inline-block;
- margin-top: var(--spacing-xs);
- color: var(--text-subtle);
- font-size: var(--font-size-xs);
-}
-
-/* Terminal Input Styles */
-.terminal-input-container {
- display: flex;
- align-items: flex-start;
- gap: var(--spacing-sm);
- padding: var(--spacing-md);
- background: var(--bg-secondary);
- border-top: 1px solid var(--border-default);
-}
-
-.prompt-symbol {
- color: var(--text-muted);
- font-size: var(--font-size-md);
- line-height: 1.5;
- padding-top: 2px;
- user-select: none;
-}
-
-.terminal-input-wrapper {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.terminal-input {
- background: var(--bg-surface);
- border: 1px solid var(--border-subtle);
- color: var(--text-primary);
- font-family: var(--font-mono);
- font-size: var(--font-size-base);
- padding: var(--spacing-sm) var(--spacing-md);
- resize: none;
- outline: none;
- transition: all var(--transition-fast);
- border-radius: 2px;
- min-height: 32px;
- line-height: 1.5;
-}
-
-.terminal-input:focus {
- border-color: var(--accent-blue);
- background: var(--bg-primary);
- box-shadow: 0 0 0 1px rgba(92, 154, 255, 0.1);
-}
-
-.terminal-input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.terminal-input::placeholder {
- color: var(--text-subtle);
-}
-
-.input-actions {
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
-}
-
-.input-hints {
- display: flex;
- gap: var(--spacing-md);
- color: var(--text-subtle);
- font-size: var(--font-size-xs);
- user-select: none;
-}
-
-.input-hint {
- display: flex;
- align-items: center;
- gap: var(--spacing-xs);
-}
-
-.key-hint {
- padding: 2px 4px;
- background: var(--bg-surface);
- border: 1px solid var(--border-subtle);
- border-radius: 2px;
- font-size: 10px;
-}
-
-/* Tool Indicator Styles */
-.tool-indicator {
- display: inline-flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-xs) 0;
- color: var(--text-secondary);
- font-size: var(--font-size-sm);
-}
-
-.tool-spinner {
- display: inline-block;
- width: 12px;
- height: 12px;
- border: 2px solid var(--border-default);
- border-top-color: var(--dot-tool);
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}
-
-.tool-name {
- color: var(--dot-tool);
- font-weight: 500;
-}
-
-.tool-status {
- color: var(--text-muted);
-}
-
-.tool-complete {
- color: var(--color-success);
-}
-
-.tool-error {
- color: var(--color-error);
-}
-
-/* ASCII Banner Styles */
-.ascii-banner {
- padding: var(--spacing-xl);
- color: var(--text-muted);
- font-size: var(--font-size-sm);
- line-height: 1.2;
- white-space: pre;
- font-family: var(--font-mono);
- opacity: 0;
- animation: fadeIn 0.5s ease-out forwards;
-}
-
-@keyframes fadeIn {
- to { opacity: 1; }
-}
-
-.ascii-banner-title {
- color: var(--text-secondary);
-}
-
-.ascii-banner-stats {
- color: var(--text-subtle);
-}
-
-/* Helper Tab Styles */
-.helper-tab {
- position: relative;
- padding: var(--spacing-sm) var(--spacing-md);
- background: transparent;
- border: none;
- border-bottom: 2px solid transparent;
- color: var(--text-muted);
- font-family: var(--font-mono);
- font-size: var(--font-size-sm);
- cursor: pointer;
- transition: all var(--transition-fast);
- outline: none;
- white-space: nowrap;
-}
-
-.helper-tab:hover {
- background: var(--bg-hover);
- color: var(--text-secondary);
-}
-
-.helper-tab.active {
- background: var(--bg-primary);
- border-bottom-color: var(--accent-blue);
- color: var(--text-primary);
-}
-
-/* Scrollbar Styles */
-.terminal-scrollbar::-webkit-scrollbar {
- width: 6px;
- height: 6px;
-}
-
-.terminal-scrollbar::-webkit-scrollbar-track {
- background: var(--bg-primary);
-}
-
-.terminal-scrollbar::-webkit-scrollbar-thumb {
- background: var(--border-strong);
- border-radius: 2px;
-}
-
-.terminal-scrollbar::-webkit-scrollbar-thumb:hover {
- background: var(--text-subtle);
-}
-
-/* Loading States */
-.loading-dots {
- display: inline-flex;
- gap: 2px;
-}
-
-.loading-dot {
- width: 4px;
- height: 4px;
- border-radius: 50%;
- background: var(--text-muted);
- animation: loadingPulse 1.4s infinite ease-in-out both;
-}
-
-.loading-dot:nth-child(1) {
- animation-delay: -0.32s;
-}
-
-.loading-dot:nth-child(2) {
- animation-delay: -0.16s;
-}
-
-@keyframes loadingPulse {
- 0%, 80%, 100% {
- transform: scale(0.8);
- opacity: 0.5;
- }
- 40% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-/* Focus States for Accessibility */
-.terminal-focusable:focus-visible {
- outline: 2px solid var(--accent-blue);
- outline-offset: 2px;
-}
-
-/* Utility Classes */
-.terminal-text-muted {
- color: var(--text-muted);
-}
-
-.terminal-text-subtle {
- color: var(--text-subtle);
-}
-
-.terminal-text-error {
- color: var(--color-error);
-}
-
-.terminal-text-success {
- color: var(--color-success);
-}
-
-.terminal-bg-surface {
- background: var(--bg-surface);
-}
-
-.terminal-border-default {
- border-color: var(--border-default);
-}
\ No newline at end of file
diff --git a/src/styles/terminalTheme.ts b/src/styles/terminalTheme.ts
deleted file mode 100644
index 91f6b4a..0000000
--- a/src/styles/terminalTheme.ts
+++ /dev/null
@@ -1,250 +0,0 @@
-/**
- * Terminal Theme Constants for ra-h
- * Centralized styling for terminal-inspired UI elements
- */
-
-import { CSSProperties } from 'react';
-
-// Terminal Color Palette
-export const TERMINAL_COLORS = {
- // Message indicators (Claude Code style)
- user: '#3b82f6', // Blue dot for user messages
- assistant: '#10b981', // Green dot for assistant messages
- tool: '#f59e0b', // Orange dot for tool use
- processing: '#eab308', // Yellow dot for thinking/processing states
-
- // Terminal elements
- prompt: '#10b981', // Terminal green for $ prompt
- accent: '#10b981', // Primary terminal accent color
-
- // Base colors (terminal-inspired palette)
- bg: {
- primary: '#000', // Main background
- secondary: '#000', // Secondary background (input area) - same as primary
- elevated: '#000', // Elevated surfaces (message bubbles) - same as primary for borderless look
- },
-
- text: {
- primary: '#d1d5db', // Primary text
- secondary: '#666', // Muted text
- tertiary: '#444', // Very muted text
- },
-
- border: {
- primary: '#333', // Primary borders
- secondary: '#222', // Subtle borders
- }
-} as const;
-
-// Terminal Typography
-export const TERMINAL_FONTS = {
- mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
- system: 'inherit'
-} as const;
-
-// Terminal Component Styles
-export const TERMINAL_STYLES = {
- // Message header with colored dot indicator
- messageHeader: (role: 'user' | 'assistant' | 'tool'): CSSProperties => ({
- fontSize: '10px',
- color: TERMINAL_COLORS.text.secondary,
- marginBottom: '4px',
- textTransform: 'uppercase' as const,
- letterSpacing: '0.05em',
- display: 'flex',
- alignItems: 'center',
- gap: '6px',
- fontFamily: TERMINAL_FONTS.mono
- }),
-
- // Colored dot indicator
- messageIndicator: (role: 'user' | 'assistant' | 'tool' | 'processing'): CSSProperties => ({
- fontSize: '8px',
- color: TERMINAL_COLORS[role],
- width: '6px',
- height: '6px',
- flexShrink: 0
- }),
-
- // Message bubble (borderless for cleaner terminal feel)
- messageBubble: (role: 'user' | 'assistant'): CSSProperties => ({
- background: role === 'user' ? TERMINAL_COLORS.bg.elevated : TERMINAL_COLORS.bg.secondary,
- border: 'none', // Removed borders for cleaner terminal look
- borderRadius: '0px', // Sharp terminal corners
- padding: '12px 16px',
- maxWidth: '80%',
- fontSize: '13px',
- lineHeight: '1.5',
- color: TERMINAL_COLORS.text.primary,
- whiteSpace: 'pre-wrap' as const,
- fontFamily: TERMINAL_FONTS.mono
- }),
-
- // Message timestamp
- messageTimestamp: (): CSSProperties => ({
- fontSize: '9px',
- color: TERMINAL_COLORS.text.tertiary,
- marginTop: '6px',
- fontFamily: TERMINAL_FONTS.mono
- }),
-
- // Terminal input container
- terminalInputContainer: (): CSSProperties => ({
- position: 'relative' as const,
- display: 'flex',
- alignItems: 'center',
- flex: 1
- }),
-
- // Terminal prompt symbol
- terminalPrompt: (): CSSProperties => ({
- position: 'absolute' as const,
- left: '12px',
- color: TERMINAL_COLORS.prompt,
- fontSize: '13px',
- fontFamily: TERMINAL_FONTS.mono,
- zIndex: 1,
- pointerEvents: 'none' as const
- }),
-
- // Terminal input field (subtle border, auto-expanding)
- terminalInput: (): CSSProperties => ({
- width: '100%',
- background: TERMINAL_COLORS.bg.elevated,
- border: `1px solid ${TERMINAL_COLORS.border.secondary}`, // More subtle border
- borderRadius: '4px',
- padding: '8px 12px 8px 24px', // Extra left padding for $ symbol
- fontSize: '13px',
- color: TERMINAL_COLORS.text.primary,
- fontFamily: TERMINAL_FONTS.mono,
- resize: 'none' as const,
- minHeight: '36px',
- maxHeight: '120px', // Increased max height
- outline: 'none',
- transition: 'border-color 0.2s, height 0.1s ease',
- overflow: 'hidden' // For auto-resize
- }),
-
- // Terminal send button
- terminalButton: (disabled: boolean = false): CSSProperties => ({
- padding: '8px 16px',
- background: disabled ? TERMINAL_COLORS.border.primary : TERMINAL_COLORS.accent,
- color: disabled ? TERMINAL_COLORS.text.secondary : '#fff',
- border: `1px solid ${TERMINAL_COLORS.border.primary}`,
- borderRadius: '4px',
- fontSize: '13px',
- cursor: disabled ? 'not-allowed' : 'pointer',
- fontFamily: TERMINAL_FONTS.mono,
- transition: 'all 0.2s',
- display: 'flex',
- alignItems: 'center',
- gap: '4px'
- }),
-
- // Chat messages container
- messagesContainer: (): CSSProperties => ({
- flex: 1,
- overflow: 'auto',
- padding: '16px',
- fontFamily: TERMINAL_FONTS.mono
- }),
-
- // Thinking indicator
- thinkingIndicator: (): CSSProperties => ({
- display: 'flex',
- alignItems: 'center',
- gap: '8px',
- padding: '12px 16px',
- background: TERMINAL_COLORS.bg.secondary,
- borderRadius: '0px',
- margin: '8px 0',
- fontSize: '12px',
- color: TERMINAL_COLORS.text.secondary,
- fontFamily: TERMINAL_FONTS.mono,
- fontStyle: 'italic'
- }),
-
- // Tool use indicator (separate from messages)
- toolIndicator: (): CSSProperties => ({
- display: 'flex',
- alignItems: 'center',
- gap: '8px',
- padding: '8px 12px',
- background: 'rgba(245, 158, 11, 0.1)', // Orange background with transparency
- border: `1px solid ${TERMINAL_COLORS.tool}`,
- borderRadius: '0px',
- margin: '4px 0',
- fontSize: '11px',
- color: TERMINAL_COLORS.tool,
- fontFamily: TERMINAL_FONTS.mono
- }),
-
- // Input form container (remove border)
- inputForm: (): CSSProperties => ({
- borderTop: 'none', // Remove border between chat and input
- padding: '16px',
- background: TERMINAL_COLORS.bg.primary // Same as main background for seamless look
- })
-} as const;
-
-// Terminal Tool Indicators
-export const TERMINAL_TOOLS = {
- processing: '⟡ Processing...',
- generating: '▸ Generating response...',
- thinking: (displayName: string) => `${displayName} is thinking...`,
- toolPrefix: '⟩',
- thinkingDots: '⋯'
-} as const;
-
-// Terminal Animations (for use in CSS)
-export const TERMINAL_ANIMATIONS = {
- pulse: 'pulse 1.5s ease-in-out infinite',
- typing: 'typing 1s ease-in-out infinite'
-} as const;
-
-// Helper function to get role-based styling
-export const getMessageStyles = (role: 'user' | 'assistant', isLoading?: boolean) => ({
- header: TERMINAL_STYLES.messageHeader(role),
- indicator: TERMINAL_STYLES.messageIndicator(isLoading ? 'processing' : role),
- bubble: TERMINAL_STYLES.messageBubble(role),
- timestamp: TERMINAL_STYLES.messageTimestamp()
-});
-
-// Helper function to get input styling with focus states
-export const getTerminalInputStyles = (isFocused: boolean = false) => ({
- container: TERMINAL_STYLES.terminalInputContainer(),
- prompt: TERMINAL_STYLES.terminalPrompt(),
- input: {
- ...TERMINAL_STYLES.terminalInput(),
- borderColor: isFocused ? TERMINAL_COLORS.accent : TERMINAL_COLORS.border.secondary
- }
-});
-
-// Auto-resize textarea helper function
-export const autoResizeTextarea = (textarea: HTMLTextAreaElement) => {
- textarea.style.height = 'auto';
- const newHeight = Math.min(textarea.scrollHeight, 120); // Max 120px
- textarea.style.height = `${Math.max(newHeight, 36)}px`; // Min 36px
-};
-
-// Thinking indicator component props
-export const getThinkingIndicator = (displayName: string, isVisible: boolean) => {
- if (!isVisible) return null;
-
- return {
- style: TERMINAL_STYLES.thinkingIndicator(),
- content: TERMINAL_TOOLS.thinking(displayName),
- dots: TERMINAL_TOOLS.thinkingDots
- };
-};
-
-// Tool use indicator component props
-export const getToolIndicator = (toolName: string, isActive: boolean) => {
- if (!isActive) return null;
-
- return {
- style: TERMINAL_STYLES.toolIndicator(),
- content: `${TERMINAL_TOOLS.toolPrefix} ${toolName}`,
- isActive
- };
-};
\ No newline at end of file
diff --git a/src/tools/database/lockDimension.ts b/src/tools/database/lockDimension.ts
deleted file mode 100644
index e62f67d..0000000
--- a/src/tools/database/lockDimension.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { tool } from 'ai';
-import { z } from 'zod';
-
-export const lockDimensionTool = tool({
- description: 'Lock a dimension to enable auto-assignment to new nodes',
- inputSchema: z.object({
- name: z.string().describe('Dimension name to lock')
- }),
- execute: async (params) => {
- console.log('🔒 LockDimension tool called with params:', JSON.stringify(params, null, 2));
- try {
- const trimmedName = params.name.trim();
- if (!trimmedName) {
- return {
- success: false,
- error: 'Dimension name is required',
- data: null
- };
- }
-
- const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/api/dimensions`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- name: trimmedName,
- isPriority: true
- })
- });
-
- if (!response.ok) {
- let errorMessage = 'Failed to lock dimension';
- try {
- const errorResult = await response.json();
- errorMessage = errorResult.error || errorMessage;
- } catch {
- // If response is not JSON (e.g., HTML error page), use status text
- errorMessage = `Failed to lock dimension: ${response.status} ${response.statusText}`;
- }
- return {
- success: false,
- error: errorMessage,
- data: null
- };
- }
-
- const result = await response.json();
-
- return {
- success: true,
- data: result.data,
- message: `Locked dimension "${trimmedName}" - it will now be auto-assigned to new nodes`
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : 'Failed to lock dimension',
- data: null
- };
- }
- }
-});
-
diff --git a/src/tools/database/unlockDimension.ts b/src/tools/database/unlockDimension.ts
deleted file mode 100644
index 7ef79b0..0000000
--- a/src/tools/database/unlockDimension.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { tool } from 'ai';
-import { z } from 'zod';
-
-export const unlockDimensionTool = tool({
- description: 'Unlock a dimension to disable auto-assignment to new nodes',
- inputSchema: z.object({
- name: z.string().describe('Dimension name to unlock')
- }),
- execute: async (params) => {
- console.log('🔓 UnlockDimension tool called with params:', JSON.stringify(params, null, 2));
- try {
- const trimmedName = params.name.trim();
- if (!trimmedName) {
- return {
- success: false,
- error: 'Dimension name is required',
- data: null
- };
- }
-
- const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/api/dimensions`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- name: trimmedName,
- isPriority: false
- })
- });
-
- if (!response.ok) {
- let errorMessage = 'Failed to unlock dimension';
- try {
- const errorResult = await response.json();
- errorMessage = errorResult.error || errorMessage;
- } catch {
- // If response is not JSON (e.g., HTML error page), use status text
- errorMessage = `Failed to unlock dimension: ${response.status} ${response.statusText}`;
- }
- return {
- success: false,
- error: errorMessage,
- data: null
- };
- }
-
- const result = await response.json();
-
- return {
- success: true,
- data: result.data,
- message: `Unlocked dimension "${trimmedName}" - it will no longer be auto-assigned to new nodes`
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : 'Failed to unlock dimension',
- data: null
- };
- }
- }
-});
-
diff --git a/src/tools/other/embedContent.ts b/src/tools/other/embedContent.ts
deleted file mode 100644
index bd57c90..0000000
--- a/src/tools/other/embedContent.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { tool } from 'ai';
-import { z } from 'zod';
-
-export const embedContentTool = tool({
- description: 'Chunk and embed a node’s content so semantic search stays current',
- inputSchema: z.object({
- nodeId: z.number().describe('The ID of the node to process')
- }),
- execute: async ({ nodeId }) => {
- try {
- // Call the same API endpoint that the manual embed button uses
- const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/api/ingestion`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ nodeId })
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- return {
- success: false,
- error: result.error || 'Failed to embed content',
- data: null
- };
- }
-
- return {
- success: true,
- message: result.message || `Successfully chunked and embedded content for node ${nodeId}`,
- data: { nodeId }
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : 'Failed to embed content',
- data: null
- };
- }
- }
-});
diff --git a/src/types/analytics.ts b/src/types/analytics.ts
index 027f5d8..52fc72d 100644
--- a/src/types/analytics.ts
+++ b/src/types/analytics.ts
@@ -23,92 +23,6 @@ export interface UsageData {
mode?: 'easy' | 'hard';
}
-export interface EnhancedChatMetadata {
- timestamp: string;
- session_id: string;
- current_view: 'nodes' | 'memory';
- open_tab_count: number;
- has_focused_node: boolean;
- message_count: number;
- input_tokens?: number;
- output_tokens?: number;
- total_tokens?: number;
- cache_write_tokens?: number;
- cache_read_tokens?: number;
- cache_hit?: boolean;
- cache_savings_pct?: number;
- estimated_cost_usd?: number;
- model_used?: string;
- provider?: 'anthropic' | 'openai';
- tools_used?: string[];
- tool_calls_count?: number;
- trace_id?: string;
- parent_chat_id?: number;
- voice_tts_chars?: number;
- voice_tts_cost_usd?: number;
- voice_tts_chars_total?: number;
- voice_tts_cost_usd_total?: number;
- voice_request_id?: string;
- voice_tts_request_count?: number;
- voice_usage?: Array<{
- request_id: string;
- message_id?: string | null;
- chars: number;
- cost_usd: number;
- voice?: string;
- model?: string;
- duration_ms?: number | null;
- logged_at?: string;
- }>;
-}
-
-export interface CostReport {
- periodStart: string;
- periodEnd: string;
- totalCostUsd: number;
- totalChats: number;
- totalTokens: number;
- inputTokens: number;
- outputTokens: number;
- cacheReadTokens: number;
- cacheWriteTokens: number;
- cacheHitRate: number;
- cacheSavingsUsd: number;
- avgCostPerChat: number;
- avgTokensPerChat: number;
- costByAgent: {
- [agentName: string]: {
- costUsd: number;
- chats: number;
- tokens: number;
- };
- };
- costByModel: {
- [modelId: string]: {
- costUsd: number;
- chats: number;
- tokens: number;
- };
- };
-}
-
-export interface TraceCostSummary {
- traceId: string;
- totalCostUsd: number;
- chatCount: number;
- orchestratorCost: number;
- executorCost: number;
- plannerCost: number;
- totalTokens: number;
- interactions: Array<{
- chatId: number;
- agentName: string;
- costUsd: number;
- tokens: number;
- createdAt: string;
- }>;
-}
-
export interface ModelPricing {
provider: 'anthropic' | 'openai';
inputPer1M: number;
@@ -117,12 +31,3 @@ export interface ModelPricing {
cacheReadPer1M?: number;
}
-export interface CacheEffectiveness {
- totalRequests: number;
- cacheHits: number;
- cacheMisses: number;
- hitRate: number;
- totalCacheSavingsUsd: number;
- avgSavingsPerHit: number;
- totalTokensSaved: number;
-}
diff --git a/src/types/database.ts b/src/types/database.ts
index b33feba..3b068fe 100644
--- a/src/types/database.ts
+++ b/src/types/database.ts
@@ -21,27 +21,6 @@ export interface Node {
chunk_status?: 'not_chunked' | 'chunking' | 'chunked' | 'error' | null;
}
-// Legacy Item interface - DEPRECATED, use Node instead
-// Kept temporarily for migration compatibility
-export interface Item extends Node {
- // Legacy fields for backwards compatibility during transition
- description?: string;
- abstract?: string;
- type?: string;
- legacyType?: string[];
- stage?: string;
- segment?: string[];
- tags?: string[];
- sub_type?: any;
- notes?: any;
- extras?: any;
- score?: number;
- content_embedding?: number[];
- embedding_updated_at?: string;
- chunk_status?: 'not_chunked' | 'chunking' | 'chunked' | 'error';
- chunk_updated_at?: string;
-}
-
export interface Chunk {
id: number;
node_id: number; // Updated from item_id to node_id
@@ -85,36 +64,6 @@ export interface EdgeContext {
created_via: EdgeCreatedVia;
}
-export interface Chat {
- id: number;
- user_message?: string;
- assistant_message?: string;
- thread_id: string;
- focused_node_id?: number; // Updated from focused_item_id
- metadata?: any;
- embedding?: number[]; // Renamed from content_embedding
- created_at: string;
-}
-
-export interface SessionContext {
- id: number;
- session_id: string;
- focused_node_id: number; // Updated from focused_item_id
- context_data: any;
- created_at: string;
- last_accessed: string;
- expires_at: string;
-}
-
-export interface SessionCache {
- id: number;
- session_id: string;
- cache_key: string;
- cache_data: any;
- expires_at: string;
- created_at: string;
-}
-
// New NodeFilters interface replacing rigid ItemFilters
export interface NodeFilters {
dimensions?: string[]; // Filter by dimensions (replaces stage/type filtering)
@@ -124,13 +73,6 @@ export interface NodeFilters {
sortBy?: 'updated' | 'edges'; // Sort by updated_at or edge count
}
-// Legacy filters - DEPRECATED, use NodeFilters instead
-export interface ItemFilters extends NodeFilters {
- stage?: string;
- type?: string;
- tags?: string[];
-}
-
export interface ChunkData {
node_id: number; // Updated from item_id
chunk_idx?: number;
@@ -158,13 +100,6 @@ export interface ChatData {
embedding?: number[]; // Renamed from content_embedding
}
-export interface CachedContext {
- sessionId: string;
- focusedNodeId: number; // Updated from focusedItemId
- contextData: any;
- expiresAt: string;
-}
-
// New NodeConnection interface
export interface NodeConnection {
id: number;
@@ -172,25 +107,6 @@ export interface NodeConnection {
edge: Edge;
}
-// Legacy connection - DEPRECATED, use NodeConnection instead
-export interface ItemConnection {
- id: number;
- connected_item: Item;
- edge: Edge;
-}
-
-export interface DatabaseConfig {
- host: string;
- port: number;
- database: string;
- user: string;
- password: string;
- ssl?: boolean;
- connectionTimeoutMillis?: number;
- idleTimeoutMillis?: number;
- max?: number;
-}
-
export interface DatabaseError {
message: string;
code?: string;
diff --git a/src/types/helpers.ts b/src/types/helpers.ts
deleted file mode 100644
index 5087cd4..0000000
--- a/src/types/helpers.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export interface MemoryContext {
- insights: {
- user_patterns: string[];
- preferences: string[];
- common_queries: string[];
- knowledge_domains: string[];
- };
- context: string;
-}
-
-export interface HelperConfig {
- name: string;
- display_name: string;
- description: string;
- system_prompt: string;
- component_key: string;
- available_tools: string[];
- enabled: boolean;
- created_at: string;
- updated_at: string;
- memory?: MemoryContext;
-}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 40d698a..09f5f15 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,80 +1,12 @@
import type { Config } from "tailwindcss";
const config = {
- darkMode: ["class"],
content: [
- "./pages/**/*.{ts,tsx}",
- "./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
- prefix: "",
- theme: {
- container: {
- center: true,
- padding: "2rem",
- screens: {
- "2xl": "1400px",
- },
- },
- extend: {
- colors: {
- border: "hsl(var(--border))",
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
- background: "hsl(var(--background))",
- foreground: "hsl(var(--foreground))",
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- destructive: {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- popover: {
- DEFAULT: "hsl(var(--popover))",
- foreground: "hsl(var(--popover-foreground))",
- },
- card: {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
- },
- },
- borderRadius: {
- lg: "var(--radius)",
- md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)",
- },
- keyframes: {
- "accordion-down": {
- from: { height: "0" },
- to: { height: "var(--radix-accordion-content-height)" },
- },
- "accordion-up": {
- from: { height: "var(--radix-accordion-content-height)" },
- to: { height: "0" },
- },
- },
- animation: {
- "accordion-down": "accordion-down 0.2s ease-out",
- "accordion-up": "accordion-up 0.2s ease-out",
- },
- },
- },
- plugins: [require("tailwindcss-animate")],
+ theme: { extend: {} },
+ plugins: [],
} satisfies Config;
-export default config;
\ No newline at end of file
+export default config;