mirror of
https://github.com/Zippland/NanoComic.git
synced 2026-01-19 09:41:10 +08:00
feat: add image generation support and Chinese language default
This commit is contained in:
@@ -18,6 +18,7 @@ dependencies = [
|
||||
"langgraph-api",
|
||||
"fastapi",
|
||||
"google-genai",
|
||||
"pillow",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user