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:
1 parent
ada029d500
commit
7d94fe53ad
6 files changed
+461
-2
No files matched your search
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user