新增画布对话助手

This commit is contained in:
zcr
2026-04-01 15:41:27 +08:00
parent 0890241cb1
commit 3f34bb005c
5 changed files with 228 additions and 0 deletions

2
main.py Normal file → Executable file
View File

@@ -9,6 +9,7 @@ from src.routers import deep_agent_chat
from src.routers import generate_3D
from src.routers import flux2_gen_img
from src.routers import seg_furniture
from src.routers import canvas_assistant
logging.config.dictConfig(LOGGER_CONFIG_DICT)
@@ -31,6 +32,7 @@ app_server.include_router(deep_agent_chat.router)
app_server.include_router(generate_3D.router)
app_server.include_router(flux2_gen_img.router)
app_server.include_router(seg_furniture.router)
app_server.include_router(canvas_assistant.router)
@app_server.get("/")

121
src/routers/canvas_assistant.py Executable file
View File

@@ -0,0 +1,121 @@
import json
import logging
from typing import AsyncGenerator
import uuid
from fastapi import APIRouter
from starlette.responses import StreamingResponse
from src.schemas.canvas_assistant import TriggerRequest
from src.server.canvas_assistant.graph import graph
router = APIRouter(prefix="/canvas", tags=["Furniture Canvas assistant"])
logger = logging.getLogger(__name__)
async def stream_fiphant(req: TriggerRequest) -> AsyncGenerator[str, None]:
thread_id = f"canvas_{str(uuid.uuid4())}"
config = {"configurable": {"thread_id": thread_id}}
input_state = {
"messages": [],
"trigger": req.tool_name if req.action == "tool_trigger" else None,
"language": req.language,
"is_first_enter": req.action == "enter_canvas"
}
yield f"data: {json.dumps({'thread_id': thread_id, 'status': 'start'}, ensure_ascii=False)}\n\n"
async for event in graph.astream(input_state, config, stream_mode="messages", version="v2"):
if event["type"] == "messages":
msg, metadata = event["data"]
payload_out = {
"node": metadata.get("langgraph_node", "unknown"),
"is_delta": True,
"content": msg.content,
"type": "text"
}
yield f"data: {json.dumps(payload_out, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'status': 'end'}, ensure_ascii=False)}\n\n"
@router.post("/assistant")
async def canvas_assistant(req: TriggerRequest):
"""
### 接口说明
触发 Fiphant 设计助手返回消息。
支持两种场景:
- 用户进入画布时,自动返回欢迎引导语
- 用户点击画布中的工具按钮时,返回对应工具的使用说明
### 参数说明:
- **action**: 操作类型(必填)
- `enter_canvas`: 用户进入画布时调用(返回欢迎语)
- `tool_trigger`: 用户点击工具按钮时调用(返回工具说明)
- **tool_name**: 工具名称(当 action 为 tool_trigger 时必填)
支持以下值:
- `to_real_style`
- `surface_edit_canvas`
- `surface_edit_ai`
- `color_palette`
- `scene_composition`
- `3d_model`
- `to_3d_view`
- **language**: 返回语言(非必填,默认 zh
- `zh`: 中文
- `en`: 英文
### 请求体示例:
**1. 进入画布时调用**
```json
{
"action": "enter_canvas",
"language": "zh"
}
```
2. 点击工具时调用(推荐)
```JSON
{
"action": "tool_trigger",
"tool_name": "3d_model",
"language": "zh"
}
```
3. 点击工具 - 英文版
```JSON
{
"action": "tool_trigger",
"tool_name": "scene_composition",
"language": "en"
}
```
输出说明:
返回 Server-Sent Events (SSE) 流式响应,文字会逐句出现,提升用户体验。
流式输出示例(实际返回内容):
消息开始结束:
```json
{
"thread_id": "canvas_be76cb75-18ef-4e84-8e30-5d36aef5b83a",
"status": "start"
}
{
"status": "end"
}
```
正文消息:
```json
{
"node": "assistant",
"is_delta": true,
"content": "Hi我是你的设计助手 Fiphant 👋 我来帮你快速上手这个画布。我给你准备了两个起点——你可以用 To Real Style 直接把草图变成效果图,也可以先用 Surface Edit 换个材质或贴上印花。有了产品图之后,我们再一起配色、配场景、看 3D 效果,最后导出三视图就完成了。我建议先从 To Real Style 开始,看看整体感觉 ✨",
"type": "text"
}
```
"""
return StreamingResponse(stream_fiphant(req), media_type="text/event-stream")

31
src/schemas/canvas_assistant.py Executable file
View File

@@ -0,0 +1,31 @@
from typing import Literal
from pydantic import BaseModel, Field
# ====================== 请求模型 ======================
class TriggerRequest(BaseModel):
action: Literal["enter_canvas", "tool_trigger"] = Field(
...,
description="操作类型enter_canvas = 进入画布tool_trigger = 点击工具"
)
tool_name: str | None = Field(
None,
description="当 action=tool_trigger 时必填。支持的工具to_real_style, surface_edit_canvas, surface_edit_ai, color_palette, scene_composition, 3d_model, to_3d_view"
)
language: Literal["zh", "en"] = Field(
"zh",
description="返回语言zh=中文en=英文"
)
class Config:
json_schema_extra = {
"example": {
"session_id": "canvas_20260331_001",
"action": "tool_trigger",
"tool_name": "3d_model",
"language": "zh"
}
}

