Compare commits
2 Commits
c86b565eed
...
f5011fddca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5011fddca | ||
|
|
86e78cc4c6 |
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(grep -E \"^d|^-.*\\\\.\\(json|md\\)$\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,5 @@ data/
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
*.db
|
*.db
|
||||||
|
/.claude
|
||||||
EOF
|
EOF
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { API_DEFAULTS } from "../config/constants";
|
import { API_DEFAULTS } from "../config/constants";
|
||||||
|
import { logger } from "@nexusai/shared";
|
||||||
|
|
||||||
const BASE_URL = import.meta.env.VITE_ORCHESTRATION_URL ?? '';
|
const BASE_URL = import.meta.env.VITE_ORCHESTRATION_URL ?? '';
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ export function streamMessage(sessionId, message, model, { onChunk, onDone, onEr
|
|||||||
if (data.done) onDone({ model: data.model ?? model, tokenCount: data.tokenCount ?? 0 });
|
if (data.done) onDone({ model: data.model ?? model, tokenCount: data.tokenCount ?? 0 });
|
||||||
if (data.error) onError(new Error(data.error));
|
if (data.error) onError(new Error(data.error));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[chat-client] Failed to parse SSE event:', raw, err);
|
logger.error('[chat-client] Failed to parse SSE event:', raw, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { fetchSessions, deleteSession } from '../api/orchestration';
|
import { fetchSessions, deleteSession } from '../api/orchestration';
|
||||||
import { CLIENT_DEFAULTS } from '../config/constants';
|
import { CLIENT_DEFAULTS } from '../config/constants';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
const PAGE_SIZE = CLIENT_DEFAULTS.PAGE_SIZE;
|
const PAGE_SIZE = CLIENT_DEFAULTS.PAGE_SIZE;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export default function AllChatsView({ onSelectSession, onBack, projects }) {
|
|||||||
setSessions(data);
|
setSessions(data);
|
||||||
setTotal(data.length === PAGE_SIZE ? (p + 2) * PAGE_SIZE : p * PAGE_SIZE + data.length);
|
setTotal(data.length === PAGE_SIZE ? (p + 2) * PAGE_SIZE : p * PAGE_SIZE + data.length);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AllChatsView] Failed to load sessions:', err.message);
|
logger.error('[AllChatsView] Failed to load sessions:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,7 @@ export default function AllChatsView({ onSelectSession, onBack, projects }) {
|
|||||||
setConfirmOpen(false);
|
setConfirmOpen(false);
|
||||||
await loadPage(page);
|
await loadPage(page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AllChatsView] Bulk delete failed:', err.message);
|
logger.error('[AllChatsView] Bulk delete failed:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setDeleting(false);
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import ProjectModal from './ProjectModal';
|
import ProjectModal from './ProjectModal';
|
||||||
import { fetchProjects, createProject, updateProject, deleteProject } from '../api/orchestration';
|
import { fetchProjects, createProject, updateProject, deleteProject } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export default function AllProjectsView({ onProjectsChange, onBack, onSelectProject, onNavigate }) {
|
export default function AllProjectsView({ onProjectsChange, onBack, onSelectProject, onNavigate }) {
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
@@ -14,7 +15,7 @@ export default function AllProjectsView({ onProjectsChange, onBack, onSelectProj
|
|||||||
try {
|
try {
|
||||||
setProjects(await fetchProjects());
|
setProjects(await fetchProjects());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AllProjectsView] Failed to load:', err.message);
|
logger.error('[AllProjectsView] Failed to load:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,7 @@ async function handleSave({ name, description, colour, icon }) {
|
|||||||
await load();
|
await load();
|
||||||
onProjectsChange?.(); // add this
|
onProjectsChange?.(); // add this
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AllProjectsView] Save failed:', err.message);
|
logger.error('[AllProjectsView] Save failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ async function handleDelete(id) {
|
|||||||
await load();
|
await load();
|
||||||
onProjectsChange?.(); // add this
|
onProjectsChange?.(); // add this
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AllProjectsView] Delete failed:', err.message);
|
logger.error('[AllProjectsView] Delete failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { fetchSessions, updateProject, deleteProject, generateProjectSummary, fetchProjectOverviewSummary } from '../api/orchestration';
|
import { fetchSessions, updateProject, deleteProject, generateProjectSummary, fetchProjectOverviewSummary } from '../api/orchestration';
|
||||||
import ProjectModal from './ProjectModal';
|
import ProjectModal from './ProjectModal';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export default function ProjectView({ project, onNavigate, onBack, onSelectSession, onNewProjectChat, onProjectsChange }) {
|
export default function ProjectView({ project, onNavigate, onBack, onSelectSession, onNewProjectChat, onProjectsChange }) {
|
||||||
const [sessions, setSessions] = useState([]);
|
const [sessions, setSessions] = useState([]);
|
||||||
@@ -21,7 +22,7 @@ export default function ProjectView({ project, onNavigate, onBack, onSelectSessi
|
|||||||
try {
|
try {
|
||||||
setOverview(await fetchProjectOverviewSummary(project.id));
|
setOverview(await fetchProjectOverviewSummary(project.id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ProjectView] Failed to load overview:', err.message);
|
logger.error('[ProjectView] Failed to load overview:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setOverviewLoading(false);
|
setOverviewLoading(false);
|
||||||
}
|
}
|
||||||
@@ -34,7 +35,7 @@ export default function ProjectView({ project, onNavigate, onBack, onSelectSessi
|
|||||||
try {
|
try {
|
||||||
setSessions(await fetchSessions(50, 0, project.id));
|
setSessions(await fetchSessions(50, 0, project.id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ProjectView] Failed to load sessions:', err.message);
|
logger.error('[ProjectView] Failed to load sessions:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -60,7 +61,7 @@ export default function ProjectView({ project, onNavigate, onBack, onSelectSessi
|
|||||||
onProjectsChange?.();
|
onProjectsChange?.();
|
||||||
setModal(null);
|
setModal(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ProjectView] Update failed:', err.message);
|
logger.error('[ProjectView] Update failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ export default function ProjectView({ project, onNavigate, onBack, onSelectSessi
|
|||||||
onProjectsChange?.();
|
onProjectsChange?.();
|
||||||
onBack();
|
onBack();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ProjectView] Delete failed:', err.message);
|
logger.error('[ProjectView] Delete failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +376,7 @@ function NotesSection({ projectId, initialNotes }) {
|
|||||||
await updateProject(projectId, { notes });
|
await updateProject(projectId, { notes });
|
||||||
setSavedNotes(notes);
|
setSavedNotes(notes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[NotesSection] Save failed:', err.message);
|
logger.error('[NotesSection] Save failed:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../hooks/useSettings';
|
||||||
import { useModels } from '../hooks/useModels';
|
import { useModels } from '../hooks/useModels';
|
||||||
import { getServiceHealth } from '../api/orchestration';
|
import { getServiceHealth } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export default function SettingsView({ onNavigate, onBack, modelProps }) {
|
export default function SettingsView({ onNavigate, onBack, modelProps }) {
|
||||||
const { settings, saveSetting, saving } = useSettings();
|
const { settings, saveSetting, saving } = useSettings();
|
||||||
@@ -276,7 +277,7 @@ function ServiceHealth() {
|
|||||||
setServices(await getServiceHealth());
|
setServices(await getServiceHealth());
|
||||||
setLastChecked(new Date());
|
setLastChecked(new Date());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[ServiceHealth]', err.message);
|
logger.error('[ServiceHealth]', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import SessionModal from './SessionModal';
|
import SessionModal from './SessionModal';
|
||||||
import { useContextMenu } from '../hooks/useContextMenu';
|
import { useContextMenu } from '../hooks/useContextMenu';
|
||||||
import { renameSession, deleteSession, updateSession } from '../api/orchestration';
|
import { renameSession, deleteSession, updateSession } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
@@ -32,7 +33,7 @@ export default function Sidebar({
|
|||||||
await updateSession(session.external_id, { name, projectId });
|
await updateSession(session.external_id, { name, projectId });
|
||||||
onSessionsChange();
|
onSessionsChange();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Sidebar] Rename failed:', err.message);
|
logger.error('[Sidebar] Rename failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export default function Sidebar({
|
|||||||
await deleteSession(session.external_id);
|
await deleteSession(session.external_id);
|
||||||
onSessionsChange(session);
|
onSessionsChange(session);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Sidebar] Delete failed:', err.message);
|
logger.error('[Sidebar] Delete failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
import { streamMessage, updateSession } from '../api/orchestration';
|
import { streamMessage, updateSession } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export function useChat({ activeSession, appendMessage, updateLastMessage, refreshSessions }) {
|
export function useChat({ activeSession, appendMessage, updateLastMessage, refreshSessions }) {
|
||||||
const [streaming, setStreaming] = useState(false);
|
const [streaming, setStreaming] = useState(false);
|
||||||
@@ -73,7 +74,7 @@ export function useChat({ activeSession, appendMessage, updateLastMessage, refre
|
|||||||
// Assign project after first message if one was set
|
// Assign project after first message if one was set
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
updateSession(targetSession.external_id, { projectId })
|
updateSession(targetSession.external_id, { projectId })
|
||||||
.catch(err => console.warn('[useChat] Failed to assign project:', err.message));
|
.catch(err => logger.warn('[useChat] Failed to assign project:', err.message));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { fetchModels } from '../api/orchestration';
|
import { fetchModels } from '../api/orchestration';
|
||||||
import { FALLBACK_MODELS, DEFAULT_MODEL } from '../config/constants';
|
import { FALLBACK_MODELS, DEFAULT_MODEL } from '../config/constants';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export function useModels() {
|
export function useModels() {
|
||||||
const [models, setModels] = useState(FALLBACK_MODELS);
|
const [models, setModels] = useState(FALLBACK_MODELS);
|
||||||
@@ -15,7 +16,7 @@ export function useModels() {
|
|||||||
setSelectedModel(data[0]?.value ?? DEFAULT_MODEL);
|
setSelectedModel(data[0]?.value ?? DEFAULT_MODEL);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.warn('[useModels] Falling back to static list:', err.message);
|
logger.warn('[useModels] Falling back to static list:', err.message);
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { fetchProjects } from '../api/orchestration';
|
import { fetchProjects } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export function useProjects() {
|
export function useProjects() {
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
@@ -8,7 +9,7 @@ export function useProjects() {
|
|||||||
try {
|
try {
|
||||||
setProjects(await fetchProjects());
|
setProjects(await fetchProjects());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[useProjects] Failed to load projects:', err.message);
|
logger.warn('[useProjects] Failed to load projects:', err.message);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { getSettings, updateSettings } from '../api/orchestration';
|
import { getSettings, updateSettings } from '../api/orchestration';
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
const [settings, setSettings] = useState(null);
|
const [settings, setSettings] = useState(null);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSettings().then(setSettings).catch(console.error);
|
getSettings().then(setSettings).catch(logger.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function saveSetting(key, value) {
|
async function saveSetting(key, value) {
|
||||||
@@ -15,7 +16,7 @@ export function useSettings() {
|
|||||||
const updated = await updateSettings({ [key]: value });
|
const updated = await updateSettings({ [key]: value });
|
||||||
setSettings(updated);
|
setSettings(updated);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[useSettings] Save failed:', err.message);
|
logger.error('[useSettings] Save failed:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require ('dotenv').config();
|
require ('dotenv').config();
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const {getEnv, PORTS, OLLAMA} = require('@nexusai/shared');
|
const {getEnv, PORTS, OLLAMA, logger} = require('@nexusai/shared');
|
||||||
const inferenceRouter = require('./routes/inference');
|
const inferenceRouter = require('./routes/inference');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -24,5 +24,5 @@ app.use('/', inferenceRouter);
|
|||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Inference Service is running on port ${PORT}`);
|
logger.info(`Inference Service is running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const { getEnv, LLAMACPP, INFERENCE_DEFAULTS } = require("@nexusai/shared");
|
const { getEnv, LLAMACPP, INFERENCE_DEFAULTS, logger } = require("@nexusai/shared");
|
||||||
|
|
||||||
const BASE_URL = getEnv("INFERENCE_URL", LLAMACPP.DEFAULT_URL);
|
const BASE_URL = getEnv("INFERENCE_URL", LLAMACPP.DEFAULT_URL);
|
||||||
const DEFAULT_MODEL = getEnv("DEFAULT_MODEL", LLAMACPP.DEFAULT_MODEL);
|
const DEFAULT_MODEL = getEnv("DEFAULT_MODEL", LLAMACPP.DEFAULT_MODEL);
|
||||||
@@ -89,7 +89,7 @@ async function* completeStream(prompt, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[llamacpp] finalTokenCount:', finalTokenCount);
|
logger.info('[llamacpp] finalTokenCount:', finalTokenCount);
|
||||||
|
|
||||||
yield { response: '', done: true, model: finalModel, tokenCount: finalTokenCount };
|
yield { response: '', done: true, model: finalModel, tokenCount: finalTokenCount };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { Router } = require('express');
|
const { Router } = require('express');
|
||||||
const { complete, completeStream } = require('../infer');
|
const { complete, completeStream } = require('../infer');
|
||||||
|
const { logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ router.post('/complete', async (req, res) => {
|
|||||||
const result = await complete (prompt, {model, temperature, maxTokens, topP, topK, repeatPenalty});
|
const result = await complete (prompt, {model, temperature, maxTokens, topP, topK, repeatPenalty});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Inference] Completion error:', error.message);
|
logger.error('[Inference] Completion error:', error.message);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -42,7 +43,7 @@ router.post('/complete/stream', async (req, res) => {
|
|||||||
// capture final metadata from the done signal
|
// capture final metadata from the done signal
|
||||||
lastModel = chunk.model ?? lastModel;
|
lastModel = chunk.model ?? lastModel;
|
||||||
tokenCount = chunk.tokenCount ?? tokenCount;
|
tokenCount = chunk.tokenCount ?? tokenCount;
|
||||||
console.log('[inference router] tokenCount from chunk:', chunk.tokenCount, '→', tokenCount);
|
logger.info('[inference router] tokenCount from chunk:', chunk.tokenCount, '→', tokenCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ router.post('/complete/stream', async (req, res) => {
|
|||||||
res.write('data: [DONE]\n\n');
|
res.write('data: [DONE]\n\n');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Inference] Streaming error:', err.message);
|
logger.error('[Inference] Streaming error:', err.message);
|
||||||
res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
|
res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
|
||||||
} finally {
|
} finally {
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Database = require('better-sqlite3');
|
const Database = require('better-sqlite3');
|
||||||
const schema = require('./schema');
|
const schema = require('./schema');
|
||||||
const {getEnv, SQLITE } = require('@nexusai/shared');
|
const {getEnv, SQLITE, logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
let db; // Declare db variable in a scope accessible to all functions
|
let db; // Declare db variable in a scope accessible to all functions
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ function getDB() {
|
|||||||
db.exec(`INSERT OR REPLACE INTO episodes_fts(rowid, user_message, ai_response)
|
db.exec(`INSERT OR REPLACE INTO episodes_fts(rowid, user_message, ai_response)
|
||||||
SELECT id, user_message, ai_response FROM episodes`);
|
SELECT id, user_message, ai_response FROM episodes`);
|
||||||
|
|
||||||
console.log(`Connected to SQLite database at ${path}`);
|
logger.info(`Connected to SQLite database at ${path}`);
|
||||||
}
|
}
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const semantic = require('../semantic')
|
const semantic = require('../semantic')
|
||||||
const { getEnv, SERVICES, formatEpisodeText, ENTITIES } = require('@nexusai/shared');
|
const { getEnv, SERVICES, formatEpisodeText, ENTITIES, logger } = require('@nexusai/shared');
|
||||||
const { upsertEntity } = require('./index');
|
const { upsertEntity } = require('./index');
|
||||||
|
|
||||||
const EXTRACTION_URL = getEnv('EXTRACTION_URL', 'http://localhost:11434');
|
const EXTRACTION_URL = getEnv('EXTRACTION_URL', 'http://localhost:11434');
|
||||||
@@ -59,7 +59,7 @@ async function embedEntity(entity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function extractAndStoreEntities(userMessage, aiResponse, projectId=null) {
|
async function extractAndStoreEntities(userMessage, aiResponse, projectId=null) {
|
||||||
console.log('[entities] Extraction triggered')
|
logger.info('[entities] Extraction triggered')
|
||||||
try {
|
try {
|
||||||
// Fetch existing entities to guide the model toward consistent name/type pairs
|
// Fetch existing entities to guide the model toward consistent name/type pairs
|
||||||
const db = require('../db').getDB();
|
const db = require('../db').getDB();
|
||||||
@@ -90,7 +90,7 @@ async function extractAndStoreEntities(userMessage, aiResponse, projectId=null)
|
|||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
const entities = Array.isArray(parsed.entities) ? parsed.entities : [];
|
const entities = Array.isArray(parsed.entities) ? parsed.entities : [];
|
||||||
if (entities.length === 0) {
|
if (entities.length === 0) {
|
||||||
console.log('[entities] No entities found in this exchange — skipping');
|
logger.debug('[entities] No entities found in this exchange — skipping');
|
||||||
return; // not an error, just nothing to extract
|
return; // not an error, just nothing to extract
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ async function extractAndStoreEntities(userMessage, aiResponse, projectId=null)
|
|||||||
if (IGNORED_NAMES.includes(name.toLowerCase())) continue;
|
if (IGNORED_NAMES.includes(name.toLowerCase())) continue;
|
||||||
|
|
||||||
const entity = upsertEntity(name, type, notes ?? null);
|
const entity = upsertEntity(name, type, notes ?? null);
|
||||||
console.log('[entities] Upserted entity:', entity);
|
logger.info('[entities] Upserted entity:', entity);
|
||||||
|
|
||||||
// Embed and upsert to Qdrant fire-and-forget
|
// Embed and upsert to Qdrant fire-and-forget
|
||||||
embedEntity(entity)
|
embedEntity(entity)
|
||||||
@@ -116,17 +116,17 @@ async function extractAndStoreEntities(userMessage, aiResponse, projectId=null)
|
|||||||
projectId: projectId ?? null,
|
projectId: projectId ?? null,
|
||||||
}))
|
}))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.warn(`[entities] Failed to embed entity "${entity.name}":`, err.message);
|
logger.warn(`[entities] Failed to embed entity "${entity.name}":`, err.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
saved++;
|
saved++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saved > 0) console.log(`[entities] Extracted and stored ${saved} entities`);
|
if (saved > 0) logger.info(`[entities] Extracted and stored ${saved} entities`);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Non-critical — log and move on, episode is already saved
|
// Non-critical — log and move on, episode is already saved
|
||||||
console.warn('[entities] Extraction failed:', err.message);
|
logger.warn('[entities] Extraction failed:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const {getDB} = require('../db');
|
const {getDB} = require('../db');
|
||||||
const { EPISODIC, getEnv, SERVICES, parseRow, formatEpisodeText, SUMMARIES } = require('@nexusai/shared');
|
const { EPISODIC, getEnv, SERVICES, parseRow, formatEpisodeText, SUMMARIES, logger } = require('@nexusai/shared');
|
||||||
const semantic = require('../semantic');
|
const semantic = require('../semantic');
|
||||||
const { extractAndStoreEntities } = require('../entities/extraction')
|
const { extractAndStoreEntities } = require('../entities/extraction')
|
||||||
|
|
||||||
@@ -125,10 +125,10 @@ async function createEpisode(sessionId, userMessage, aiResponse, tokenCount = nu
|
|||||||
sessionId: episode.session_id,
|
sessionId: episode.session_id,
|
||||||
createdAt: episode.created_at
|
createdAt: episode.created_at
|
||||||
}))
|
}))
|
||||||
.catch(err => console.error(`Failed to embed episode ${episode.id}:`, err.message));
|
.catch(err => logger.error(`Failed to embed episode ${episode.id}:`, err.message));
|
||||||
|
|
||||||
extractAndStoreEntities(userMessage, aiResponse, projectId)
|
extractAndStoreEntities(userMessage, aiResponse, projectId)
|
||||||
.catch(err => console.error(`Failed to extract entities for episode ${episode.id}:`, err.message));
|
.catch(err => logger.error(`Failed to extract entities for episode ${episode.id}:`, err.message));
|
||||||
|
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require ('dotenv').config();
|
require ('dotenv').config();
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const {getEnv, PORTS, EPISODIC} = require('@nexusai/shared');
|
const {getEnv, PORTS, EPISODIC, logger} = require('@nexusai/shared');
|
||||||
const { getDB } = require('./db');
|
const { getDB } = require('./db');
|
||||||
const { createProject, getProjects, getProject, updateProject, deleteProject } = require('./db/projects');
|
const { createProject, getProjects, getProject, updateProject, deleteProject } = require('./db/projects');
|
||||||
const { createSummary, getSummary, getSummariesBySession, getSummariesByProject, updateSummary, deleteSummary } = require('./db/summaries');
|
const { createSummary, getSummary, getSummariesBySession, getSummariesByProject, updateSummary, deleteSummary } = require('./db/summaries');
|
||||||
@@ -19,8 +19,8 @@ const PORT = getEnv('PORT', PORTS.MEMORY);
|
|||||||
const db = getDB();
|
const db = getDB();
|
||||||
|
|
||||||
semantic.initCollections()
|
semantic.initCollections()
|
||||||
.then(() => console.log(`QDrant collections ready`))
|
.then(() => logger.info(`QDrant collections ready`))
|
||||||
.catch(err => console.error(`QDrant initialization error:`, err.message));
|
.catch(err => logger.error(`QDrant initialization error:`, err.message));
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
@@ -158,7 +158,7 @@ app.delete('/episodes/:id', (req, res) => {
|
|||||||
episodic.deleteEpisode(id);
|
episodic.deleteEpisode(id);
|
||||||
|
|
||||||
semantic.deleteEpisode(id) // fire-and-forget
|
semantic.deleteEpisode(id) // fire-and-forget
|
||||||
.catch(err => console.error(`[Memory] Qdrant delete failed for episode ${id}:`, err.message));
|
.catch(err => logger.error(`[Memory] Qdrant delete failed for episode ${id}:`, err.message));
|
||||||
|
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
});
|
});
|
||||||
@@ -338,5 +338,5 @@ app.delete('/summaries/:id', (req, res) => {
|
|||||||
/********** Start Server ********** */
|
/********** Start Server ********** */
|
||||||
/********************************** */
|
/********************************** */
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Memory Service is running on port ${PORT}`);
|
logger.info(`Memory Service is running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const {QdrantClient} = require('@qdrant/js-client-rest');
|
const {QdrantClient} = require('@qdrant/js-client-rest');
|
||||||
const {QDRANT, COLLECTIONS, getEnv} = require('@nexusai/shared');
|
const {QDRANT, COLLECTIONS, getEnv, logger} = require('@nexusai/shared');
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ async function initCollections() {
|
|||||||
distance: QDRANT.DISTANCE_METRIC
|
distance: QDRANT.DISTANCE_METRIC
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(`Created Qdrant collection: ${name}`);
|
logger.info(`Created Qdrant collection: ${name}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Qdrant collection already exists: ${name}`);
|
logger.info(`Qdrant collection already exists: ${name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const memory = require("../services/memory");
|
|||||||
const inference = require("../services/inference");
|
const inference = require("../services/inference");
|
||||||
const embedding = require("../services/embedding");
|
const embedding = require("../services/embedding");
|
||||||
const qdrant = require("../services/qdrant");
|
const qdrant = require("../services/qdrant");
|
||||||
const { ORCHESTRATION } = require("@nexusai/shared");
|
const { ORCHESTRATION, logger } = require("@nexusai/shared");
|
||||||
const appSettings = require("../config/settings");
|
const appSettings = require("../config/settings");
|
||||||
const {triggerSummary} = require('../services/summarization')
|
const {triggerSummary} = require('../services/summarization')
|
||||||
|
|
||||||
@@ -64,12 +64,12 @@ async function autoNameSession(externalId, userMessage, aiResponse) {
|
|||||||
const name = result.text?.trim().replace(/^["']|["']$/g, ""); // strip any quotes the model adds
|
const name = result.text?.trim().replace(/^["']|["']$/g, ""); // strip any quotes the model adds
|
||||||
if (name) {
|
if (name) {
|
||||||
await memory.updateSession(externalId, { name });
|
await memory.updateSession(externalId, { name });
|
||||||
console.log(
|
logger.info(
|
||||||
`[orchestration] Auto-named session "${externalId}": "${name}"`,
|
`[orchestration] Auto-named session "${externalId}": "${name}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
"[orchestration] Auto-naming failed (non-critical):",
|
"[orchestration] Auto-naming failed (non-critical):",
|
||||||
err.message,
|
err.message,
|
||||||
);
|
);
|
||||||
@@ -99,7 +99,7 @@ async function getSemanticEpisodes(
|
|||||||
);
|
);
|
||||||
return fetched.filter(Boolean);
|
return fetched.filter(Boolean);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[orchestration] Semantic search failed, continuing without: `,
|
`[orchestration] Semantic search failed, continuing without: `,
|
||||||
err.message,
|
err.message,
|
||||||
);
|
);
|
||||||
@@ -111,13 +111,13 @@ async function getRelevantEntities(userMessage, projectId=null) {
|
|||||||
try {
|
try {
|
||||||
const vector = await embedding.embed(userMessage);
|
const vector = await embedding.embed(userMessage);
|
||||||
const results = await qdrant.searchEntities(vector, { projectId });
|
const results = await qdrant.searchEntities(vector, { projectId });
|
||||||
console.log(
|
logger.info(
|
||||||
"[orchestration] Entity search results:",
|
"[orchestration] Entity search results:",
|
||||||
results.map((r) => ({ name: r.payload?.name, score: r.score })),
|
results.map((r) => ({ name: r.payload?.name, score: r.score })),
|
||||||
);
|
);
|
||||||
return results.map((r) => r.payload).filter(Boolean);
|
return results.map((r) => r.payload).filter(Boolean);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
"[orchestration] Entity search failed, continuing without:",
|
"[orchestration] Entity search failed, continuing without:",
|
||||||
err.message,
|
err.message,
|
||||||
);
|
);
|
||||||
@@ -143,7 +143,7 @@ async function chat(externalId, userMessage, options = {}) {
|
|||||||
projectSessionIds = projectSessions.map((s) => s.id);
|
projectSessionIds = projectSessions.map((s) => s.id);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
"[orchestration] Failed to resolve project context:",
|
"[orchestration] Failed to resolve project context:",
|
||||||
err.message,
|
err.message,
|
||||||
);
|
);
|
||||||
@@ -189,7 +189,7 @@ async function chat(externalId, userMessage, options = {}) {
|
|||||||
session.project_id ?? null,
|
session.project_id ?? null,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[orchestration] Failed to save episode:', err.message);
|
logger.error('[orchestration] Failed to save episode:', err.message);
|
||||||
}
|
}
|
||||||
const allEpisodes = await memory.getRecentEpisodes(session.id, 9999);
|
const allEpisodes = await memory.getRecentEpisodes(session.id, 9999);
|
||||||
triggerSummary(session, allEpisodes);
|
triggerSummary(session, allEpisodes);
|
||||||
@@ -231,7 +231,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
"[orchestration] Failed to resolve project context:",
|
"[orchestration] Failed to resolve project context:",
|
||||||
err.message,
|
err.message,
|
||||||
);
|
);
|
||||||
@@ -302,7 +302,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
throw new Error(data.error);
|
throw new Error(data.error);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
logger.error(
|
||||||
"[orchestration] Failed to parse inference SSE event:",
|
"[orchestration] Failed to parse inference SSE event:",
|
||||||
raw,
|
raw,
|
||||||
err.message,
|
err.message,
|
||||||
@@ -316,7 +316,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
const allEpisodes = await memory.getRecentEpisodes(session.id, 9999);
|
const allEpisodes = await memory.getRecentEpisodes(session.id, 9999);
|
||||||
triggerSummary(session, allEpisodes);
|
triggerSummary(session, allEpisodes);
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
logger.warn(
|
||||||
"[orchestration] Stream finished with no assistant text; episode not saved",
|
"[orchestration] Stream finished with no assistant text; episode not saved",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
|
|
||||||
return { model, tokenCount };
|
return { model, tokenCount };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
logger.error(
|
||||||
"[orchestration] chatStream fatal error:",
|
"[orchestration] chatStream fatal error:",
|
||||||
err.message,
|
err.message,
|
||||||
err.stack,
|
err.stack,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const { Router } = require('express')
|
const { Router } = require('express')
|
||||||
const { chat, chatStream } = require('../chat/index');
|
const { chat, chatStream } = require('../chat/index');
|
||||||
const memory = require('../services/memory')
|
const memory = require('../services/memory')
|
||||||
|
const logger = require('@nexusai/shared');
|
||||||
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ router.post('/', async (req, res) => {
|
|||||||
});
|
});
|
||||||
res.json(result)
|
res.json(result)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[orchestration] chat error: `, err.message)
|
logger.error(`[orchestration] chat error: `, err.message)
|
||||||
res.status(500).json ({ error: err.message})
|
res.status(500).json ({ error: err.message})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const appSettings = require('../config/settings');
|
const appSettings = require('../config/settings');
|
||||||
|
|
||||||
const { getEnv, LLAMACPP } = require('@nexusai/shared');
|
const { getEnv, LLAMACPP, logger } = require('@nexusai/shared');
|
||||||
const LLAMA_URL = getEnv('LLAMA_SERVER_URL', LLAMACPP.DEFAULT_URL);
|
const LLAMA_URL = getEnv('LLAMA_SERVER_URL', LLAMACPP.DEFAULT_URL);
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
@@ -38,7 +38,7 @@ router.get('/', (req, res) => {
|
|||||||
|
|
||||||
res.json(models);
|
res.json(models);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[models] Failed to scan folder:', err.message);
|
logger.error('[models] Failed to scan folder:', err.message);
|
||||||
res.status(500).json({ error: `Could not read models folder: ${modelsFolderPath}` });
|
res.status(500).json({ error: `Could not read models folder: ${modelsFolderPath}` });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,7 @@ router.get('/props', async (req, res) => {
|
|||||||
modelAlias: data.model_alias,
|
modelAlias: data.model_alias,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[models/props]', err.message);
|
logger.error('[models/props]', err.message);
|
||||||
res.status(503).json({ error: 'Could not reach llama-server' });
|
res.status(503).json({ error: 'Could not reach llama-server' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { getEnv, SERVICES, SUMMARIES } = require('@nexusai/shared');
|
const { getEnv, SERVICES, SUMMARIES, logger } = require('@nexusai/shared');
|
||||||
|
|
||||||
const EXTRACTION_URL = getEnv('EXTRACTION_URL', 'http://localhost:11434');
|
const EXTRACTION_URL = getEnv('EXTRACTION_URL', 'http://localhost:11434');
|
||||||
const EXTRACTION_MODEL = getEnv('EXTRACTION_MODEL', 'qwen2.5:3b');
|
const EXTRACTION_MODEL = getEnv('EXTRACTION_MODEL', 'qwen2.5:3b');
|
||||||
@@ -104,7 +104,7 @@ async function maybeSummarize(session, allEpisodes) {
|
|||||||
const totalEpisodeTokens = allEpisodes.reduce((sum, ep) => sum + (ep.token_count || 0), 0);
|
const totalEpisodeTokens = allEpisodes.reduce((sum, ep) => sum + (ep.token_count || 0), 0);
|
||||||
|
|
||||||
// add temporarily before the generateSummary call
|
// add temporarily before the generateSummary call
|
||||||
console.log('[summarization] episodes to summarize:', episodesToSummarize.length);
|
logger.debug('[summarization] episodes to summarize:', episodesToSummarize.length);
|
||||||
|
|
||||||
const content = await generateSummary(
|
const content = await generateSummary(
|
||||||
episodesToSummarize,
|
episodesToSummarize,
|
||||||
@@ -126,7 +126,7 @@ async function maybeSummarize(session, allEpisodes) {
|
|||||||
episodeRange,
|
episodeRange,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log(`[summarization] Created new summary for session ${session.id}`);
|
logger.debug(`[summarization] Created new summary for session ${session.id}`);
|
||||||
} else {
|
} else {
|
||||||
await fetch(`${MEMORY_URL}/summaries/${latest.id}`, {
|
await fetch(`${MEMORY_URL}/summaries/${latest.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -137,14 +137,14 @@ async function maybeSummarize(session, allEpisodes) {
|
|||||||
episodeRange,
|
episodeRange,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log(`[summarization] Updated summary ${latest.id} for session ${session.id}`);
|
logger.debug(`[summarization] Updated summary ${latest.id} for session ${session.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function triggerSummary(session, allEpisodes) {
|
async function triggerSummary(session, allEpisodes) {
|
||||||
// Intentionally fire-and-forget — caller doesn't await this
|
// Intentionally fire-and-forget — caller doesn't await this
|
||||||
maybeSummarize(session, allEpisodes).catch(err =>
|
maybeSummarize(session, allEpisodes).catch(err =>
|
||||||
console.warn('[summarization] Summary failed (non-critical):', err.message)
|
logger.warn('[summarization] Summary failed (non-critical):', err.message)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user