FrankChang-TBLA commited on
Commit
77fe570
·
verified ·
1 Parent(s): b19d5b4

improve-readaility (#7)

Browse files

- consistent tabs (2220afd02d3a2de619a8cbe7efdeb0c8ba048f3d)
- replace config if else with dict (54ed6ad2f0adfad3fc1081f1a2355dacd7de4f00)
- fix indent (4dddbd27509ed7e2729063b1bf2da514e0e40776)
- add git ignore (92267b3f2ee427e086d2ef116c4a5896e1e44c38)
- migrate from app.py to app/main.py (6e0932f7bf878e209d5eccf0d8e95f2d425233cc)
- use app.py as entrypoint, to keep the launching command of gradio app.py (602f944eed88c6ef6e92adb3aa1dd356537c0eca)

.gitignore ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ env/
26
+ ENV/
27
+ .env
28
+ .venv
29
+ env.bak/
30
+ venv.bak/
31
+
32
+ # IDE
33
+ .idea/
34
+ .vscode/
35
+ *.swp
36
+ *.swo
37
+ .DS_Store
38
+ .project
39
+ .pydevproject
40
+ .settings/
41
+
42
+ # Jupyter Notebook
43
+ .ipynb_checkpoints
44
+ *.ipynb
45
+
46
+ # Testing
47
+ .coverage
48
+ htmlcov/
49
+ .pytest_cache/
50
+ .tox/
51
+ .nox/
52
+ coverage.xml
53
+ *.cover
54
+ .hypothesis/
55
+
56
+ # Logs
57
+ *.log
58
+ logs/
59
+ log/
60
+
61
+ # Local development
62
+ .env.local
63
+ .env.development.local
64
+ .env.test.local
65
+ .env.production.local
66
+
67
+ # Distribution / packaging
68
+ .Python
69
+ *.manifest
70
+ *.spec
71
+
72
+ # Installer logs
73
+ pip-log.txt
74
+ pip-delete-this-directory.txt
75
+
76
+ # Unit test / coverage reports
77
+ .coverage
78
+ .coverage.*
79
+ .cache
80
+ nosetests.xml
81
+ coverage.xml
82
+ *.cover
83
+ .hypothesis/
84
+ .pytest_cache/
85
+
86
+ # mypy
87
+ .mypy_cache/
88
+ .dmypy.json
89
+ dmypy.json
90
+
91
+ # Rope project settings
92
+ .ropeproject
93
+
94
+ # mkdocs documentation
95
+ /site
96
+
97
+ # Sphinx documentation
98
+ docs/_build/
app.py CHANGED
@@ -1,460 +1,4 @@
1
- '''
2
- Note
3
- - DO NOT create an assistant every time! UPDATE through assistant_id.
4
- '''
5
-
6
- '''
7
- References
8
- - https://github.com/openai/openai-cookbook/blob/main/examples/Assistants_API_overview_python.ipynb
9
- '''
10
-
11
-
12
-
13
- from openai import OpenAI
14
-
15
- import os
16
- import json
17
-
18
- import gradio as gr
19
- from json_repair import repair_json
20
- import json_repair # enable streaming
21
-
22
- from chat_history_manager import ChatHistoryManager
23
- from diff_text import compare_text, extract_modified_sections
24
-
25
- # Initialize OpenAI client
26
- OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
27
- client = OpenAI(api_key=OPENAI_API_KEY)
28
-
29
- APP_NAME = os.getenv('app_name') # 'NCSLM_LPD', 'SEL', 'SEL_COACH'
30
- # App name enum
31
- class AppName:
32
- NCSLM_LPD = 'NCSLM_LPD'
33
- SEL = 'SEL'
34
- SEL_COACH = 'SEL_COACH'
35
-
36
- # Import configuration files with different aliases
37
- if APP_NAME == AppName.NCSLM_LPD:
38
- from NCSLM_LPD import assistant_config as ncslm_config
39
- elif APP_NAME == AppName.SEL:
40
- from SEL import assistant_config as sel_config
41
- elif APP_NAME == AppName.SEL_COACH:
42
- from SEL_COACH import assistant_config as sel_coach_config
43
- else:
44
- # Fallback or default config
45
- import assistant_config as default_config
46
-
47
- # Use the appropriate config based on APP_NAME
48
- if APP_NAME == AppName.NCSLM_LPD:
49
- active_config = ncslm_config
50
- elif APP_NAME == AppName.SEL:
51
- active_config = sel_config
52
- elif APP_NAME == AppName.SEL_COACH:
53
- active_config = sel_coach_config
54
- else:
55
- active_config = default_config
56
-
57
- # Initialize chat history manager
58
- chat_manager = ChatHistoryManager()
59
-
60
- # View existing assistants
61
- existed_assistants = client.beta.assistants.list(
62
- order="desc",
63
- limit="20",
64
- )
65
- print(len(existed_assistants.data),existed_assistants.data)
66
-
67
- # Assistant setting (Playground: https://platform.openai.com/playground/assistants)
68
-
69
- # Record once created
70
- assistant_id = os.getenv('assistant_id')
71
-
72
- ASSISTANT_MODEL = active_config.ASSISTANT_MODEL
73
- ASSISTANT_NAME = active_config.ASSISTANT_NAME
74
- ASSISTANT_DESCRIPTION = active_config.ASSISTANT_DESCRIPTION
75
- ASSISTANT_INSTRUCTION = active_config.ASSISTANT_INSTRUCTION
76
-
77
- RESPONSE_FORMAT = active_config.RESPONSE_FORMAT
78
-
79
- VECTOR_STORE_NAME = "lesson-plan"
80
- SHOW_CANVAS = active_config.SHOW_CANVAS
81
-
82
-
83
- CONVERSATION_STARTER_SAMPLES = active_config.CONVERSATION_STARTER_SAMPLES
84
-
85
- def show_json(obj):
86
- print(json.loads(obj.model_dump_json()))
87
-
88
- def print_assistant_info(assistant):
89
- """Print key information about an assistant object."""
90
- # Get model
91
- model = assistant.model
92
-
93
- # Get description (limited to 2 lines)
94
- desc_lines = assistant.description.split('\n')[:2]
95
- desc = '\n'.join(desc_lines)
96
- if len(desc) > 150: # Truncate if too long
97
- desc = desc[:147] + "..."
98
-
99
- # Get instructions (limited to 2 lines)
100
- instr_lines = assistant.instructions.split('\n')[:2]
101
- instr = '\n'.join(instr_lines)
102
- if len(instr) > 150: # Truncate if too long
103
- instr = instr[:147] + "..."
104
-
105
- # Print information
106
- print(f"Assistant ID: {assistant.id}")
107
- print(f"Name: {assistant.name}")
108
- print(f"Model: {model}")
109
- print(f"Description: {desc}")
110
- print(f"Instructions: {instr}")
111
- print(f"Tools: {[tool.type for tool in assistant.tools]}")
112
- print(f"Response Format: {assistant.response_format}")
113
- print(f"Created at: {assistant.created_at}")
114
-
115
-
116
-
117
-
118
- # Retrieve assistant
119
- try:
120
- assistant = client.beta.assistants.retrieve(assistant_id)
121
- print(f"retrieve assistant success.")
122
- print_assistant_info(assistant)
123
- # update_assistant(assistant_id)
124
- except Exception as e:
125
- print(f"retrieve Assistant Fail: {e}")
126
-
127
-
128
-
129
-
130
- # Enable assistant streaming event handler (openai template)
131
- from typing_extensions import override
132
- from openai import AssistantEventHandler, OpenAI
133
- from openai.types.beta.threads import Text, TextDelta
134
- from openai.types.beta.threads.runs import ToolCall, ToolCallDelta
135
-
136
- class EventHandler(AssistantEventHandler):
137
- @override
138
- def on_text_created(self, text: Text) -> None:
139
- print(f"\nassistant > ", end="", flush=True)
140
-
141
- @override
142
- def on_text_delta(self, delta: TextDelta, snapshot: Text):
143
- print(delta.value, end="", flush=True)
144
-
145
- @override
146
- def on_tool_call_created(self, tool_call: ToolCall):
147
- print(f"\nassistant > {tool_call.type}\n", flush=True)
148
-
149
-
150
-
151
- # Components
152
- chatbot = gr.Chatbot(
153
- type="messages",
154
- container=True,
155
- scale=8,
156
- # height="50%",
157
- # max_height="650px",
158
- # elem_classes="full-height",
159
- autoscroll=True
160
- )
161
- textbox = gr.Textbox( # Canvas
162
- label="教案編輯",
163
- lines=30,
164
- render=False,
165
- interactive=True,
166
- scale=1
167
- )
168
- prompt_input = gr.Textbox( # User prompt
169
- label="用戶需求",
170
- submit_btn=True,
171
- render=False,
172
- scale=1
173
- )
174
- quick_response = gr.Dataset( # Suggested user prompt
175
- samples=CONVERSATION_STARTER_SAMPLES,
176
- components=[prompt_input],
177
- render=False,
178
- scale=1
179
- )
180
- hidden_list = gr.JSON(
181
- value=[[]],
182
- render=False,
183
- visible=False
184
- )
185
- chat_selector = gr.Dropdown(
186
- label="Select Chat",
187
- choices=[],
188
- interactive=True,
189
- container=True,
190
- scale=0
191
- )
192
- new_chat_btn = gr.Button(
193
- "New Chat",
194
- size="sm",
195
- )
196
-
197
- # Todo
198
- # Handle new chat creation
199
- def create_new_chat():
200
- """Create a new chat using OpenAI thread ID as chat_id"""
201
- thread = client.beta.threads.create()
202
- chat_id = thread.id # Use thread ID as chat ID
203
- handle_response.current_chat_id = chat_id
204
- chats = list_user_chats()
205
- return (
206
- gr.update(choices=[(c["preview"], c["chat_id"]) for c in chats], value=chat_id),
207
- [] # Clear the chatbot
208
- )
209
-
210
- def get_or_create_thread(chat_id="thread_dTU1oB9qxn8UTejS4TcujSsF"):
211
- """
212
- Get existing thread or create a new one.
213
-
214
- Args:
215
- chat_id (str, optional): Existing thread ID. Defaults to None.
216
-
217
- Returns:
218
- tuple: (thread, chat_id)
219
- """
220
- try:
221
- if chat_id:
222
- # Try to retrieve existing thread
223
- thread = client.beta.threads.retrieve(chat_id)
224
- else:
225
- # Create new thread if no chat_id
226
- thread = client.beta.threads.create()
227
- chat_id = thread.id
228
- # print(f"\n\n\nNew Thread {chat_id} created\n\n\n")
229
- return thread, chat_id
230
- except Exception as e:
231
- print(f"Error in thread management: {str(e)}")
232
- # Create new thread as fallback
233
- thread = client.beta.threads.create()
234
- return thread, thread.id
235
-
236
- def handle_response(message, history, textbox_content, username):
237
- # Get or create chat_id (which is the thread_id)
238
- chat_id = getattr(handle_response, 'current_chat_id', None)
239
- thread, chat_id = get_or_create_thread(chat_id)
240
- handle_response.current_chat_id = chat_id
241
-
242
- preprocessed_data = active_config.handle_thread_before_chat(client, chat_id, message, history, textbox_content)
243
-
244
- full_response = ""
245
- canvas_result = ""
246
- chat_response = ""
247
- next_step_prompt = [[]]
248
- prompt_tokens = 0
249
- total_tokens = 0
250
- try:
251
- # Print chat_id and assistant_id for debugging
252
- print(f"Processing request with chat_id: {chat_id}")
253
- with client.beta.threads.runs.stream(
254
- thread_id=chat_id,
255
- assistant_id=assistant_id,
256
- timeout=60 # Add timeout in seconds
257
- ) as stream:
258
- try:
259
- # For debugging, we need to print the original stream_delta instead of just the text_delta
260
- for stream_delta in stream:
261
- # Skip if empty response
262
- if not stream_delta:
263
- print("Warning: Empty text delta received", flush=True)
264
- continue
265
-
266
- if stream_delta.event == 'thread.run.failed':
267
- print(f"Stream delta received: {stream_delta}", flush=True)
268
- if hasattr(stream_delta.data, 'last_error') and stream_delta.data.last_error is not None:
269
- chat_response = stream_delta.data.last_error.model_dump_json()
270
- else:
271
- chat_response = "Sorry, there was an error processing the response. Please try again."
272
- continue
273
- elif stream_delta.event != 'thread.message.delta':
274
- # Debug: Print stream response if it's not a TextDelta
275
- print(f"Stream delta received: {stream_delta}", flush=True)
276
-
277
- # Check for token usage in thread.run.step.completed events
278
- if stream_delta.event == 'thread.run.step.completed' and hasattr(stream_delta, 'data'):
279
- if hasattr(stream_delta.data, 'usage'):
280
- prompt_tokens = stream_delta.data.usage.prompt_tokens
281
- total_tokens = stream_delta.data.usage.total_tokens
282
- print(f"\nPrompt tokens: {prompt_tokens}, Total tokens: {total_tokens}\n", flush=True)
283
-
284
- # Skip other event types (like ThreadRunCreated)
285
- continue
286
- if stream_delta.data.delta.content[0].text:
287
- full_response += stream_delta.data.delta.content[0].text.value
288
-
289
- # Only process TextDelta events
290
- # for text in stream.text_deltas:
291
- # full_response += text
292
-
293
- # Debug: Print accumulated response length
294
- print(f"Accumulated response length: {len(full_response)}", flush=True)
295
-
296
- # Skip if response is too short to be valid JSON
297
- if len(full_response) < 2:
298
- continue
299
- chat_response, canvas_result, next_step_prompt = active_config.handle_stream_delta(full_response)
300
-
301
- yield chat_response, canvas_result, next_step_prompt
302
- except Exception as stream_error:
303
- print(f"Stream processing error: {str(stream_error)}")
304
- print(f"Error type: {type(stream_error).__name__}")
305
- yield "Sorry, there was an error processing the response. Please try again.", "", [[]]
306
- except Exception as connection_error:
307
- print(f"Connection error: {str(connection_error)}")
308
- print(f"Error type: {type(connection_error).__name__}")
309
- yield "Sorry, the connection timed out. Please try again.", "", [[]]
310
-
311
- print(f"Current Messages: {message}")
312
- print(f"Suggestion: {chat_response}")
313
- print(f"Current Response: {full_response}")
314
- print(f"Prompt tokens: {prompt_tokens}, Total tokens: {total_tokens}")
315
-
316
- final_chat_response, final_canvas_result, final_next_step_prompt, msg_records = active_config.handle_stream_end(message, history, chat_response, textbox_content, full_response, canvas_result, preprocessed_data)
317
-
318
- chat_manager.save_chat(username, chat_id, msg_records, canvas_result, APP_NAME)
319
-
320
- yield final_chat_response, final_canvas_result, final_next_step_prompt
321
-
322
-
323
- def handle_quick_response_click(selected):
324
- return selected[0]
325
-
326
- def handle_quick_response_samples(next_step_prompt):
327
- if len(next_step_prompt) > 0 and len(next_step_prompt[0]) > 0:
328
- return gr.Dataset(samples=next_step_prompt,visible=True)
329
- return gr.Dataset(samples=[['-']],visible=False)
330
-
331
- CORRECT_PASSWORD = os.getenv('ui_password')
332
-
333
- def check_password(username, input_password):
334
- if input_password == CORRECT_PASSWORD:
335
- return gr.update(visible=False), gr.update(visible=True), "", username
336
- else:
337
- return gr.update(visible=True), gr.update(visible=False), gr.update(value="密码错误,请重试。hint: channel name", visible=True), username
338
-
339
- def load_chat_history(user_id="default_user"):
340
- chat_id = chat_manager.get_latest_chat_id(user_id)
341
- if chat_id:
342
- return chat_manager.load_chat(user_id, chat_id)
343
- return []
344
-
345
- def list_user_chats(user_id="default_user"):
346
- return chat_manager.list_chats(user_id)
347
-
348
- def switch_chat(chat_id, user_id="default_user"):
349
- handle_response.current_chat_id = chat_id
350
- messages = chat_manager.load_chat(user_id, chat_id)
351
- return messages
352
-
353
- def clear_input():
354
- return gr.update(value="")
355
-
356
- # Handle chat submission
357
- def chat_submit(message, history, textbox, username):
358
- if message:
359
- msg_records = [{'role': msg['role'], 'content': msg['content']} for msg in history]
360
- msg_records.append({'role': 'user', 'content': message})
361
- response_generator = handle_response(message, history, textbox, username)
362
- for suggestion, current_lesson_plan, next_step_prompt in response_generator:
363
- yield msg_records + [{'role': 'assistant', 'content': suggestion}], current_lesson_plan, [[]]
364
- yield msg_records + [{'role': 'assistant', 'content': suggestion}], current_lesson_plan, next_step_prompt
365
-
366
-
367
- # Reset chat ID on page load
368
- def reset_chat_on_load(username):
369
- thread = client.beta.threads.create()
370
- chat_id = thread.id
371
- handle_response.current_chat_id = chat_id
372
-
373
- # Update chat selector with available chats for this user
374
- chats = list_user_chats(username) # Pass username
375
- return gr.update(choices=[(c["preview"], c["chat_id"]) for c in chats], value=chat_id), []
376
-
377
- # with gr.Blocks(css=" .contain {height: 100vh}") as demo:
378
- with gr.Blocks(css="""
379
- .full-height {height: 100%}
380
- """) as demo:
381
- # password UI popup
382
- with gr.Group(visible=True) as password_popup:
383
- username_input = gr.Textbox(label="請輸入使用者名稱")
384
- password_input = gr.Textbox(label="請輸入密碼", type="password")
385
- submit_button = gr.Button("提交")
386
- error_message = gr.Textbox(label="", visible=False, interactive=False)
387
-
388
- # Store username in state
389
- user_state = gr.State(value="default_user")
390
-
391
- # Main UI
392
- with gr.Group(visible=False) as main_ui:
393
- with gr.Row(equal_height=True, elem_classes="contain"):
394
- with gr.Column(scale=1, min_width=200, render=False):
395
- chat_selector.render()
396
- # new_chat_btn.render()
397
- with gr.Column(scale=4):
398
- with gr.Row(equal_height=True, height="90vh"):
399
- with gr.Column():
400
- chatbot.render()
401
- prompt_input.render()
402
- quick_response.render()
403
- hidden_list.render()
404
- with gr.Column(elem_classes="full-height", visible=SHOW_CANVAS) as textbox_column:
405
- textbox.render()
406
-
407
- # submit button event
408
- submit_button.click(
409
- check_password,
410
- inputs=[username_input, password_input],
411
- outputs=[password_popup, main_ui, error_message, user_state]
412
- )
413
- # password input submit event (click enter)
414
- password_input.submit(
415
- check_password,
416
- inputs=[username_input, password_input],
417
- outputs=[password_popup, main_ui, error_message, user_state]
418
- )
419
-
420
- prompt_input.submit(
421
- chat_submit,
422
- inputs=[prompt_input, chatbot, textbox, user_state], # Add user_state
423
- outputs=[chatbot, textbox, hidden_list]
424
- ).then(
425
- clear_input,
426
- outputs=prompt_input
427
- )
428
-
429
- quick_response.click(
430
- handle_quick_response_click,
431
- quick_response,
432
- prompt_input
433
- )
434
-
435
- hidden_list.change(handle_quick_response_samples, hidden_list, quick_response)
436
-
437
- # Update chat selector when page loads
438
- demo.load(
439
- reset_chat_on_load,
440
- inputs=[user_state], # Add user_state
441
- outputs=[chat_selector, chatbot]
442
- )
443
-
444
- # Handle chat switching
445
- chat_selector.change(
446
- switch_chat,
447
- inputs=[chat_selector, user_state], # Add user_state
448
- outputs=[chatbot] # Update the chatbot component instead of ChatInterface
449
- )
450
-
451
- new_chat_btn.click(
452
- create_new_chat,
453
- outputs=[chat_selector, chatbot] # Update selector and chatbot
454
- )
455
-
456
- demo.launch(debug=True)
457
-
458
-
459
-
460
 
 
 
 
1
+ from app.main import demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ if __name__ == "__main__":
4
+ demo.launch()
app/__init__.py ADDED
File without changes
app/config/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .base import get_app_name_and_config, AppName, REQUIRED_ATTRIBUTES, CONFIG_MAPPING
2
+
3
+ __all__ = ['get_app_name_and_config', 'AppName', 'REQUIRED_ATTRIBUTES', 'CONFIG_MAPPING']
app/config/base.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Type, Set
2
+ import importlib
3
+ import os
4
+
5
+ class AppName:
6
+ """Enum for application names."""
7
+ NCSLM_LPD = 'NCSLM_LPD'
8
+ SEL = 'SEL'
9
+ SEL_COACH = 'SEL_COACH'
10
+
11
+ # Define required attributes that must be present in config
12
+ REQUIRED_ATTRIBUTES: Set[str] = {
13
+ 'ASSISTANT_MODEL',
14
+ 'ASSISTANT_NAME',
15
+ 'ASSISTANT_DESCRIPTION',
16
+ 'ASSISTANT_INSTRUCTION',
17
+ 'RESPONSE_FORMAT',
18
+ 'SHOW_CANVAS',
19
+ 'CONVERSATION_STARTER_SAMPLES'
20
+ }
21
+
22
+ # Mapping of app names to their config module paths
23
+ CONFIG_MAPPING: Dict[str, str] = {
24
+ AppName.NCSLM_LPD: 'NCSLM_LPD.assistant_config',
25
+ AppName.SEL: 'SEL.assistant_config',
26
+ AppName.SEL_COACH: 'SEL_COACH.assistant_config',
27
+ 'default': 'assistant_config'
28
+ }
29
+
30
+ def validate_config(config: Type) -> tuple[bool, list[str]]:
31
+ """
32
+ Validate that the config has all required attributes.
33
+
34
+ Args:
35
+ config: The configuration module to validate
36
+
37
+ Returns:
38
+ tuple[bool, list[str]]: (is_valid, list of missing attributes)
39
+ """
40
+ missing_attrs = [attr for attr in REQUIRED_ATTRIBUTES if not hasattr(config, attr)]
41
+ return len(missing_attrs) == 0, missing_attrs
42
+
43
+ def get_app_name_and_config() -> tuple[str, Type]:
44
+ """
45
+ Get the appropriate configuration module based on APP_NAME.
46
+ Validates that the config has all required attributes.
47
+
48
+ Returns:
49
+ Type: The configuration module
50
+
51
+ Raises:
52
+ ImportError: If the config module cannot be imported
53
+ ValueError: If the config is missing required attributes
54
+ """
55
+ app_name = os.getenv('app_name')
56
+ config_path = CONFIG_MAPPING.get(app_name, CONFIG_MAPPING['default'])
57
+
58
+ try:
59
+ config = importlib.import_module(config_path)
60
+ except ImportError as e:
61
+ print(f"Error importing config {config_path}: {e}")
62
+ config = importlib.import_module(CONFIG_MAPPING['default'])
63
+
64
+ # Validate the config
65
+ is_valid, missing_attrs = validate_config(config)
66
+ if not is_valid:
67
+ error_msg = f"Config {config_path} is missing required attributes: {missing_attrs}"
68
+ print(f"Warning: {error_msg}")
69
+
70
+ # Try to load default config if current config is invalid
71
+ if config_path != CONFIG_MAPPING['default']:
72
+ print("Attempting to load default config...")
73
+ default_config = importlib.import_module(CONFIG_MAPPING['default'])
74
+ is_valid, missing_attrs = validate_config(default_config)
75
+ if not is_valid:
76
+ raise ValueError(f"Default config is also invalid. Missing: {missing_attrs}")
77
+ return default_config
78
+ else:
79
+ raise ValueError(error_msg)
80
+
81
+ return app_name, config
app/handlers/__init__.py ADDED
File without changes
app/handlers/event_handler.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing_extensions import override
2
+ from openai import AssistantEventHandler
3
+ from openai.types.beta.threads import Text, TextDelta
4
+ from openai.types.beta.threads.runs import ToolCall
5
+
6
+ class EventHandler(AssistantEventHandler):
7
+ """Handler for assistant events during streaming."""
8
+
9
+ @override
10
+ def on_text_created(self, text: Text) -> None:
11
+ """Called when text is created."""
12
+ print(f"\nassistant > ", end="", flush=True)
13
+
14
+ @override
15
+ def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None:
16
+ """Called when text is updated."""
17
+ print(delta.value, end="", flush=True)
18
+
19
+ @override
20
+ def on_tool_call_created(self, tool_call: ToolCall) -> None:
21
+ """Called when a tool call is created."""
22
+ print(f"\nassistant > {tool_call.type}\n", flush=True)
app/handlers/response_handler.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from typing import Generator, List, Tuple, Dict, Any
3
+
4
+ class ResponseHandler:
5
+ def __init__(self, chat_manager, app_name, active_config):
6
+ self.chat_manager = chat_manager
7
+ self.app_name = app_name
8
+ self.active_config = active_config
9
+
10
+ def handle_response(self, message: str, history: List[Tuple[str, str]],
11
+ textbox_content: str, username: str) -> Generator:
12
+ """Handle the chat response"""
13
+ # Get or create chat_id (which is the thread_id)
14
+ chat_id = self.chat_manager.get_or_create_thread()
15
+
16
+ preprocessed_data = self.active_config.handle_thread_before_chat(self.chat_manager.client, chat_id, message, history, textbox_content)
17
+
18
+ full_response = ""
19
+ canvas_result = ""
20
+ chat_response = ""
21
+ next_step_prompt = [[]]
22
+ prompt_tokens = 0
23
+ total_tokens = 0
24
+
25
+ try:
26
+ print(f"Processing request with chat_id: {chat_id}")
27
+ with self.chat_manager.client.beta.threads.runs.stream(
28
+ thread_id=chat_id,
29
+ assistant_id=self.chat_manager.assistant_id,
30
+ timeout=60
31
+ ) as stream:
32
+ try:
33
+ for stream_delta in stream:
34
+ if not stream_delta:
35
+ print("Warning: Empty text delta received", flush=True)
36
+ continue
37
+
38
+ if stream_delta.event == 'thread.run.failed':
39
+ print(f"Stream delta received: {stream_delta}", flush=True)
40
+ if hasattr(stream_delta.data, 'last_error') and stream_delta.data.last_error is not None:
41
+ chat_response = stream_delta.data.last_error.model_dump_json()
42
+ else:
43
+ chat_response = "Sorry, there was an error processing the response. Please try again."
44
+ continue
45
+ elif stream_delta.event != 'thread.message.delta':
46
+ print(f"Stream delta received: {stream_delta}", flush=True)
47
+
48
+ if stream_delta.event == 'thread.run.step.completed' and hasattr(stream_delta, 'data'):
49
+ if hasattr(stream_delta.data, 'usage'):
50
+ prompt_tokens = stream_delta.data.usage.prompt_tokens
51
+ total_tokens = stream_delta.data.usage.total_tokens
52
+ print(f"\nPrompt tokens: {prompt_tokens}, Total tokens: {total_tokens}\n", flush=True)
53
+ continue
54
+
55
+ if stream_delta.data.delta.content[0].text:
56
+ full_response += stream_delta.data.delta.content[0].text.value
57
+
58
+ print(f"Accumulated response length: {len(full_response)}", flush=True)
59
+
60
+ if len(full_response) < 2:
61
+ continue
62
+
63
+ chat_response, canvas_result, next_step_prompt = self.active_config.handle_stream_delta(full_response)
64
+ yield chat_response, canvas_result, next_step_prompt
65
+
66
+ except Exception as stream_error:
67
+ print(f"Stream processing error: {str(stream_error)}")
68
+ print(f"Error type: {type(stream_error).__name__}")
69
+ yield "Sorry, there was an error processing the response. Please try again.", "", [[]]
70
+
71
+ except Exception as connection_error:
72
+ print(f"Connection error: {str(connection_error)}")
73
+ print(f"Error type: {type(connection_error).__name__}")
74
+ yield "Sorry, the connection timed out. Please try again.", "", [[]]
75
+
76
+ # print(f"Current Messages: {message}")
77
+ # print(f"Suggestion: {chat_response}")
78
+ # print(f"Current Response: {full_response}")
79
+ print(f"Prompt tokens: {prompt_tokens}, Total tokens: {total_tokens}")
80
+
81
+ final_chat_response, final_canvas_result, final_next_step_prompt, msg_records = self.active_config.handle_stream_end(
82
+ message, history, chat_response, textbox_content, full_response, canvas_result, preprocessed_data
83
+ )
84
+
85
+ self.chat_manager.save_chat(username, chat_id, msg_records, canvas_result, self.app_name)
86
+ yield final_chat_response, final_canvas_result, final_next_step_prompt
87
+
88
+ def handle_quick_response_click(self, selected: str) -> str:
89
+ """Handle quick response selection"""
90
+ return selected[0]
91
+
92
+ def handle_quick_response_samples(self, next_step_prompt: List[List[str]]) -> gr.Dataset:
93
+ """Handle quick response samples"""
94
+ if len(next_step_prompt) > 0 and len(next_step_prompt[0]) > 0:
95
+ return gr.Dataset(samples=next_step_prompt, visible=True)
96
+ return gr.Dataset(samples=[['-']], visible=False)
97
+
98
+ def _get_assistant_response(self, message: str) -> str:
99
+ """Get response from the assistant"""
100
+ # Implementation of assistant response logic
101
+ pass
app/main.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from openai import OpenAI
4
+ from .config.base import get_app_name_and_config
5
+ from .handlers.event_handler import EventHandler
6
+ from .handlers.response_handler import ResponseHandler
7
+ from .utils.chat import ChatManager
8
+ from .utils.utils import print_assistant_info, check_password
9
+ from .ui.components import create_chatbot, create_textbox, create_prompt_input, create_quick_response, create_hidden_list, create_chat_selector, create_new_chat_button
10
+
11
+
12
+ # Initialize OpenAI client
13
+ OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
14
+ client = OpenAI(api_key=OPENAI_API_KEY)
15
+
16
+ # Get the active config
17
+ try:
18
+ app_name, active_config = get_app_name_and_config()
19
+ except (ImportError, ValueError) as e:
20
+ print(f"Fatal error loading config: {e}")
21
+ raise
22
+
23
+ # View existing assistants
24
+ existed_assistants = client.beta.assistants.list(
25
+ order="desc",
26
+ limit="20",
27
+ )
28
+
29
+ # print(len(existed_assistants.data), existed_assistants.data)
30
+
31
+ # Assistant settings
32
+ assistant_id = os.getenv('assistant_id')
33
+ ASSISTANT_MODEL = active_config.ASSISTANT_MODEL
34
+ ASSISTANT_NAME = active_config.ASSISTANT_NAME
35
+ ASSISTANT_DESCRIPTION = active_config.ASSISTANT_DESCRIPTION
36
+ ASSISTANT_INSTRUCTION = active_config.ASSISTANT_INSTRUCTION
37
+ RESPONSE_FORMAT = active_config.RESPONSE_FORMAT
38
+ VECTOR_STORE_NAME = "lesson-plan"
39
+ SHOW_CANVAS = active_config.SHOW_CANVAS
40
+ CONVERSATION_STARTER_SAMPLES = active_config.CONVERSATION_STARTER_SAMPLES
41
+
42
+ # Initialize chat history manager
43
+ chat_manager = ChatManager(client, assistant_id)
44
+ response_handler = ResponseHandler(chat_manager, app_name, active_config)
45
+
46
+ # Retrieve assistant
47
+ try:
48
+ assistant = client.beta.assistants.retrieve(assistant_id)
49
+ print(f"retrieve assistant success.")
50
+ print_assistant_info(assistant)
51
+ except Exception as e:
52
+ print(f"retrieve Assistant Fail: {e}")
53
+
54
+ # Components
55
+ chatbot = create_chatbot()
56
+ textbox = create_textbox()
57
+ prompt_input = create_prompt_input()
58
+ quick_response = create_quick_response(CONVERSATION_STARTER_SAMPLES)
59
+ hidden_list = create_hidden_list()
60
+ chat_selector = create_chat_selector()
61
+ new_chat_btn = create_new_chat_button()
62
+
63
+ CORRECT_PASSWORD = os.getenv('ui_password')
64
+
65
+ # Functions
66
+ def clear_input():
67
+ return ""
68
+
69
+ def chat_submit(message, history, textbox, username):
70
+ if message:
71
+ msg_records = [{'role': msg['role'], 'content': msg['content']} for msg in history]
72
+ msg_records.append({'role': 'user', 'content': message})
73
+ response_generator = response_handler.handle_response(message, history, textbox, username)
74
+ for suggestion, current_lesson_plan, next_step_prompt in response_generator:
75
+ yield msg_records + [{'role': 'assistant', 'content': suggestion}], current_lesson_plan, [[]]
76
+ yield msg_records + [{'role': 'assistant', 'content': suggestion}], current_lesson_plan, next_step_prompt
77
+
78
+ # Main UI
79
+ with gr.Blocks(css="""
80
+ .full-height {height: 100%}
81
+ """) as demo:
82
+ # password UI popup
83
+ with gr.Group(visible=True) as password_popup:
84
+ username_input = gr.Textbox(label="請輸入使用者名稱")
85
+ password_input = gr.Textbox(label="請輸入密碼", type="password")
86
+ submit_button = gr.Button("提交")
87
+ error_message = gr.Textbox(label="", visible=False, interactive=False)
88
+
89
+ # Store username in state
90
+ user_state = gr.State(value="default_user")
91
+
92
+ # Main UI
93
+ with gr.Group(visible=False) as main_ui:
94
+ with gr.Row(equal_height=True, elem_classes="contain"):
95
+ with gr.Column(scale=1, min_width=200, render=False):
96
+ chat_selector.render()
97
+ with gr.Column(scale=4):
98
+ with gr.Row(equal_height=True, height="90vh"):
99
+ with gr.Column():
100
+ chatbot.render()
101
+ prompt_input.render()
102
+ quick_response.render()
103
+ hidden_list.render()
104
+ with gr.Column(elem_classes="full-height", visible=SHOW_CANVAS) as textbox_column:
105
+ textbox.render()
106
+
107
+ # Event handlers
108
+ submit_button.click(
109
+ check_password,
110
+ inputs=[username_input, password_input],
111
+ outputs=[password_popup, main_ui, error_message, user_state]
112
+ )
113
+
114
+ password_input.submit(
115
+ check_password,
116
+ inputs=[username_input, password_input],
117
+ outputs=[password_popup, main_ui, error_message, user_state]
118
+ )
119
+
120
+ prompt_input.submit(
121
+ chat_submit,
122
+ inputs=[prompt_input, chatbot, textbox, user_state],
123
+ outputs=[chatbot, textbox, hidden_list]
124
+ ).then(
125
+ clear_input,
126
+ outputs=prompt_input
127
+ )
128
+
129
+ quick_response.click(
130
+ response_handler.handle_quick_response_click,
131
+ quick_response,
132
+ prompt_input
133
+ )
134
+
135
+ hidden_list.change(
136
+ response_handler.handle_quick_response_samples,
137
+ hidden_list,
138
+ quick_response
139
+ )
140
+
141
+ # Update chat selector when page loads
142
+ demo.load(
143
+ chat_manager.reset_chat_on_load,
144
+ inputs=[user_state],
145
+ outputs=[chat_selector, chatbot]
146
+ )
147
+
148
+ # Handle chat switching
149
+ chat_selector.change(
150
+ chat_manager.switch_chat,
151
+ inputs=[chat_selector, user_state],
152
+ outputs=[chatbot]
153
+ )
154
+
155
+ new_chat_btn.click(
156
+ chat_manager.create_new_chat,
157
+ outputs=[chat_selector, chatbot]
158
+ )
159
+
160
+ if __name__ == "__main__":
161
+ demo.launch(debug=True)
app/ui/__init__.py ADDED
File without changes
app/ui/components.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ def create_chatbot() -> gr.Chatbot:
4
+ """Create the main chatbot component."""
5
+ return gr.Chatbot(
6
+ type="messages",
7
+ container=True,
8
+ scale=8,
9
+ autoscroll=True
10
+ )
11
+
12
+ def create_textbox() -> gr.Textbox:
13
+ """Create the text editing component."""
14
+ return gr.Textbox(
15
+ label="教案編輯",
16
+ lines=30,
17
+ render=False,
18
+ interactive=True,
19
+ scale=1
20
+ )
21
+
22
+ def create_prompt_input() -> gr.Textbox:
23
+ """Create the user prompt input component."""
24
+ return gr.Textbox(
25
+ label="用戶需求",
26
+ submit_btn=True,
27
+ render=False,
28
+ scale=1
29
+ )
30
+
31
+ def create_quick_response(samples) -> gr.Dataset:
32
+ """Create the quick response suggestions component."""
33
+ return gr.Dataset(
34
+ samples=samples,
35
+ components=[create_prompt_input()],
36
+ render=False,
37
+ scale=1
38
+ )
39
+
40
+ def create_chat_selector() -> gr.Dropdown:
41
+ """Create the chat selection dropdown."""
42
+ return gr.Dropdown(
43
+ label="Select Chat",
44
+ choices=[],
45
+ interactive=True,
46
+ container=True,
47
+ scale=0
48
+ )
49
+
50
+ def create_new_chat_button() -> gr.Button:
51
+ """Create the new chat button."""
52
+ return gr.Button(
53
+ "New Chat",
54
+ size="sm",
55
+ )
56
+
57
+ def create_hidden_list() -> gr.JSON:
58
+ """Create the hidden list component."""
59
+ return gr.JSON(
60
+ value=[[]],
61
+ render=False,
62
+ visible=False
63
+ )
app/utils/__init__.py ADDED
File without changes
app/utils/chat.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from openai import OpenAI
3
+ import gradio as gr
4
+ from typing import Tuple
5
+ from supabase import create_client
6
+ from datetime import datetime
7
+ class ChatManager:
8
+ def __init__(self, client: OpenAI, assistant_id: str, supabase_url: str = None, supabase_key: str = None):
9
+ self.client = client
10
+ self.current_chat_id = None
11
+ self.assistant_id = assistant_id
12
+ """Initialize Supabase client"""
13
+ self.supabase = create_client(
14
+ supabase_url or os.environ.get("SUPABASE_URL"),
15
+ supabase_key or os.environ.get("SUPABASE_KEY")
16
+ )
17
+
18
+ def create_new_chat(self):
19
+ """Create a new chat using OpenAI thread ID as chat_id"""
20
+ thread = self.client.beta.threads.create()
21
+ self.current_chat_id = thread.id
22
+ return self.current_chat_id
23
+
24
+ def get_or_create_thread(self):
25
+ """Get or create a thread for the chat"""
26
+ if not self.current_chat_id:
27
+ self.current_chat_id = self.create_new_chat()
28
+ return self.current_chat_id
29
+
30
+ def reset_chat_on_load(self, username: str) -> Tuple[gr.update, list]:
31
+ """Reset chat ID on page load and update chat selector"""
32
+ self.create_new_chat()
33
+
34
+ # Update chat selector with available chats for this user
35
+ chats = self.list_user_chats(username)
36
+ return gr.update(choices=[(c["preview"], c["chat_id"]) for c in chats], value=self.current_chat_id), []
37
+
38
+ def load_chat_history(self, user_id="default_user"):
39
+ """Load chat history for a user"""
40
+ chat_id = self.get_latest_chat_id(user_id)
41
+ if chat_id:
42
+ return self.load_chat(user_id, chat_id)
43
+ return []
44
+
45
+ def list_user_chats(self, user_id="default_user"):
46
+ """List all chats for a user"""
47
+ response = self.supabase.table("chats") \
48
+ .select("chat_id,last_updated,messages") \
49
+ .eq("user_id", user_id) \
50
+ .execute()
51
+
52
+ chats = [{
53
+ "chat_id": chat["chat_id"],
54
+ "last_updated": chat["last_updated"],
55
+ "preview": chat["messages"][0]["content"] if chat["messages"] else "Empty chat"
56
+ } for chat in response.data]
57
+
58
+ return sorted(chats, key=lambda x: x["last_updated"], reverse=True)
59
+
60
+
61
+ def switch_chat(self, chat_id, user_id="default_user"):
62
+ """Switch to a different chat"""
63
+ self.current_chat_id = chat_id
64
+ messages = self.load_chat(user_id, chat_id)
65
+ return messages
66
+
67
+ def save_chat(self, user_id, chat_id, messages, current_lesson_plan, app_name):
68
+ """Save chat history to Supabase"""
69
+ chat_data = {
70
+ "user_id": user_id,
71
+ "chat_id": chat_id,
72
+ "last_updated": datetime.now().isoformat(),
73
+ "messages": messages,
74
+ "current_lesson_plan": current_lesson_plan,
75
+ "app_name": app_name
76
+ }
77
+
78
+ # Check if chat exists
79
+ existing_chat = self.supabase.table("chats") \
80
+ .select("*") \
81
+ .eq("user_id", user_id) \
82
+ .eq("chat_id", chat_id) \
83
+ .execute()
84
+
85
+ if existing_chat.data:
86
+ # Update existing chat
87
+ self.supabase.table("chats") \
88
+ .update(chat_data) \
89
+ .eq("user_id", user_id) \
90
+ .eq("chat_id", chat_id) \
91
+ .execute()
92
+ else:
93
+ # Insert new chat
94
+ self.supabase.table("chats") \
95
+ .insert(chat_data) \
96
+ .execute()
app/utils/utils.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import gradio as gr
3
+ import os
4
+
5
+ CORRECT_PASSWORD = os.getenv('ui_password')
6
+
7
+ def show_json(obj):
8
+ print(json.loads(obj.model_dump_json()))
9
+
10
+ def print_assistant_info(assistant):
11
+ """Print key information about an assistant object."""
12
+ # Get model
13
+ model = assistant.model
14
+
15
+ # Get description (limited to 2 lines)
16
+ desc_lines = assistant.description.split('\n')[:2]
17
+ desc = '\n'.join(desc_lines)
18
+ if len(desc) > 150: # Truncate if too long
19
+ desc = desc[:147] + "..."
20
+
21
+ # Get instructions (limited to 2 lines)
22
+ instr_lines = assistant.instructions.split('\n')[:2]
23
+ instr = '\n'.join(instr_lines)
24
+ if len(instr) > 150: # Truncate if too long
25
+ instr = instr[:147] + "..."
26
+
27
+ # Print information
28
+ print(f"Assistant ID: {assistant.id}")
29
+ print(f"Name: {assistant.name}")
30
+ print(f"Model: {model}")
31
+ print(f"Description: {desc}")
32
+ print(f"Instructions: {instr}")
33
+ print(f"Tools: {[tool.type for tool in assistant.tools]}")
34
+ print(f"Response Format: {assistant.response_format}")
35
+ print(f"Created at: {assistant.created_at}")
36
+
37
+ def check_password(username, input_password):
38
+ if input_password == CORRECT_PASSWORD:
39
+ return gr.update(visible=False), gr.update(visible=True), "", username
40
+ else:
41
+ return gr.update(visible=True), gr.update(visible=False), gr.update(value="密码错误,请重试。hint: channel name", visible=True), username