View File

View File

@@ -0,0 +1,74 @@
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import MessagesState
from langchain_core.messages import AIMessage
# ====================== 中英文固定文案 ======================
PROMPTS = {
# 中文
"welcome_zh": "Hi我是你的设计助手 Fiphant 👋 我来帮你快速上手这个画布。我给你准备了两个起点——你可以用 To Real Style 直接把草图变成效果图,也可以先用 Surface Edit 换个材质或贴上印花。有了产品图之后,我们再一起配色、配场景、看 3D 效果,最后导出三视图就完成了。我建议先从 To Real Style 开始,看看整体感觉 ✨",
"to_real_style_zh": "To Real Style 🎨 这个功能我很喜欢——你只需要把草图丢进来,我来帮你把光影和材质都处理好,直接生成真实感效果图。出来不满意的话就多试几次,每次我都会给你不一样的结果。",
"surface_edit_canvas_zh": "Surface EditCanvas 模式) 🪡 如果你对材质有具体想法,可以用这个模式来做。布艺、皮革、木材这些都可以换,也可以把你自己的印花上传进来。这个模式支持你手动精细编辑,想细调哪里都可以。",
"surface_edit_ai_zh": "Surface EditAI 模式) 🪡 想快速看看换材质之后的效果用这个模式把你想要的材质或印花告诉我AI 智能贴图帮你一步到位。如果觉得还想再调整细节,随时可以切换到 Canvas 模式继续编辑。",
"color_palette_zh": "Color Palette 🎨 配色交给我来帮你——你选几种喜欢的颜色,我来帮你搭配应用到产品上。我可以一次给你生成好几个方案,你对比着挑就好。",
"scene_composition_zh": "Scene Composition 🛋️ 我来帮你把产品放进一个真实的空间场景里,光影我会自动帮你匹配。我的建议是先出一张背景干净的主图,再出一张有生活感的氛围图,两张配合着用,展示效果会好很多。",
"3d_model_zh": "3D Model 🔄 我把你的效果图变成可以转着看的立体模型,你可以从各个角度检查一下结构。我建议重点看看转角、腿脚比例和座面厚度——这几个地方在草图里不容易发现问题,但打样的时候最容易出偏差,现在发现比较好改。",
"to_3d_view_zh": "To 3D View 📐 我们到最后一步了!我来帮你把 3D 模型导出为前视图、侧视图和俯视图!",
# English
"welcome_en": "Hi, I'm Fiphant, your design assistant 👋 I'm here to help you get started with the canvas. My suggestion: start with To Real Style ✨",
"to_real_style_en": "To Real Style 🎨 This is one of my favorite features — just drop in your sketch and I'll handle the lighting and materials to create a photorealistic render.",
"surface_edit_canvas_en": "Surface Edit (Canvas Mode) 🪡 Perfect for precise material control. You can swap fabrics, leather, wood, or upload your own prints.",
"surface_edit_ai_en": "Surface Edit (AI Mode) 🪡 Want quick material preview? Tell me what you want and I'll apply it instantly with AI.",
"color_palette_en": "Color Palette 🎨 Let me handle the colors for you. Pick your favorite colors and I'll generate multiple schemes.",
"scene_composition_en": "Scene Composition 🛋️ I'll place your product in a real scene with matching lighting.",
"3d_model_en": "3D Model 🔄 I'll turn your render into a rotatable 3D model. Check corners, leg proportions, and seat thickness carefully.",
"to_3d_view_en": "To 3D View 📐 Final step! I'll export your 3D model as front, side, and top views."
}
class AgentState(MessagesState):
trigger: str | None = None
language: str = "zh"
is_first_enter: bool = True # 新增标志位,控制是否输出欢迎语
# ==================== Nodes ====================
def assistant_node(state: AgentState):
"""路由节点:判断是进入画布还是点击工具"""
if state.get("is_first_enter", True):
# 第一次进入画布
lang = state.get("language", "zh")
key = f"welcome_{lang}"
content = PROMPTS.get(key, PROMPTS["welcome_zh"])
return {
"messages": [AIMessage(content=content)],
"is_first_enter": False
}
else:
# 点击工具
trigger = state.get("trigger")
lang = state.get("language", "zh")
if trigger:
key = f"{trigger}_{lang}"
content = PROMPTS.get(key, "功能说明加载中...")
else:
content = "请点击工具让我为你说明用法。" if lang == "zh" else "Please click a tool for instructions."
return {
"messages": [AIMessage(content=content)],
"is_first_enter": False
}
# ==================== Graph ====================
workflow = StateGraph(state_schema=AgentState)
workflow.add_node("assistant", assistant_node)
workflow.add_edge(START, "assistant")
workflow.add_edge("assistant", END)
graph = workflow.compile()