@memextend/webui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export declare const configRouter: import("express-serve-static-core").Router;
2
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/api/config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,YAAY,4CAAW,CAAC"}
@@ -0,0 +1,106 @@
1
+ // apps/webui/src/api/config.ts
2
+ // Copyright (c) 2026 ZodTTD LLC. MIT License.
3
+ import { Router } from 'express';
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ export const configRouter = Router();
8
+ const MEMEXTEND_DIR = join(homedir(), '.memextend');
9
+ const CONFIG_PATH = join(MEMEXTEND_DIR, 'config.json');
10
+ const DEFAULT_CONFIG = {
11
+ capture: {
12
+ captureReasoning: true,
13
+ maxReasoningLength: 10000,
14
+ maxToolOutputLength: 2000,
15
+ tools: {
16
+ Edit: false,
17
+ Write: false,
18
+ Bash: false,
19
+ Task: false
20
+ }
21
+ },
22
+ retrieval: {
23
+ autoInject: true,
24
+ maxMemories: 0,
25
+ recentDays: 0,
26
+ includeGlobal: true,
27
+ deduplicationThreshold: 0.85,
28
+ sessionMaxChars: 10000,
29
+ compactMaxChars: 2000
30
+ },
31
+ storage: {
32
+ maxMemoriesPerProject: 500,
33
+ maxTotalMemories: 5000,
34
+ deduplicateOnPrune: true
35
+ },
36
+ debug: false
37
+ };
38
+ function loadConfig() {
39
+ try {
40
+ if (existsSync(CONFIG_PATH)) {
41
+ const content = readFileSync(CONFIG_PATH, 'utf-8');
42
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
43
+ }
44
+ }
45
+ catch {
46
+ // Return defaults on error
47
+ }
48
+ return { ...DEFAULT_CONFIG };
49
+ }
50
+ function saveConfig(config) {
51
+ // Ensure directory exists
52
+ if (!existsSync(MEMEXTEND_DIR)) {
53
+ mkdirSync(MEMEXTEND_DIR, { recursive: true });
54
+ }
55
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
56
+ }
57
+ // GET /api/config - Get current configuration
58
+ configRouter.get('/', (req, res) => {
59
+ try {
60
+ const config = loadConfig();
61
+ res.json(config);
62
+ }
63
+ catch (error) {
64
+ console.error('Error loading config:', error);
65
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
66
+ }
67
+ });
68
+ // PUT /api/config - Update configuration
69
+ configRouter.put('/', (req, res) => {
70
+ try {
71
+ const updates = req.body;
72
+ // Load current config and merge with updates
73
+ const currentConfig = loadConfig();
74
+ const newConfig = {
75
+ ...currentConfig,
76
+ ...updates,
77
+ capture: {
78
+ ...currentConfig.capture,
79
+ ...updates.capture,
80
+ tools: {
81
+ ...currentConfig.capture?.tools,
82
+ ...updates.capture?.tools
83
+ }
84
+ },
85
+ retrieval: {
86
+ ...currentConfig.retrieval,
87
+ ...updates.retrieval
88
+ },
89
+ storage: {
90
+ ...currentConfig.storage,
91
+ ...updates.storage
92
+ }
93
+ };
94
+ saveConfig(newConfig);
95
+ res.json({ success: true, config: newConfig });
96
+ }
97
+ catch (error) {
98
+ console.error('Error saving config:', error);
99
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
100
+ }
101
+ });
102
+ // GET /api/config/defaults - Get default configuration values
103
+ configRouter.get('/defaults', (req, res) => {
104
+ res.json(DEFAULT_CONFIG);
105
+ });
106
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/api/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const memoriesRouter: import("express-serve-static-core").Router;
2
+ //# sourceMappingURL=memories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../../src/api/memories.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,4CAAW,CAAC"}
@@ -0,0 +1,213 @@
1
+ // apps/webui/src/api/memories.ts
2
+ // Copyright (c) 2026 ZodTTD LLC. MIT License.
3
+ import { Router } from 'express';
4
+ export const memoriesRouter = Router();
5
+ // Helper to get storage instances
6
+ async function getStorage(req) {
7
+ const { SQLiteStorage, LanceDBStorage } = await import('@memextend/core');
8
+ const sqlite = new SQLiteStorage(req.app.locals.dbPath);
9
+ const lancedb = await LanceDBStorage.create(req.app.locals.vectorsPath);
10
+ return { sqlite, lancedb };
11
+ }
12
+ // GET /api/memories - List memories with pagination
13
+ memoriesRouter.get('/', async (req, res) => {
14
+ try {
15
+ const { sqlite, lancedb } = await getStorage(req);
16
+ const limit = parseInt(req.query.limit || '50', 10);
17
+ const offset = parseInt(req.query.offset || '0', 10);
18
+ const projectId = req.query.projectId;
19
+ const type = req.query.type;
20
+ const tool = req.query.tool;
21
+ const startDate = req.query.startDate;
22
+ const endDate = req.query.endDate;
23
+ // Get all memories with project filter
24
+ let memories = sqlite.getAllMemories(projectId, limit + offset);
25
+ // Apply type filter
26
+ if (type) {
27
+ memories = memories.filter(m => m.type === type);
28
+ }
29
+ // Apply tool filter
30
+ if (tool) {
31
+ memories = memories.filter(m => m.sourceTool === tool);
32
+ }
33
+ // Apply date filters
34
+ if (startDate) {
35
+ const start = new Date(startDate);
36
+ memories = memories.filter(m => new Date(m.createdAt) >= start);
37
+ }
38
+ if (endDate) {
39
+ const end = new Date(endDate);
40
+ end.setHours(23, 59, 59, 999);
41
+ memories = memories.filter(m => new Date(m.createdAt) <= end);
42
+ }
43
+ // Apply pagination
44
+ const paginatedMemories = memories.slice(offset, offset + limit);
45
+ const total = sqlite.getMemoryCount();
46
+ sqlite.close();
47
+ await lancedb.close();
48
+ res.json({
49
+ memories: paginatedMemories,
50
+ pagination: {
51
+ limit,
52
+ offset,
53
+ total,
54
+ hasMore: offset + limit < total
55
+ }
56
+ });
57
+ }
58
+ catch (error) {
59
+ console.error('Error fetching memories:', error);
60
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
61
+ }
62
+ });
63
+ // POST /api/memories - Create a new memory
64
+ memoriesRouter.post('/', async (req, res) => {
65
+ try {
66
+ const { content, projectId, type = 'manual' } = req.body;
67
+ if (!content || typeof content !== 'string') {
68
+ res.status(400).json({ error: 'Content is required and must be a string' });
69
+ return;
70
+ }
71
+ const { SQLiteStorage, LanceDBStorage, LocalEmbedding } = await import('@memextend/core');
72
+ const { randomUUID } = await import('crypto');
73
+ const sqlite = new SQLiteStorage(req.app.locals.dbPath);
74
+ const lancedb = await LanceDBStorage.create(req.app.locals.vectorsPath);
75
+ const embedder = await LocalEmbedding.create(req.app.locals.memextendDir);
76
+ const memoryId = randomUUID();
77
+ const memory = {
78
+ id: memoryId,
79
+ projectId: projectId || null,
80
+ content,
81
+ type: 'manual', // Always use 'manual' type for user-created memories
82
+ sourceTool: null,
83
+ createdAt: new Date().toISOString(),
84
+ sessionId: null,
85
+ metadata: null
86
+ };
87
+ // Save to SQLite
88
+ sqlite.insertMemory(memory);
89
+ // Generate and save embedding
90
+ const embedding = await embedder.embed(content);
91
+ await lancedb.insertVector(memoryId, embedding);
92
+ sqlite.close();
93
+ await lancedb.close();
94
+ res.status(201).json({ success: true, id: memoryId, memory });
95
+ }
96
+ catch (error) {
97
+ console.error('Error creating memory:', error);
98
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
99
+ }
100
+ });
101
+ // GET /api/memories/:id - Get single memory
102
+ memoriesRouter.get('/:id', async (req, res) => {
103
+ try {
104
+ const { sqlite, lancedb } = await getStorage(req);
105
+ const memory = sqlite.getMemory(req.params.id);
106
+ sqlite.close();
107
+ await lancedb.close();
108
+ if (!memory) {
109
+ res.status(404).json({ error: 'Memory not found' });
110
+ return;
111
+ }
112
+ res.json(memory);
113
+ }
114
+ catch (error) {
115
+ console.error('Error fetching memory:', error);
116
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
117
+ }
118
+ });
119
+ // PUT /api/memories/:id - Update memory content
120
+ memoriesRouter.put('/:id', async (req, res) => {
121
+ try {
122
+ const { content } = req.body;
123
+ if (!content || typeof content !== 'string') {
124
+ res.status(400).json({ error: 'Content is required and must be a string' });
125
+ return;
126
+ }
127
+ const { sqlite, lancedb } = await getStorage(req);
128
+ // Check if memory exists
129
+ const existing = sqlite.getMemory(req.params.id);
130
+ if (!existing) {
131
+ sqlite.close();
132
+ await lancedb.close();
133
+ res.status(404).json({ error: 'Memory not found' });
134
+ return;
135
+ }
136
+ const updated = sqlite.updateMemory(req.params.id, content);
137
+ sqlite.close();
138
+ await lancedb.close();
139
+ if (updated) {
140
+ res.json({ success: true, id: req.params.id });
141
+ }
142
+ else {
143
+ res.status(500).json({ error: 'Failed to update memory' });
144
+ }
145
+ }
146
+ catch (error) {
147
+ console.error('Error updating memory:', error);
148
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
149
+ }
150
+ });
151
+ // DELETE /api/memories/:id - Delete single memory
152
+ memoriesRouter.delete('/:id', async (req, res) => {
153
+ try {
154
+ const { sqlite, lancedb } = await getStorage(req);
155
+ // Check if memory exists
156
+ const existing = sqlite.getMemory(req.params.id);
157
+ if (!existing) {
158
+ sqlite.close();
159
+ await lancedb.close();
160
+ res.status(404).json({ error: 'Memory not found' });
161
+ return;
162
+ }
163
+ const deleted = sqlite.deleteMemory(req.params.id);
164
+ if (deleted) {
165
+ // Also delete the vector embedding
166
+ await lancedb.deleteVector(req.params.id);
167
+ }
168
+ sqlite.close();
169
+ await lancedb.close();
170
+ if (deleted) {
171
+ res.json({ success: true, id: req.params.id });
172
+ }
173
+ else {
174
+ res.status(500).json({ error: 'Failed to delete memory' });
175
+ }
176
+ }
177
+ catch (error) {
178
+ console.error('Error deleting memory:', error);
179
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
180
+ }
181
+ });
182
+ // DELETE /api/memories - Bulk delete with filters
183
+ memoriesRouter.delete('/', async (req, res) => {
184
+ try {
185
+ const projectId = req.query.projectId;
186
+ const before = req.query.before;
187
+ const { sqlite, lancedb } = await getStorage(req);
188
+ let deleted = 0;
189
+ if (before) {
190
+ const date = new Date(before);
191
+ if (isNaN(date.getTime())) {
192
+ sqlite.close();
193
+ await lancedb.close();
194
+ res.status(400).json({ error: 'Invalid date format' });
195
+ return;
196
+ }
197
+ deleted = sqlite.deleteMemoriesBefore(date, projectId);
198
+ }
199
+ else {
200
+ deleted = sqlite.deleteAllMemories(projectId);
201
+ }
202
+ sqlite.close();
203
+ await lancedb.close();
204
+ // Note: Bulk delete doesn't delete vectors individually
205
+ // Orphaned vectors are harmless but take up space
206
+ res.json({ success: true, deleted });
207
+ }
208
+ catch (error) {
209
+ console.error('Error bulk deleting memories:', error);
210
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
211
+ }
212
+ });
213
+ //# sourceMappingURL=memories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memories.js","sourceRoot":"","sources":["../../src/api/memories.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const projectsRouter: import("express-serve-static-core").Router;
2
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/api/projects.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,cAAc,4CAAW,CAAC"}
@@ -0,0 +1,136 @@
1
+ // apps/webui/src/api/projects.ts
2
+ // Copyright (c) 2026 ZodTTD LLC. MIT License.
3
+ import { Router } from 'express';
4
+ export const projectsRouter = Router();
5
+ // Helper to get SQLiteStorage instance
6
+ async function getStorage(req) {
7
+ const { SQLiteStorage } = await import('@memextend/core');
8
+ return new SQLiteStorage(req.app.locals.dbPath);
9
+ }
10
+ // GET /api/projects - List all projects
11
+ projectsRouter.get('/', async (req, res) => {
12
+ try {
13
+ const sqlite = await getStorage(req);
14
+ // Get all memories to extract unique projects
15
+ const memories = sqlite.getAllMemories(undefined, 10000);
16
+ // Get unique project IDs
17
+ const projectIds = new Set();
18
+ const projectMemoryCounts = {};
19
+ for (const memory of memories) {
20
+ if (memory.projectId) {
21
+ projectIds.add(memory.projectId);
22
+ projectMemoryCounts[memory.projectId] = (projectMemoryCounts[memory.projectId] || 0) + 1;
23
+ }
24
+ }
25
+ // Get project details for each unique project ID
26
+ const projects = [];
27
+ for (const id of projectIds) {
28
+ const project = sqlite.getProject(id);
29
+ projects.push({
30
+ id,
31
+ name: project?.name || 'Unknown Project',
32
+ path: project?.path || 'Unknown Path',
33
+ createdAt: project?.createdAt || null,
34
+ memoryCount: projectMemoryCounts[id] || 0
35
+ });
36
+ }
37
+ // Sort by memory count descending
38
+ projects.sort((a, b) => b.memoryCount - a.memoryCount);
39
+ sqlite.close();
40
+ res.json({ projects });
41
+ }
42
+ catch (error) {
43
+ console.error('Error fetching projects:', error);
44
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
45
+ }
46
+ });
47
+ // GET /api/projects/:id - Get single project with its memories
48
+ projectsRouter.get('/:id', async (req, res) => {
49
+ try {
50
+ const sqlite = await getStorage(req);
51
+ const project = sqlite.getProject(req.params.id);
52
+ const memories = sqlite.getAllMemories(req.params.id, 100);
53
+ sqlite.close();
54
+ if (!project && memories.length === 0) {
55
+ res.status(404).json({ error: 'Project not found' });
56
+ return;
57
+ }
58
+ res.json({
59
+ id: req.params.id,
60
+ name: project?.name || 'Unknown Project',
61
+ path: project?.path || 'Unknown Path',
62
+ createdAt: project?.createdAt || null,
63
+ memoryCount: memories.length,
64
+ recentMemories: memories.slice(0, 10)
65
+ });
66
+ }
67
+ catch (error) {
68
+ console.error('Error fetching project:', error);
69
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
70
+ }
71
+ });
72
+ // GET /api/projects/:id/memories - Get project memories with pagination
73
+ projectsRouter.get('/:id/memories', async (req, res) => {
74
+ try {
75
+ const sqlite = await getStorage(req);
76
+ const limit = parseInt(req.query.limit || '50', 10);
77
+ const offset = parseInt(req.query.offset || '0', 10);
78
+ const memories = sqlite.getAllMemories(req.params.id, limit + offset);
79
+ const paginatedMemories = memories.slice(offset, offset + limit);
80
+ sqlite.close();
81
+ res.json({
82
+ memories: paginatedMemories,
83
+ pagination: {
84
+ limit,
85
+ offset,
86
+ total: memories.length,
87
+ hasMore: offset + limit < memories.length
88
+ }
89
+ });
90
+ }
91
+ catch (error) {
92
+ console.error('Error fetching project memories:', error);
93
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
94
+ }
95
+ });
96
+ // DELETE /api/projects/:id - Delete a project and all its memories
97
+ projectsRouter.delete('/:id', async (req, res) => {
98
+ try {
99
+ const { SQLiteStorage, LanceDBStorage } = await import('@memextend/core');
100
+ const sqlite = new SQLiteStorage(req.app.locals.dbPath);
101
+ const lancedb = await LanceDBStorage.create(req.app.locals.vectorsPath);
102
+ // Get all memory IDs for this project before deleting (for vector cleanup)
103
+ const memories = sqlite.getAllMemories(req.params.id, 100000);
104
+ const memoryIds = memories.map(m => m.id);
105
+ // Delete the project and its memories from SQLite
106
+ const result = sqlite.deleteProject(req.params.id);
107
+ // Delete vectors for all those memories
108
+ let vectorsDeleted = 0;
109
+ for (const memoryId of memoryIds) {
110
+ try {
111
+ await lancedb.deleteVector(memoryId);
112
+ vectorsDeleted++;
113
+ }
114
+ catch {
115
+ // Ignore vector deletion errors
116
+ }
117
+ }
118
+ sqlite.close();
119
+ await lancedb.close();
120
+ if (!result.projectDeleted && result.memoriesDeleted === 0) {
121
+ res.status(404).json({ error: 'Project not found' });
122
+ return;
123
+ }
124
+ res.json({
125
+ success: true,
126
+ projectDeleted: result.projectDeleted,
127
+ memoriesDeleted: result.memoriesDeleted,
128
+ vectorsDeleted
129
+ });
130
+ }
131
+ catch (error) {
132
+ console.error('Error deleting project:', error);
133
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
134
+ }
135
+ });
136
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/api/projects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const searchRouter: import("express-serve-static-core").Router;
2
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/api/search.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,YAAY,4CAAW,CAAC"}
@@ -0,0 +1,97 @@
1
+ // apps/webui/src/api/search.ts
2
+ // Copyright (c) 2026 ZodTTD LLC. MIT License.
3
+ import { Router } from 'express';
4
+ export const searchRouter = Router();
5
+ // GET /api/search - Search memories
6
+ searchRouter.get('/', async (req, res) => {
7
+ try {
8
+ const query = req.query.q;
9
+ const scope = req.query.scope || 'all'; // 'all', 'global', 'project'
10
+ const projectId = req.query.projectId;
11
+ const limit = parseInt(req.query.limit || '20', 10);
12
+ if (!query) {
13
+ res.status(400).json({ error: 'Query parameter "q" is required' });
14
+ return;
15
+ }
16
+ const { SQLiteStorage, LanceDBStorage, MemoryRetriever, createEmbedFunction } = await import('@memextend/core');
17
+ const sqlite = new SQLiteStorage(req.app.locals.dbPath);
18
+ const lancedb = await LanceDBStorage.create(req.app.locals.vectorsPath);
19
+ // Create embedding function
20
+ const embedder = await createEmbedFunction(req.app.locals.modelsPath);
21
+ const retriever = new MemoryRetriever(sqlite, lancedb, embedder.embedQuery);
22
+ let results;
23
+ if (scope === 'global') {
24
+ // Search global profiles
25
+ const profiles = sqlite.getGlobalProfiles(limit);
26
+ const filtered = profiles.filter(p => p.content.toLowerCase().includes(query.toLowerCase()) ||
27
+ p.key.toLowerCase().includes(query.toLowerCase()));
28
+ sqlite.close();
29
+ await lancedb.close();
30
+ await embedder.close();
31
+ res.json({
32
+ results: filtered.map((p, i) => ({
33
+ type: 'global_profile',
34
+ item: p,
35
+ score: 1 - (i * 0.1) // Approximate score
36
+ })),
37
+ query,
38
+ scope,
39
+ total: filtered.length,
40
+ usingRealEmbeddings: embedder.isReal
41
+ });
42
+ return;
43
+ }
44
+ // Hybrid search for project or all memories
45
+ const searchProjectId = scope === 'project' ? projectId : undefined;
46
+ results = await retriever.hybridSearch(query, { limit, projectId: searchProjectId });
47
+ sqlite.close();
48
+ await lancedb.close();
49
+ await embedder.close();
50
+ res.json({
51
+ results: results.map(r => ({
52
+ type: 'memory',
53
+ item: r.memory,
54
+ score: r.score,
55
+ source: r.source
56
+ })),
57
+ query,
58
+ scope,
59
+ projectId: searchProjectId,
60
+ total: results.length,
61
+ usingRealEmbeddings: embedder.isReal
62
+ });
63
+ }
64
+ catch (error) {
65
+ console.error('Error searching memories:', error);
66
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
67
+ }
68
+ });
69
+ // POST /api/search/fts - Full-text search only (faster, no embeddings)
70
+ searchRouter.post('/fts', async (req, res) => {
71
+ try {
72
+ const { query, limit = 20 } = req.body;
73
+ if (!query) {
74
+ res.status(400).json({ error: 'Query is required' });
75
+ return;
76
+ }
77
+ const { SQLiteStorage } = await import('@memextend/core');
78
+ const sqlite = new SQLiteStorage(req.app.locals.dbPath);
79
+ const results = sqlite.searchFTS(query, limit);
80
+ sqlite.close();
81
+ res.json({
82
+ results: results.map(r => ({
83
+ type: 'memory',
84
+ item: r.memory,
85
+ score: r.score,
86
+ source: 'fts'
87
+ })),
88
+ query,
89
+ total: results.length
90
+ });
91
+ }
92
+ catch (error) {
93
+ console.error('Error in FTS search:', error);
94
+ res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
95
+ }
96
+ });
97
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/api/search.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,8CAA8C;AAE9C,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC;AAErC,oCAAoC;AACpC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAC1D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAW,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,KAAe,IAAI,KAAK,CAAC,CAAC,6BAA6B;QAC/E,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,SAA+B,CAAC;QAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAe,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAE9D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;YACnE,OAAO;QACT,CAAC;QAED,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAEhH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAExE,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAE5E,IAAI,OAAO,CAAC;QAEZ,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvB,yBAAyB;YACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;gBACrD,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAClD,CAAC;YAEF,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;YAEvB,GAAG,CAAC,IAAI,CAAC;gBACP,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC/B,IAAI,EAAE,gBAAgB;oBACtB,IAAI,EAAE,CAAC;oBACP,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,oBAAoB;iBAC1C,CAAC,CAAC;gBACH,KAAK;gBACL,KAAK;gBACL,KAAK,EAAE,QAAQ,CAAC,MAAM;gBACtB,mBAAmB,EAAE,QAAQ,CAAC,MAAM;aACrC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,4CAA4C;QAC5C,MAAM,eAAe,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,OAAO,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC;QAErF,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QAEvB,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,CAAC,MAAM;gBACd,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,MAAM,EAAE,CAAC,CAAC,MAAM;aACjB,CAAC,CAAC;YACH,KAAK;YACL,KAAK;YACL,SAAS,EAAE,eAAe;YAC1B,KAAK,EAAE,OAAO,CAAC,MAAM;YACrB,mBAAmB,EAAE,QAAQ,CAAC,MAAM;SACrC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;QAClD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,uEAAuE;AACvE,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAC9D,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QAEvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACrD,OAAO;QACT,CAAC;QAED,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAExD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAE/C,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,CAAC,MAAM;gBACd,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;YACH,KAAK;YACL,KAAK,EAAE,OAAO,CAAC,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;QAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const statsRouter: import("express-serve-static-core").Router;
2
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/api/stats.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,4CAAW,CAAC"}