Introduces new API endpoints for AI chat and title generation, integrates an AI chat component into the layout, and updates client-side services to communicate with the new backend AI endpoints. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 64961019-b4a5-48d8-97fc-c4980d29f3c4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/fhRML7y Replit-Helium-Checkpoint-Created: true
172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
import type { Request, Response } from "express";
|
|
import { GoogleGenAI, Chat, FunctionDeclaration, Type } from "@google/genai";
|
|
|
|
const GEMINI_API_KEY = process.env.AI_INTEGRATIONS_GEMINI_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
|
|
const ai = GEMINI_API_KEY ? new GoogleGenAI({ apiKey: GEMINI_API_KEY }) : null;
|
|
|
|
interface ChatMessage {
|
|
role: "user" | "model";
|
|
content: string;
|
|
}
|
|
|
|
interface ChatRequest {
|
|
prompt: string;
|
|
history: ChatMessage[];
|
|
systemInstruction?: string;
|
|
personaId?: string;
|
|
useTools?: boolean;
|
|
}
|
|
|
|
const AETHEX_TOOLS: FunctionDeclaration[] = [
|
|
{
|
|
name: "get_account_balance",
|
|
description: "Retrieves the current AETH balance for a given wallet address.",
|
|
parameters: {
|
|
type: Type.OBJECT,
|
|
properties: {
|
|
address: {
|
|
type: Type.STRING,
|
|
description: "The 42-character hexadecimal Aethex wallet address.",
|
|
},
|
|
},
|
|
required: ["address"],
|
|
},
|
|
},
|
|
{
|
|
name: "get_transaction_details",
|
|
description: "Fetches detailed information about a specific transaction.",
|
|
parameters: {
|
|
type: Type.OBJECT,
|
|
properties: {
|
|
tx_hash: {
|
|
type: Type.STRING,
|
|
description: "The 66-character hexadecimal transaction hash.",
|
|
},
|
|
},
|
|
required: ["tx_hash"],
|
|
},
|
|
},
|
|
{
|
|
name: "check_domain_availability",
|
|
description: "Checks if a .aethex domain name is available for registration.",
|
|
parameters: {
|
|
type: Type.OBJECT,
|
|
properties: {
|
|
domain: {
|
|
type: Type.STRING,
|
|
description: "The domain name to check (without .aethex suffix)",
|
|
},
|
|
},
|
|
required: ["domain"],
|
|
},
|
|
},
|
|
];
|
|
|
|
const executeTool = (name: string, args: Record<string, unknown>): Record<string, unknown> => {
|
|
console.log(`[AI Tool] Executing: ${name}`, args);
|
|
|
|
switch (name) {
|
|
case "get_account_balance": {
|
|
const balance = (Math.random() * 1000000).toFixed(2);
|
|
return {
|
|
balance: `${balance} AETH`,
|
|
address: args.address,
|
|
lastUpdated: new Date().toISOString(),
|
|
};
|
|
}
|
|
case "get_transaction_details": {
|
|
const txHash = args.tx_hash as string;
|
|
return {
|
|
hash: txHash,
|
|
from: txHash?.slice(0, 10) + "...",
|
|
to: "0x" + [...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""),
|
|
value: `${(Math.random() * 100).toFixed(2)} AETH`,
|
|
status: Math.random() > 0.1 ? "Success" : "Failed",
|
|
blockNumber: Math.floor(Math.random() * 1000000),
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
}
|
|
case "check_domain_availability": {
|
|
const domain = args.domain as string;
|
|
const isAvailable = Math.random() > 0.3;
|
|
return {
|
|
domain: `${domain}.aethex`,
|
|
available: isAvailable,
|
|
price: isAvailable ? `${(50 + Math.random() * 50).toFixed(2)} AETH` : null,
|
|
owner: isAvailable ? null : "0x" + [...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""),
|
|
};
|
|
}
|
|
default:
|
|
return { error: `Tool ${name} not found.` };
|
|
}
|
|
};
|
|
|
|
export default async function handler(req: Request, res: Response) {
|
|
if (req.method !== "POST") {
|
|
return res.status(405).json({ error: "Method not allowed" });
|
|
}
|
|
|
|
if (!ai) {
|
|
return res.status(503).json({
|
|
error: "AI service not configured",
|
|
message: "Please ensure the Gemini API key is set up."
|
|
});
|
|
}
|
|
|
|
try {
|
|
const { prompt, history, systemInstruction, useTools } = req.body as ChatRequest;
|
|
|
|
if (!prompt) {
|
|
return res.status(400).json({ error: "Prompt is required" });
|
|
}
|
|
|
|
const tools = useTools ? AETHEX_TOOLS : undefined;
|
|
|
|
const chat: Chat = ai.chats.create({
|
|
model: "gemini-2.5-flash",
|
|
config: {
|
|
tools: tools && tools.length > 0 ? [{ functionDeclarations: tools }] : undefined,
|
|
systemInstruction: systemInstruction,
|
|
},
|
|
history: (history || []).map((msg) => ({
|
|
role: msg.role,
|
|
parts: [{ text: msg.content }],
|
|
})),
|
|
});
|
|
|
|
let response = await chat.sendMessage({ message: prompt });
|
|
let text = response.text;
|
|
|
|
if (response.functionCalls && response.functionCalls.length > 0) {
|
|
const functionCalls = response.functionCalls;
|
|
console.log("[AI] Model requested function calls:", functionCalls);
|
|
|
|
const functionResponseParts = functionCalls.map((fc) => {
|
|
const result = executeTool(fc.name, fc.args as Record<string, unknown>);
|
|
return {
|
|
functionResponse: {
|
|
name: fc.name,
|
|
response: { result },
|
|
},
|
|
};
|
|
});
|
|
|
|
console.log("[AI] Sending tool responses back to model");
|
|
const result2 = await chat.sendMessage({ message: functionResponseParts });
|
|
text = result2.text;
|
|
}
|
|
|
|
if (!text) {
|
|
return res.json({ response: "I was unable to generate a response. Please try again." });
|
|
}
|
|
|
|
return res.json({ response: text });
|
|
} catch (error) {
|
|
console.error("[AI] Chat error:", error);
|
|
return res.status(500).json({
|
|
error: "AI request failed",
|
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
});
|
|
}
|
|
}
|