KaiWu commited on
Commit
ab26a21
·
1 Parent(s): d3b5ef6

feat(app): add lightweight feedback capture

Browse files
Files changed (2) hide show
  1. README.md +1 -0
  2. app.py +83 -5
README.md CHANGED
@@ -23,3 +23,4 @@ Configure these as Hugging Face Space secrets:
23
  ## Runtime Storage
24
 
25
  Set `AIGC_ARTIFACT_ROOT=/data/outputs` for persistent generated models and manifests on Hugging Face Spaces.
 
 
23
  ## Runtime Storage
24
 
25
  Set `AIGC_ARTIFACT_ROOT=/data/outputs` for persistent generated models and manifests on Hugging Face Spaces.
26
+ Feedback is appended to `$AIGC_ARTIFACT_ROOT/feedback/feedback.jsonl`.
app.py CHANGED
@@ -1,5 +1,7 @@
 
1
  import os
2
  import shutil
 
3
  from pathlib import Path
4
  from uuid import uuid4
5
 
@@ -14,6 +16,7 @@ def new_session_state() -> dict:
14
  "session_id": uuid4().hex[:12],
15
  "agent_messages": [],
16
  "chat_messages": [],
 
17
  }
18
 
19
 
@@ -63,13 +66,35 @@ def format_status(result: dict) -> str:
63
  ]).strip()
64
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def submit_message(message, state):
67
  state = state or new_session_state()
68
  text, image_path = parse_user_message(message)
69
  uploaded_image = copy_uploaded_image(image_path, state["session_id"])
70
 
71
  if not text and not uploaded_image:
72
- return state["chat_messages"], state, None, None, "Enter a message or upload an image.", {"text": "", "files": []}
73
 
74
  display_text = text or "Generate a 3D model from the uploaded image."
75
  if uploaded_image:
@@ -87,6 +112,7 @@ def submit_message(message, state):
87
  if result.get("error") and not assistant_text:
88
  assistant_text = result["error"]
89
  state["chat_messages"].append({"role": "assistant", "content": assistant_text})
 
90
  status = format_status(result)
91
  return (
92
  state["chat_messages"],
@@ -95,15 +121,30 @@ def submit_message(message, state):
95
  result.get("download_path"),
96
  status,
97
  {"text": "", "files": []},
 
 
 
98
  )
99
  except Exception as exc:
100
  state["chat_messages"].append({"role": "assistant", "content": f"Failed: {exc}"})
101
- return state["chat_messages"], state, None, None, f"Failed: `{exc}`", {"text": "", "files": []}
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
 
104
  def reset_session():
105
  state = new_session_state()
106
- return [], state, None, None, "New session started.", {"text": "", "files": []}
107
 
108
 
109
  with gr.Blocks(title="AI CAD Agent", fill_height=True) as demo:
@@ -131,15 +172,52 @@ with gr.Blocks(title="AI CAD Agent", fill_height=True) as demo:
131
  preview = gr.Model3D(label="Model Preview", height=560)
132
  download = gr.File(label="Download Latest Model")
133
  status = gr.Markdown("No model generated yet.")
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  composer.submit(
136
  submit_message,
137
  inputs=[composer, state],
138
- outputs=[chatbot, state, preview, download, status, composer],
 
 
 
 
 
 
 
 
 
 
139
  )
140
  clear.click(
141
  reset_session,
142
- outputs=[chatbot, state, preview, download, status, composer],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  )
144
 
145
 
 
1
+ import json
2
  import os
3
  import shutil
4
+ from datetime import datetime
5
  from pathlib import Path
6
  from uuid import uuid4
7
 
 
16
  "session_id": uuid4().hex[:12],
17
  "agent_messages": [],
18
  "chat_messages": [],
19
+ "latest_tool_payload": None,
20
  }
21
 
22
 
 
66
  ]).strip()
67
 
68
 
