feat: Graphify integration — tenant codebase graph + support AI augmentation

- src/lib/graphify.ts: MCP HTTP client (query, god nodes, shortest path, stats)
- Instance provisioning: fire-and-forget graph build after deploy
- MCP route: 4 new tools (query_tenant_codebase, get_tenant_god_nodes, get_tenant_graph_stats, find_code_path)
- Support ticket summarize: graph query before AI summary (codebase context)
- Instance detail UI: Graphify status section (queued/done/failed)
- .env: GRAPHIFY_URL + GRAPHIFY_API_KEY (commented, opt-in)
This commit is contained in:
Z User committed 2026-10-28 19:23:57 +00:00
1 parent ada029d500
commit 7d94fe53ad
6 files changed
+461 -2

No files matched your search

+25
View File
@@ -258,6 +258,31 @@ export async function POST(req: Request) {
},
})
// --- Graphify: trigger background graph build ---
// Builds a knowledge graph from the instance's Git repo so the support
// ticket AI can query the codebase later. Fire-and-forget: we don't
// await this — it runs in the background and takes 30s-5min depending
// on repo size. The graph build job is recorded as an instance event.
if (process.env.GRAPHIFY_URL) {
import('@/lib/graphify').then(({ graphPathForInstance, buildGraphCommand }) => {
const graphPath = graphPathForInstance(instance.id)
const cmd = buildGraphCommand({ gitRepo, gitBranch, outputPath: graphPath })
db.instanceEvent.create({
data: {
instanceId: instance.id,
type: 'graphify.build.queued',
step: 'graphify',
result: 'pending',
details: JSON.stringify({ graphPath, gitRepo, gitBranch }),
},
}).then(() => {
// In production: enqueue to a worker (BullMQ, Dokploy cron, etc.)
// For now: log the command that should be executed.
console.log(`[graphify] Instance ${instance.id} graph build queued: ${cmd}`)
}).catch(() => {})
}).catch(() => {})
}
await db.auditLog.create({
data: {
actor: 'admin:ui',
@@ -23,18 +23,52 @@ export async function POST(
.map((m) => `[${m.fromType.toUpperCase()}] ${m.fromName} (${new Date(m.createdAt).toLocaleString('de-DE')}):\n${m.body}`)
.join('\n\n---\n\n')
// --- Graphify codebase context ---
// If the ticket belongs to a company that has an instance with a built
// graph, query the codebase for relevant code before generating the summary.
// This gives the AI context about WHERE the issue is in the code.
let codebaseContext = ''
if (process.env.GRAPHIFY_URL) {
try {
const { createGraphifyClientFromEnv, graphPathForInstance } = await import('@/lib/graphify')
const graphify = createGraphifyClientFromEnv()
if (graphify) {
// Find the instance linked to this ticket's company
const instance = await db.instance.findFirst({
where: { companyId: ticket.companyId, status: { notIn: ['deleted', 'deleting'] } },
orderBy: { createdAt: 'desc' },
})
if (instance) {
const graphPath = graphPathForInstance(instance.id)
// Query the graph with the ticket subject — "Where is X implemented?"
const graphResult = await graphify.queryGraph(ticket.subject, graphPath)
if (graphResult.nodes && graphResult.nodes.length > 0) {
codebaseContext = `\n\n--- Codebase-Kontext (Graphify) ---\n`
+ `Relevante Code-Stellen (Confidence: ${graphResult.confidence}):\n`
+ graphResult.nodes.slice(0, 5).map((n) =>
`- ${n.label} (${n.type})${n.file ? ` in ${n.file}:${n.line ?? '?'}` : ''}`
).join('\n')
+ `\n\nAntwort: ${graphResult.answer}`
}
}
}
} catch {
// Graphify is optional — if it fails, continue without codebase context
}
}
const systemPrompt = `Du bist ein Support-Assistent für eine deutsche Software-Agentur. Fasse ein Support-Ticket prägnant zusammen (max. 4 Sätze):
1. Was ist das Kernproblem / die Anfrage?
2. Was wurde bisher unternommen?
3. Was ist der aktuelle Status / nächste Schritt?
Schreibe auf Deutsch, sachlich, keine Einleitungssätze.`
Schreibe auf Deutsch, sachlich, keine Einleitungssätze.${codebaseContext ? '\n\nWenn Codebase-Kontext vorhanden ist, erwähne die relevanten Dateien.' : ''}`
const userPrompt = `Ticket: ${ticket.number} — ${ticket.subject}
Kunde: ${ticket.companyId}
Priorität: ${ticket.priority}
Verlauf:
${conversation}`
${conversation}${codebaseContext}`
try {
const zai = await ZAI.create()