feat(assistant): Fragen an die Daten im Kopf der Anwendung
Management und Administration koennen oben rechts eine Frage in Worten stellen - "wie viele Stunden im August", "welches Projekt hat noch Budget", "wie ist die Auslastung". Aufbau: das Modell bekommt eine Uebersicht (Kunden, laufende Projekte, Eckdaten) und vier Werkzeuge - auswertung, projekte, buchungen, personen. Damit fragt es gezielt nach, statt dass ihm der ganze Bestand vorgelegt wird; 19.000 Buchungen passen in keinen Kontext. Die Werkzeuge rechnen mit loadEntries und loadProjects, also mit denselben Funktionen wie die Reports - der Assistent kann so keine zweite Wahrheit erzeugen. Was einfliesst, begrenzt visibleUserIds wie ueberall sonst. Benutzt wird die Responses-API von OpenAI: die neueren Modelle nehmen Werkzeuge in /v1/chat/completions nur ohne Nachdenken an. Mit "store: false" bleibt nichts bei OpenAI liegen, der Verlauf geht deshalb je Runde komplett mit. Der Schluessel liegt verschluesselt in den Einstellungen (AES-256-GCM wie das SSO-Secret) und wird nie ausgeliefert; das Modell ist einstellbar, Vorgabe gpt-5.6-luna. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c3aed8a3d3
commit
e908db2788
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import { requireRole, ROLES } from '../middleware/auth.js';
|
||||
import { DEFAULTS, getSettings, saveSetting } from '../lib/settings.js';
|
||||
import { ssoConfigured, testConnection } from '../lib/sso.js';
|
||||
import { answerText, askModel } from '../lib/ai.js';
|
||||
import { sendDocument } from '../export/render.js';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -37,6 +38,7 @@ router.get('/', requireRole('admin'), async (req, res, next) => {
|
||||
secret_set: Boolean(s.hrworks.secret_access_key),
|
||||
configured: Boolean(s.hrworks.access_key && s.hrworks.secret_access_key),
|
||||
},
|
||||
ai: { ...s.ai, api_key: '', key_set: Boolean(s.ai.api_key), configured: Boolean(s.ai.api_key) },
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
@@ -125,6 +127,17 @@ router.put('/', requireRole('admin'), async (req, res, next) => {
|
||||
if (!errors.length) await saveSetting('hrworks', next_);
|
||||
}
|
||||
|
||||
if (body.ai) {
|
||||
const next_ = { ...current.ai };
|
||||
if (body.ai.model !== undefined) next_.model = text(body.ai.model, 80).trim() || DEFAULTS.ai.model;
|
||||
if (body.ai.enabled !== undefined) next_.enabled = Boolean(body.ai.enabled);
|
||||
// Leer = unveraendert; der Schluessel wird nie ausgeliefert.
|
||||
if (body.ai.api_key) next_.api_key = text(body.ai.api_key, 300).trim();
|
||||
if (body.ai.clear_key) next_.api_key = '';
|
||||
if (next_.enabled && !next_.api_key) errors.push('Der KI-Assistent braucht einen OpenAI-Schlüssel.');
|
||||
if (!errors.length) await saveSetting('ai', next_);
|
||||
}
|
||||
|
||||
if (body.sso) {
|
||||
const next_ = { ...current.sso };
|
||||
for (const key of ['tenant_id', 'client_id', 'redirect_uri', 'button_label']) {
|
||||
@@ -149,6 +162,22 @@ router.put('/', requireRole('admin'), async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
/** Schluessel und Modell einmal ausprobieren. */
|
||||
router.post('/ai/test', requireRole('admin'), async (req, res) => {
|
||||
try {
|
||||
const { ai } = await getSettings();
|
||||
const response = await askModel(ai, {
|
||||
instructions: 'Antworte mit genau einem Wort: OK.',
|
||||
input: [{ role: 'user', content: 'Test' }],
|
||||
maxOutputTokens: 200,
|
||||
effort: 'low',
|
||||
});
|
||||
res.json({ ok: true, model: response.model, answer: answerText(response).slice(0, 200) });
|
||||
} catch (err) {
|
||||
res.status(err.status || 502).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/sso/test', requireRole('admin'), async (req, res) => {
|
||||
try {
|
||||
const { sso } = await getSettings();
|
||||
|
||||
Reference in New Issue
Block a user