69
+ def write_feedback(state: dict, rating: str, comment: str) -> Path:
70
+ payload = state.get("latest_tool_payload") or {}
71
+ feedback_dir = ARTIFACT_ROOT / "feedback"
72
+ feedback_dir.mkdir(parents=True, exist_ok=True)
73
+ feedback_path = feedback_dir / "feedback.jsonl"
74
+
75
+ record = {
76
+ "timestamp": datetime.now().astimezone().isoformat(timespec="seconds"),
77
+ "session_id": state.get("session_id"),
78
+ "run_id": payload.get("run_id"),
79
+ "rating": rating,
80
+ "comment": comment.strip(),
81
+ "output_path": payload.get("output_path"),
82
+ "manifest_path": payload.get("manifest_path"),
83
+ }
84
+
85
+ with feedback_path.open("a", encoding="utf-8") as handle:
86
+ handle.write(json.dumps(record, ensure_ascii=False) + "\n")
87
+
88
+ return feedback_path
89
+
90
+
91
  def submit_message(message, state):
92
  state = state or new_session_state()
93
  text, image_path = parse_user_message(message)
94
  uploaded_image = copy_uploaded_image(image_path, state["session_id"])
95
 
96
  if not text and not uploaded_image:
97
+ return state["chat_messages"], state, None, None, "Enter a message or upload an image.", {"text": "", "files": []}, None, "", ""
98
 
99
  display_text = text or "Generate a 3D model from the uploaded image."
100
  if uploaded_image:
 
112
  if result.get("error") and not assistant_text:
113
  assistant_text = result["error"]
114
  state["chat_messages"].append({"role": "assistant", "content": assistant_text})
115
+ state["latest_tool_payload"] = result.get("tool_payload")
116
  status = format_status(result)
117
  return (
118
  state["chat_messages"],
 
121
  result.get("download_path"),
122
  status,
123
  {"text": "", "files": []},
124
+ None,
125
+ "",
126
+ "",
127
  )
128
  except Exception as exc:
129
  state["chat_messages"].append({"role": "assistant", "content": f"Failed: {exc}"})
130
+ return state["chat_messages"], state, None, None, f"Failed: `{exc}`", {"text": "", "files": []}, None, "", ""
131
+
132
+
133
+ def submit_feedback(rating, comment, state):
134
+ state = state or new_session_state()
135
+ payload = state.get("latest_tool_payload") or {}
136
+ if not payload.get("run_id"):
137
+ return "Generate a model before submitting feedback.", comment or ""
138
+ if not rating:
139
+ return "Choose satisfied or unsatisfied before submitting feedback.", comment or ""
140
+
141
+ feedback_path = write_feedback(state, rating, comment or "")
142
+ return f"Feedback saved to `{feedback_path}`.", ""
143
 
144
 
145
  def reset_session():
146
  state = new_session_state()
147
+ return [], state, None, None, "New session started.", {"text": "", "files": []}, None, "", ""
148
 
149
 
150
  with gr.Blocks(title="AI CAD Agent", fill_height=True) as demo:
 
172
  preview = gr.Model3D(label="Model Preview", height=560)
173
  download = gr.File(label="Download Latest Model")
174
  status = gr.Markdown("No model generated yet.")
175
+ gr.Markdown("### Feedback")
176
+ feedback_rating = gr.Radio(
177
+ choices=["满意", "不满意"],
178
+ label="How was this result?",
179
+ )
180
+ feedback_comment = gr.Textbox(
181
+ label="Optional notes",
182
+ placeholder="What worked, what failed, or what you wanted instead.",
183
+ lines=3,
184
+ )
185
+ feedback_submit = gr.Button("Submit Feedback")
186
+ feedback_status = gr.Markdown("")
187
 
188
  composer.submit(
189
  submit_message,
190
  inputs=[composer, state],
191
+ outputs=[
192
+ chatbot,
193
+ state,
194
+ preview,
195
+ download,
196
+ status,
197
+ composer,
198
+ feedback_rating,
199
+ feedback_comment,
200
+ feedback_status,
201
+ ],
202
  )
203
  clear.click(
204
  reset_session,
205
+ outputs=[
206
+ chatbot,
207
+ state,
208
+ preview,
209
+ download,
210
+ status,
211
+ composer,
212
+ feedback_rating,
213
+ feedback_comment,
214
+ feedback_status,
215
+ ],
216
+ )
217
+ feedback_submit.click(
218
+ submit_feedback,
219
+ inputs=[feedback_rating, feedback_comment, state],
220
+ outputs=[feedback_status, feedback_comment],
221
  )
222
 
223