feat(refactor): live zed-style codebase files autocomplete and context attachment
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
} from "@/lib/dev-container-git";
|
||||
import { buildDesignKitPromptSection } from "@/lib/design-kits/for-ai";
|
||||
import { buildCodebaseSummary } from "@/lib/ai/codebase-summary";
|
||||
import { execInDevContainer } from "@/lib/dev-container";
|
||||
import type { ChatMessage, ToolCall } from "@/lib/ai/gemini-chat";
|
||||
|
||||
// C-01: Lowered from 15 → 8. Real workflows (scaffold → install →
|
||||
@@ -404,6 +405,7 @@ export async function POST(request: Request) {
|
||||
workspace: string;
|
||||
mcp_token?: string;
|
||||
chatMode?: "vibe" | "collaborate" | "delegate";
|
||||
attachedFiles?: string[];
|
||||
};
|
||||
try {
|
||||
body = await request.json();
|
||||
@@ -411,7 +413,14 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { thread_id, message, workspace, mcp_token, chatMode = "vibe" } = body;
|
||||
const {
|
||||
thread_id,
|
||||
message,
|
||||
workspace,
|
||||
mcp_token,
|
||||
chatMode = "vibe",
|
||||
attachedFiles = [],
|
||||
} = body;
|
||||
if (!thread_id || !message?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: "thread_id and message are required" },
|
||||
@@ -529,6 +538,32 @@ export async function POST(request: Request) {
|
||||
chatMode,
|
||||
);
|
||||
|
||||
let fileContextsBlock = "";
|
||||
if (
|
||||
Array.isArray(attachedFiles) &&
|
||||
attachedFiles.length > 0 &&
|
||||
activeProject?.slug
|
||||
) {
|
||||
fileContextsBlock =
|
||||
"\n\n=== USER-ATTACHED CODE CONTEXT ===\nThe user has explicitly attached the following files to this conversation turn as active context. You MUST refer to these file states when writing your response or deciding edits:\n";
|
||||
for (const f of attachedFiles) {
|
||||
const safePath = String(f).replace(/\.\./g, "").replace(/^\//, "");
|
||||
try {
|
||||
const res = (await execInDevContainer({
|
||||
projectId: activeProject.id,
|
||||
command: `cat "/workspace/${activeProject.slug}/${safePath}" 2>/dev/null || echo "[File not found]"`,
|
||||
})) as unknown as { exitCode: number; stdout: string };
|
||||
fileContextsBlock += `\nFile: \`${safePath}\`\n\`\`\`\n${res.stdout}\n\`\`\`\n`;
|
||||
} catch {
|
||||
fileContextsBlock += `\nFile: \`${safePath}\`\n[Error reading file]\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fileContextsBlock) {
|
||||
systemPrompt += fileContextsBlock;
|
||||
}
|
||||
|
||||
// Sentry-as-product Stage 4: auto-surface unresolved errors at
|
||||
// chat-turn start. We pull the last 6 hours' unresolved issues
|
||||
// for the active project; if anything has fired ≥2 times, we
|
||||
|
||||
63
vibn-frontend/app/api/projects/[projectId]/files/route.ts
Normal file
63
vibn-frontend/app/api/projects/[projectId]/files/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import {
|
||||
execInDevContainer,
|
||||
ensureDevContainer,
|
||||
} from "../../../../../lib/dev-container";
|
||||
import { authSession } from "../../../../../lib/auth/session-server";
|
||||
import { queryOne } from "../../../../../lib/db-postgres";
|
||||
import type { VibnWorkspace } from "../../../../../lib/workspaces";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
req: Request,
|
||||
ctx: { params: Promise<{ projectId: string }> },
|
||||
) {
|
||||
try {
|
||||
const { projectId } = await ctx.params;
|
||||
const { searchParams } = new URL(req.url);
|
||||
const workspace = searchParams.get("workspace") || "";
|
||||
|
||||
const session = await authSession();
|
||||
if (!session?.user?.email || !workspace) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Fetch project details to get the slug
|
||||
const r = await queryOne<{ data: { slug: string } }>(
|
||||
`SELECT data FROM fs_projects WHERE id = $1`,
|
||||
[projectId],
|
||||
);
|
||||
if (!r?.data?.slug) {
|
||||
return NextResponse.json({ error: "Project not found" }, { status: 404 });
|
||||
}
|
||||
const projectSlug = r.data.slug;
|
||||
|
||||
// Ensure the container is active
|
||||
await ensureDevContainer({
|
||||
projectId,
|
||||
projectSlug,
|
||||
projectName: projectSlug,
|
||||
workspace: { slug: workspace } as unknown as VibnWorkspace,
|
||||
});
|
||||
|
||||
// Run a fast find inside the dev container, excluding build/node_modules/git artifacts
|
||||
const result = (await execInDevContainer({
|
||||
projectId,
|
||||
command: `find . -maxdepth 4 -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.next/*" -not -path "*/dist/*" -not -path "*/build/*" | sort | sed 's|^\\./||'`,
|
||||
})) as unknown as { exitCode: number; stdout: string };
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
return NextResponse.json({ files: [] });
|
||||
}
|
||||
|
||||
const files = result.stdout
|
||||
.split("\n")
|
||||
.map((f: string) => f.trim())
|
||||
.filter((f: string) => f && f !== "." && f !== "..");
|
||||
|
||||
return NextResponse.json({ files });
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: String(err) }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user