Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2de2a01
feat: fine-tuning pipeline + displacement textures RAG script
Ker102 Feb 17, 2026
99b3e8c
feat: photorealism RAG scripts + pitfall updates
Ker102 Feb 17, 2026
0716f00
feat: interior rooms RAG script for enclosed room/wall creation
Ker102 Feb 17, 2026
31ab147
feat: wire viewport vision into executor — post-execution visual feed…
Ker102 Feb 17, 2026
053bdff
docs: add addon integration roadmap — 80+ addons cataloged from Blend…
Ker102 Feb 17, 2026
c5bc258
docs: 3D pipeline strategy — leaderboard, competitor analysis, integr…
Ker102 Feb 17, 2026
d00ed12
docs: comprehensive 3D pipeline strategy — techniques, competitors, p…
Ker102 Feb 17, 2026
a3e0f71
docs: 3D pipeline technique research — retopology, rigging, textures,…
Ker102 Feb 17, 2026
27b75d5
docs: update GEMINI.md — 3D pipeline strategy session, 7-phase roadma…
Ker102 Feb 17, 2026
0060874
docs: session handoff — full knowledge map + 6-phase execution plan f…
Ker102 Feb 18, 2026
6433312
feat: Phase 1 production pipeline — 6 new RAG scripts + prompt update
Ker102 Feb 18, 2026
7da22aa
feat: Phase 2 — self-hosted neural 3D generation layer
Ker102 Feb 18, 2026
d2aca48
feat: Phase 4 AI Strategy Router — classify requests as procedural/ne…
Ker102 Feb 18, 2026
c1ac618
feat: Phase 3 — Guided Workflow System (human-in-the-loop)
Ker102 Feb 18, 2026
4f519b1
docs: add future roadmap items — CRAG pipeline, tool use guide, searc…
Ker102 Feb 20, 2026
ab78eff
feat: upgrade Gemini model to gemini-3.1-pro-preview
Ker102 Feb 20, 2026
5229952
fix: change mock neural server port to 8090 to avoid Next.js conflict
Ker102 Feb 20, 2026
3103803
fix: always set mat.use_nodes = True — fixes NoneType node_tree on Bl…
Ker102 Feb 20, 2026
30c31ab
fix: add logType to step result entries & improve screenshot debug lo…
Ker102 Feb 20, 2026
5d5958c
feat: add network error handling with retry button
Ker102 Feb 20, 2026
9be6b88
fix: screenshot params + preserve lighting in scene edits
Ker102 Feb 20, 2026
0a3c687
refactor: modernize prompts.ts for Blender 5.x
Ker102 Feb 20, 2026
6dddff8
fix: add removed shader sockets to AVOID section
Ker102 Feb 20, 2026
45c0f35
fix: addon screenshot returns base64 + shadow_method AVOID rule
Ker102 Feb 20, 2026
8c8f237
fix: timeout 180s + EEVEE 5.x RAG scripts + SSR AVOID rule
Ker102 Feb 20, 2026
98f1433
fix: grounding rules, light energy, addon os import
Ker102 Feb 20, 2026
14bbbc7
refactor: make stream stall timeout configurable via env var
Ker102 Feb 20, 2026
e81d674
fix: ALPHA_BLEND -> BLEND, strengthen shadow_method AVOID
Ker102 Feb 20, 2026
baad231
feat: add blend_method, EEVEE removals, transparent material to RAG s…
Ker102 Feb 20, 2026
0f82e28
feat: planner edit awareness — scene state to code gen, structured JS…
Ker102 Feb 20, 2026
54b78a9
docs: update GEMINI.md with 2026-02-21 session log and progress tracking
Ker102 Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 160 additions & 5 deletions GEMINI.md

Large diffs are not rendered by default.

