feat: add image generation support and Chinese language default

This commit is contained in:
zihanjian
2025-12-01 21:12:35 +08:00
parent 69327f63c5
commit 13815cf3ac
5 changed files with 192 additions and 33 deletions

View File

@@ -18,6 +18,7 @@ dependencies = [
"langgraph-api",
"fastapi",
"google-genai",
"pillow",
]

View File

@@ -1,11 +1,62 @@
# mypy: disable - error - code = "no-untyped-def,misc"
import base64
import io
import os
import pathlib
from fastapi import FastAPI, Response
from fastapi import FastAPI, Response, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from google import genai
from google.genai import types
# Define the FastAPI app
app = FastAPI()
# Allow local dev origins (Vite + LangGraph dev)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY is None:
raise ValueError("GEMINI_API_KEY is not set")
image_client = genai.Client(api_key=GEMINI_API_KEY)
# Per request: use Gemini 3 image preview model
IMAGE_MODEL = "models/gemini-3-pro-image-preview"
class ImageRequest(BaseModel):
prompt: str
number_of_images: int = 1
@app.post("/generate_image")
def generate_image(req: ImageRequest):
"""Generate an image for a given prompt and return base64 data URLs."""
try:
response = image_client.models.generate_images(
model=IMAGE_MODEL,
prompt=req.prompt,
config=types.GenerateImagesConfig(number_of_images=req.number_of_images),
)
images = []
for generated_image in response.generated_images:
buffer = io.BytesIO()
generated_image.image.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode("ascii")
images.append(f"data:image/png;base64,{b64}")
if not images:
raise RuntimeError("No image generated")
return {"images": images}
except Exception as exc: # pragma: no cover
raise HTTPException(status_code=500, detail=str(exc)) from exc
def create_frontend_router(build_dir="../frontend/dist"):
"""Creates a router to serve the React frontend.

View File

@@ -1,4 +1,6 @@
import os
import json
import re
from agent.tools_and_schemas import SearchQueryList, Reflection
from dotenv import load_dotenv
@@ -267,17 +269,38 @@ def finalize_answer(state: OverallState, config: RunnableConfig):
)
result = llm.invoke(formatted_prompt)
# Clean potential markdown fences and parse JSON so we return structured content
content = result.content
if isinstance(content, str):
# Strip markdown fences ```json ... ```
cleaned = re.sub(r"^```[a-zA-Z]*\s*|\s*```$", "", content.strip())
try:
parsed = json.loads(cleaned)
except Exception:
parsed = cleaned
content_payload = parsed
else:
content_payload = content
# Replace the short urls with the original urls and add all used urls to the sources_gathered
unique_sources = []
for source in state["sources_gathered"]:
if source["short_url"] in result.content:
result.content = result.content.replace(
source["short_url"], source["value"]
)
if isinstance(content_payload, str) and source["short_url"] in content_payload:
content_payload = content_payload.replace(source["short_url"], source["value"])
unique_sources.append(source)
elif isinstance(content_payload, list):
# if list of page dicts, replace inside detail strings
updated_pages = []
for page in content_payload:
if isinstance(page, dict) and isinstance(page.get("detail"), str):
page_detail = page["detail"].replace(source["short_url"], source["value"])
page = {**page, "detail": page_detail}
updated_pages.append(page)
content_payload = updated_pages
unique_sources.append(source)
return {
"messages": [AIMessage(content=result.content)],
"messages": [AIMessage(content=content_payload)],
"sources_gathered": unique_sources,
}