新增画布对话助手
This commit is contained in:
2
main.py
Normal file → Executable file
2
main.py
Normal file → Executable 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
121
src/routers/canvas_assistant.py
Executable 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
31
src/schemas/canvas_assistant.py
Executable 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"
|
||||
}
|
||||
}
|
||||
0
src/server/canvas_assistant/__init__.py
Executable file
0
src/server/canvas_assistant/__init__.py
Executable file
74
src/server/canvas_assistant/graph.py
Executable file
74
src/server/canvas_assistant/graph.py
Executable 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 Edit(Canvas 模式) 🪡 如果你对材质有具体想法,可以用这个模式来做。布艺、皮革、木材这些都可以换,也可以把你自己的印花上传进来。这个模式支持你手动精细编辑,想细调哪里都可以。",
|
||||
"surface_edit_ai_zh": "Surface Edit(AI 模式) 🪡 想快速看看换材质之后的效果?用这个模式,把你想要的材质或印花告诉我,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()
|
||||
Reference in New Issue
Block a user