261 changes: 161 additions & 100 deletions app/api/ai/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import type {
import { recordExecutionLog } from "@/lib/orchestration/monitor"
import { buildSystemPrompt } from "@/lib/orchestration/prompts"
import { searchFirecrawl, type FirecrawlSearchResult } from "@/lib/firecrawl"
import { classifyStrategy } from "@/lib/orchestration/strategy-router"
import { generateWorkflowProposal } from "@/lib/orchestration/workflow-advisor"
import { z } from "zod"

const MAX_HISTORY_MESSAGES = 12
Expand Down Expand Up @@ -148,45 +150,40 @@ function formatSceneSnapshot(payload: unknown): string | null {
const objectCount = typeof scene.object_count === "number" ? scene.object_count : undefined
const errorMessage = typeof scene.error === "string" ? (scene.error as string) : undefined

const objectList = Array.isArray(scene.objects) ? scene.objects.slice(0, 12) : []
const objects = objectList
.map((raw) => {
if (!raw || typeof raw !== "object") {
return "- (unknown object)"
const objectList = Array.isArray(scene.objects) ? scene.objects.slice(0, 30) : []
const structuredObjects = objectList
.filter((raw): raw is Record<string, unknown> => !!raw && typeof raw === "object")
.map((obj) => {
const entry: Record<string, unknown> = {
name: typeof obj.name === "string" ? obj.name : "(unnamed)",
type: typeof obj.type === "string" ? obj.type : "UNKNOWN",
}
const obj = raw as Record<string, unknown>
const identifier = typeof obj.name === "string" ? obj.name : "(unnamed)"
const type = typeof obj.type === "string" ? obj.type : "UNKNOWN"
const locationArray = Array.isArray(obj.location) ? obj.location : []
const location = locationArray
.slice(0, 3)
.map((value) => (typeof value === "number" ? value.toFixed(2) : "?"))
.join(", ")
return `- ${identifier} [${type}] @ (${location})`
if (Array.isArray(obj.location)) {
entry.location = obj.location.slice(0, 3).map((v) => typeof v === "number" ? Math.round(v * 100) / 100 : 0)
}
if (Array.isArray(obj.dimensions)) {
entry.dimensions = obj.dimensions.slice(0, 3).map((v) => typeof v === "number" ? Math.round(v * 100) / 100 : 0)
}
return entry
})
.join("\n")

const materials =
const materialsCount =
typeof scene.materials_count === "number"
? `Materials: ${scene.materials_count}`
? scene.materials_count
: undefined

let summary = `Scene: ${name}`
if (typeof objectCount === "number") {
summary += ` | Objects: ${objectCount}`
}
if (materials) {
summary += ` | ${materials}`
// Return structured JSON so the model can parse object names and locations precisely
const snapshot: Record<string, unknown> = {
scene: name,
object_count: objectCount,
materials_count: materialsCount,
objects: structuredObjects,
}
if (errorMessage) {
summary += ` | Error: ${errorMessage}`
snapshot.error = errorMessage
}

if (objects) {
summary += `\nObjects:\n${objects}`
}

return summary
return JSON.stringify(snapshot, null, 2)
}


Expand Down Expand Up @@ -501,94 +498,158 @@ export async function POST(req: Request) {

const sceneSnapshotResult = await fetchSceneSummary()

// Strategy classification: determine procedural vs neural vs hybrid
const strategyDecision = await classifyStrategy(message, {
sceneContext: sceneSnapshotResult.summary ?? undefined,
})
send({
type: "agent:strategy_classification",
timestamp: new Date().toISOString(),
strategy: strategyDecision.strategy,
confidence: strategyDecision.confidence,
reasoning: strategyDecision.reasoning,
method: strategyDecision.classificationMethod,
})

let executedCommands: ExecutedCommand[] = []
let planningMetadata: PlanningMetadata | null = null
let executionLogs: ExecutionLogEntry[] | undefined = undefined
let planResult: PlanGenerationResult | null = null

try {
planResult = await planner.generatePlan(
message,
{
sceneSummary: sceneSnapshotResult.summary ?? undefined,
allowHyper3dAssets: assetConfig.allowHyper3d,
allowSketchfabAssets: assetConfig.allowSketchfab,
allowPolyHavenAssets: assetConfig.allowPolyHaven,
researchContext: researchContext?.promptContext,
},
llmProvider
)
// ── Neural/Hybrid → Guided Workflow (human-in-the-loop) ──
if (strategyDecision.strategy === "neural" || strategyDecision.strategy === "hybrid") {
try {
const workflowProposal = await generateWorkflowProposal(
message,
strategyDecision.strategy,
{ sceneContext: sceneSnapshotResult.summary ?? undefined }
)

// Send the workflow proposal to the UI
send({
type: "agent:workflow_proposal",
timestamp: new Date().toISOString(),
proposal: workflowProposal,
})

// Build a lightweight planningMetadata for the conversation record
planningMetadata = {
planSummary: `Workflow proposed: ${workflowProposal.title} (${workflowProposal.steps.length} steps). Awaiting user action on each step.`,
planSteps: workflowProposal.steps.map((s) => ({
stepNumber: s.stepNumber,
action: s.recommendedTool === "neural" ? "neural_generate" : s.recommendedTool === "manual" ? "manual" : "execute_code",
parameters: {
category: s.category,
tool: s.recommendedTool,
neuralProvider: s.neuralProvider,
workflowStepId: s.id,
},
rationale: s.toolReasoning,
expectedOutcome: s.description,
})),
rawPlan: JSON.stringify(workflowProposal, null, 2),
retries: 0,
executionSuccess: true, // Proposal was successfully generated
sceneSnapshot: sceneSnapshotResult.summary,
strategyDecision,
}
} catch (workflowError) {
console.error("Workflow proposal failed, falling back to planner:", workflowError)
// Fall through to the procedural planner+executor below
}
}

if (planResult && planResult.plan) {
const executionResult = await planExecutor.executePlan(
planResult.plan,
// ── Procedural → Auto-pilot planner + executor (existing flow) ──
if (!planningMetadata) {

try {
planResult = await planner.generatePlan(
message,
{
...assetConfig,
onStreamEvent: (event) => send(event),
sceneSummary: sceneSnapshotResult.summary ?? undefined,
allowHyper3dAssets: assetConfig.allowHyper3d,
allowSketchfabAssets: assetConfig.allowSketchfab,
allowPolyHavenAssets: assetConfig.allowPolyHaven,
researchContext: researchContext?.promptContext,
strategyDecision,
},
planResult.analysis,
llmProvider
)
executionLogs = executionResult.logs
executedCommands = buildExecutedCommandsFromPlan(planResult.plan, executionResult)
planningMetadata = {
planSummary: planResult.plan.planSummary,
planSteps: planResult.plan.steps,
rawPlan: planResult.rawResponse,
retries: planResult.retries ?? 0,
executionSuccess: executionResult.success,
errors: planResult.errors,
executionLog: executionResult.logs,

if (planResult && planResult.plan) {
const executionResult = await planExecutor.executePlan(
planResult.plan,
message,
{
...assetConfig,
enableVisualFeedback: true,
onStreamEvent: (event) => send(event),
strategyDecision,
},
planResult.analysis,
llmProvider
)
executionLogs = executionResult.logs
executedCommands = buildExecutedCommandsFromPlan(planResult.plan, executionResult)
planningMetadata = {
planSummary: planResult.plan.planSummary,
planSteps: planResult.plan.steps,
rawPlan: planResult.rawResponse,
retries: planResult.retries ?? 0,
executionSuccess: executionResult.success,
errors: planResult.errors,
executionLog: executionResult.logs,
sceneSnapshot: sceneSnapshotResult.summary,
analysis: planResult.analysis,
researchSummary: researchContext?.promptContext,
researchSources: researchContext?.sources,
strategyDecision,
}

if (!executionResult.success) {
planningMetadata.executionSuccess = false
}
} else if (planResult) {
const previousLogs = executionLogs
planningMetadata = {
planSummary: "Plan generation failed",
planSteps: [],
rawPlan: planResult.rawResponse,
retries: planResult.retries ?? 0,
executionSuccess: false,
errors: planResult.errors,
fallbackUsed: false,
executionLog: previousLogs,
sceneSnapshot: sceneSnapshotResult.summary,
analysis: planResult.analysis,
researchSummary: researchContext?.promptContext,
researchSources: researchContext?.sources,
}
executedCommands = []
} else {
throw new Error("Planner returned no result")
}
} catch (error) {
console.error("Planning pipeline error:", error)
const messageText =
error instanceof Error ? error.message : "Unknown planning error"
planningMetadata = planningMetadata ?? {
planSummary: "Planner error",
planSteps: [],
rawPlan: "",
retries: 0,
executionSuccess: false,
errors: [messageText],
fallbackUsed: false,
executionLog: executionLogs,
sceneSnapshot: sceneSnapshotResult.summary,
analysis: planResult.analysis,
analysis: planResult?.analysis,
researchSummary: researchContext?.promptContext,
researchSources: researchContext?.sources,
}

if (!executionResult.success) {
planningMetadata.executionSuccess = false
}
} else if (planResult) {
const previousLogs = executionLogs
planningMetadata = {
planSummary: "Plan generation failed",
planSteps: [],
rawPlan: planResult.rawResponse,
retries: planResult.retries ?? 0,
executionSuccess: false,
errors: planResult.errors,
fallbackUsed: false,
executionLog: previousLogs,
sceneSnapshot: sceneSnapshotResult.summary,
analysis: planResult.analysis,
researchSummary: researchContext?.promptContext,
researchSources: researchContext?.sources,
}
executedCommands = []
} else {
throw new Error("Planner returned no result")
}
} catch (error) {
console.error("Planning pipeline error:", error)
const messageText =
error instanceof Error ? error.message : "Unknown planning error"
planningMetadata = planningMetadata ?? {
planSummary: "Planner error",
planSteps: [],
rawPlan: "",
retries: 0,
executionSuccess: false,
errors: [messageText],
fallbackUsed: false,
executionLog: executionLogs,
sceneSnapshot: sceneSnapshotResult.summary,
analysis: planResult?.analysis,
researchSummary: researchContext?.promptContext,
researchSources: researchContext?.sources,
}
executedCommands = []
}
} // ← closing brace for procedural fallback block



Expand Down
Loading
Loading