+ {/* Header */}
+
+
+
+
+
+
+
+
+ {/* Thread list dropdown */}
+ {showThreads && (
+
+ {threads.length === 0 && (
+
+ No conversations yet
+
+ )}
+ {threads.map((t) => (
+
loadThread(t.id)}
+ style={{
+ display: "flex", alignItems: "center", justifyContent: "space-between",
+ padding: "9px 16px",
+ background: activeThread === t.id ? "#f0ede8" : "transparent",
+ cursor: "pointer",
+ borderBottom: "1px solid #f0ede8",
+ }}
+ onMouseEnter={(e) => { if (activeThread !== t.id) e.currentTarget.style.background = "#f7f4ef"; }}
+ onMouseLeave={(e) => { if (activeThread !== t.id) e.currentTarget.style.background = "transparent"; }}
+ >
+
+
+ {t.title}
+
+
{timeAgo(t.updatedAt)}
+
+
+
+ ))}
+
+ )}
+
+ {/* Messages */}
+
+ {messages.length === 0 && !sending && (
+
+
+ V
+
+
+ Vibn AI
+
+
+ Ask me to deploy an app, register a domain,
or help plan your next product.
+
+
+ )}
+
+ {messages.map((msg, i) => (
+
+ ))}
+
+ {toolEvents.map((ev, i) => (
+
+ ))}
+
+ {sending && messages[messages.length - 1]?.role !== "assistant" && (
+
+
+ V
+
+
+ {[0, 1, 2].map((i) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+ {/* Input */}
+
+ {!mcpToken && (
+
+ Read-only mode — add your MCP token in Settings to enable actions.
+
+ )}
+
+
+
+ Powered by Gemini 3.1 Pro
+
+
+
+
+
+ );
+}
diff --git a/lib/ai/gemini-chat.ts b/lib/ai/gemini-chat.ts
new file mode 100644
index 00000000..aa0d3817
--- /dev/null
+++ b/lib/ai/gemini-chat.ts
@@ -0,0 +1,192 @@
+/**
+ * Gemini 3.1 Pro streaming chat client with tool-calling support.
+ *
+ * Uses the Gemini API (generativelanguage.googleapis.com) with the
+ * existing GOOGLE_API_KEY. Drop-in upgrade to Vertex AI when needed
+ * by swapping GEMINI_BASE_URL.
+ */
+
+const GEMINI_API_KEY = process.env.GOOGLE_API_KEY || '';
+const GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || 'gemini-3.1-pro-preview';
+const GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
+
+export interface ChatMessage {
+ role: 'user' | 'assistant' | 'tool';
+ content: string;
+ /** Populated when role === 'assistant' and model made tool calls */
+ toolCalls?: ToolCall[];
+ /** Populated when role === 'tool' */
+ toolCallId?: string;
+ toolName?: string;
+}
+
+export interface ToolCall {
+ id: string;
+ name: string;
+ args: Record