Remove has_stock_etf feature and add system prompt modal
Browse files- Remove has_stock_etf checkbox, logic, and ETF URL generation
- Add system prompt modal UI positioned on left side of screen
- Add 📝 button in chat header to open system prompt viewer
- Implement /system-prompt API endpoint to return current prompt
- Extract system prompt building logic into reusable function
- Add responsive design for mobile devices
- Apply changes to both embedded (chat-widget.js) and standalone (chat-widget.html) versions
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- api_server.py +124 -79
- chat-widget.html +186 -8
- chat-widget.js +186 -8
api_server.py
CHANGED
@@ -48,7 +48,6 @@ PRICE = {
|
|
48 |
}
|
49 |
|
50 |
SITE_LANGUAGES = ['繁體中文', '简体中文', 'English']
|
51 |
-
LANGUAGE_PROMPTS = ['- 使用繁體中文', '- 使用简体中文', '- Use English.']
|
52 |
SUBDOMAINS = ['www', 'sc', 'en']
|
53 |
LANG_ROUTES = ['zh-tw', 'zh-cn', 'en-001']
|
54 |
|
@@ -74,7 +73,6 @@ class ConfigModel(BaseModel):
|
|
74 |
has_quickie: bool = True
|
75 |
has_blog: bool = True
|
76 |
has_edm: bool = True
|
77 |
-
has_stock_etf: bool = True
|
78 |
has_hc: bool = True
|
79 |
has_google_search: bool = True
|
80 |
thinking_model: str = 'gemini-2.5-flash-preview-05-20'
|
@@ -85,6 +83,13 @@ class SearchRequest(BaseModel):
|
|
85 |
class SearchResponse(BaseModel):
|
86 |
results: str
|
87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
# Global variables for token counting
|
89 |
class TokenCounter:
|
90 |
def __init__(self):
|
@@ -181,14 +186,20 @@ def generate_content(user_prompt, system_prompt, response_type, response_schema,
|
|
181 |
token_counter.accumulate(response.usage_metadata, model)
|
182 |
return response
|
183 |
|
184 |
-
def
|
185 |
-
system_prompt = 'Given a user query, identify its language
|
186 |
response_type = 'application/json'
|
187 |
response_schema = str
|
188 |
tools = None
|
189 |
try:
|
190 |
response_parsed = generate_content(user_prompt, system_prompt, response_type, response_schema, tools, token_counter).parsed
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
except Exception as e:
|
193 |
raise HTTPException(status_code=500, detail=f"Language detection error: {e}")
|
194 |
|
@@ -282,6 +293,87 @@ def get_retrieval_from_help_center(csv_file, user_prompt, knowledge, token_count
|
|
282 |
df['html'] = htmls
|
283 |
return df.to_json(orient='records', force_ascii=False)
|
284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
285 |
@app.post("/chat", response_model=ChatResponse)
|
286 |
async def chat(request: ChatRequest):
|
287 |
"""Main chat endpoint"""
|
@@ -308,81 +400,10 @@ async def chat(request: ChatRequest):
|
|
308 |
user_prompt = request.message
|
309 |
contents.append(types.Content(role="user", parts=[types.Part.from_text(text=user_prompt)]))
|
310 |
|
311 |
-
#
|
312 |
-
|
|
|
313 |
site_language = SITE_LANGUAGES[site_language_idx]
|
314 |
-
subdomain = SUBDOMAINS[site_language_idx]
|
315 |
-
|
316 |
-
# Get system prompt
|
317 |
-
if SYSTEM_PROMPT_URL:
|
318 |
-
try:
|
319 |
-
system_prompt = requests.get(SYSTEM_PROMPT_URL).text
|
320 |
-
except Exception as e:
|
321 |
-
print(f"Warning: Could not fetch system prompt: {e}")
|
322 |
-
system_prompt = "You are MM Madam, a helpful financial AI assistant."
|
323 |
-
else:
|
324 |
-
system_prompt = "You are MM Madam, a helpful financial AI assistant."
|
325 |
-
|
326 |
-
# Determine prompt type
|
327 |
-
user_prompt_type_pro = get_user_prompt_type(contents, token_counter)
|
328 |
-
|
329 |
-
if user_prompt_type_pro:
|
330 |
-
if not config.is_paid_user:
|
331 |
-
system_prompt += '- 你會鼓勵用戶升級成為付費用戶就能享有完整問答服務,並且提供訂閱方案連結 \n'
|
332 |
-
system_prompt += f'`https://{subdomain}.macromicro.me/subscribe` \n'
|
333 |
-
|
334 |
-
# Add retrievals based on config
|
335 |
-
if config.has_chart and config.is_paid_user:
|
336 |
-
if retrieval := get_retrieval_from_charts_data_api('chart.csv', user_prompt, knowledge, token_counter):
|
337 |
-
system_prompt += '- MM圖表的資料,當中時間序列(series)包含前值及最新數據,務必引用 \n'
|
338 |
-
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/charts/{{id}}/{{slug}}` \n'
|
339 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
340 |
-
|
341 |
-
if config.has_quickie and config.is_paid_user:
|
342 |
-
if retrieval := get_retrieval('quickie.csv', user_prompt, knowledge, token_counter):
|
343 |
-
system_prompt += '- MM短評的資料 \n'
|
344 |
-
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/quickie?id={{id}}` \n' if subdomain != 'en' else ''
|
345 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
346 |
-
|
347 |
-
if config.has_blog and config.is_paid_user:
|
348 |
-
if retrieval := get_retrieval('post.csv', user_prompt, knowledge, token_counter):
|
349 |
-
system_prompt += '- MM部落格的資料 \n'
|
350 |
-
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/blog/{{slug}}` \n' if subdomain != 'en' else ''
|
351 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
352 |
-
if retrieval := get_retrieval('post_en.csv', user_prompt, knowledge, token_counter):
|
353 |
-
system_prompt += '- MM英文部落格的資料 \n'
|
354 |
-
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/blog/{{slug}}` \n' if subdomain == 'en' else ''
|
355 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
356 |
-
|
357 |
-
if config.has_edm and config.is_paid_user:
|
358 |
-
if retrieval := get_retrieval('edm.csv', user_prompt, knowledge, token_counter):
|
359 |
-
system_prompt += '- MM獨家報告的資料 \n'
|
360 |
-
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/mails/edm/{"tc" if site_language[0] == "繁" else "sc"}/display/{{id}}` \n' if subdomain != 'en' else ''
|
361 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
362 |
-
|
363 |
-
if config.has_stock_etf:
|
364 |
-
system_prompt += '- MM美股財報資料庫、ETF專區的網址規則 \n'
|
365 |
-
system_prompt += f'美股財報資料庫 `https://{subdomain}.macromicro.me/stocks/info/{{ticker_symbol}}` \n'
|
366 |
-
system_prompt += f'美國ETF專區 `https://{subdomain}.macromicro.me/etf/us/intro/{{ticker_symbol}}` \n'
|
367 |
-
system_prompt += f'台灣ETF專區 `https://{subdomain}.macromicro.me/etf/tw/intro/{{ticker_symbol}}` \n'
|
368 |
-
|
369 |
-
if config.has_google_search:
|
370 |
-
if retrieval := get_retrieval_from_google_search(user_prompt, token_counter):
|
371 |
-
system_prompt += '- 網路搜尋的資料 \n'
|
372 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
373 |
-
else:
|
374 |
-
if config.has_hc:
|
375 |
-
lang_route = LANG_ROUTES[site_language_idx]
|
376 |
-
if retrieval := get_retrieval_from_help_center(f'hc/{lang_route}/_log.csv', user_prompt, knowledge, token_counter):
|
377 |
-
system_prompt += '- MM幫助中心的資料 \n'
|
378 |
-
system_prompt += '不要提供來信或來電的客服聯繫方式 \n'
|
379 |
-
system_prompt += f'網址規則 `https://support.macromicro.me/hc/{lang_route}/articles/{{id}}` \n'
|
380 |
-
system_prompt += f'```\n{retrieval}\n```\n'
|
381 |
-
system_prompt += f'- MM幫助中心網址 `https://support.macromicro.me/hc/{lang_route}` \n'
|
382 |
-
system_prompt += '- 若非網站客服相關問題,你會婉拒回答 \n'
|
383 |
-
|
384 |
-
# Add language instruction
|
385 |
-
system_prompt += LANGUAGE_PROMPTS[site_language_idx]
|
386 |
|
387 |
# Generate response
|
388 |
response_type = 'text/plain'
|
@@ -448,6 +469,30 @@ async def search(request: SearchRequest):
|
|
448 |
traceback.print_exc() # Print full stack trace
|
449 |
raise HTTPException(status_code=500, detail=str(e))
|
450 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
451 |
@app.get("/")
|
452 |
async def serve_index():
|
453 |
"""Serve the main index.html page"""
|
|
|
48 |
}
|
49 |
|
50 |
SITE_LANGUAGES = ['繁體中文', '简体中文', 'English']
|
|
|
51 |
SUBDOMAINS = ['www', 'sc', 'en']
|
52 |
LANG_ROUTES = ['zh-tw', 'zh-cn', 'en-001']
|
53 |
|
|
|
73 |
has_quickie: bool = True
|
74 |
has_blog: bool = True
|
75 |
has_edm: bool = True
|
|
|
76 |
has_hc: bool = True
|
77 |
has_google_search: bool = True
|
78 |
thinking_model: str = 'gemini-2.5-flash-preview-05-20'
|
|
|
83 |
class SearchResponse(BaseModel):
|
84 |
results: str
|
85 |
|
86 |
+
class SystemPromptRequest(BaseModel):
|
87 |
+
message: str
|
88 |
+
config: Optional[Dict[str, Any]] = {}
|
89 |
+
|
90 |
+
class SystemPromptResponse(BaseModel):
|
91 |
+
system_prompt: str
|
92 |
+
|
93 |
# Global variables for token counting
|
94 |
class TokenCounter:
|
95 |
def __init__(self):
|
|
|
186 |
token_counter.accumulate(response.usage_metadata, model)
|
187 |
return response
|
188 |
|
189 |
+
def get_site_language_idx(user_prompt, token_counter):
|
190 |
+
system_prompt = 'Given a user query, identify its language code'
|
191 |
response_type = 'application/json'
|
192 |
response_schema = str
|
193 |
tools = None
|
194 |
try:
|
195 |
response_parsed = generate_content(user_prompt, system_prompt, response_type, response_schema, tools, token_counter).parsed
|
196 |
+
lang_idx_map = {
|
197 |
+
'zh': 0,
|
198 |
+
'zh-tw': 0, 'tw': 0,
|
199 |
+
'zh-cn': 1, 'cn': 1,
|
200 |
+
}
|
201 |
+
site_language_idx = lang_idx_map.get(response_parsed.lower(), 2)
|
202 |
+
return site_language_idx # site_language = 'English' for other languages
|
203 |
except Exception as e:
|
204 |
raise HTTPException(status_code=500, detail=f"Language detection error: {e}")
|
205 |
|
|
|
293 |
df['html'] = htmls
|
294 |
return df.to_json(orient='records', force_ascii=False)
|
295 |
|
296 |
+
def build_system_prompt(user_prompt, config, knowledge, token_counter):
|
297 |
+
"""Build the system prompt based on user input and configuration"""
|
298 |
+
# Detect language
|
299 |
+
site_language_idx = get_site_language_idx(user_prompt, token_counter)
|
300 |
+
site_language = SITE_LANGUAGES[site_language_idx]
|
301 |
+
subdomain = SUBDOMAINS[site_language_idx]
|
302 |
+
|
303 |
+
# Get base system prompt
|
304 |
+
system_prompt = ''
|
305 |
+
system_prompt += f'- language = "{site_language}"\n'
|
306 |
+
system_prompt += f'- subdomain = "{subdomain}"\n'
|
307 |
+
if SYSTEM_PROMPT_URL:
|
308 |
+
try:
|
309 |
+
system_prompt += requests.get(SYSTEM_PROMPT_URL).text
|
310 |
+
except:
|
311 |
+
system_prompt += "You are MM Madam, a helpful financial AI assistant."
|
312 |
+
else:
|
313 |
+
system_prompt += "You are MM Madam, a helpful financial AI assistant."
|
314 |
+
|
315 |
+
# Create dummy contents for type detection
|
316 |
+
contents = [{'role': 'user', 'content': user_prompt}]
|
317 |
+
dummy_contents = []
|
318 |
+
for msg in contents:
|
319 |
+
dummy_contents.append(types.Content(
|
320 |
+
role="user" if msg['role'] == "user" else "model",
|
321 |
+
parts=[types.Part.from_text(text=msg['content'])]
|
322 |
+
))
|
323 |
+
|
324 |
+
# Determine prompt type
|
325 |
+
user_prompt_type_pro = get_user_prompt_type(dummy_contents, token_counter)
|
326 |
+
|
327 |
+
if user_prompt_type_pro:
|
328 |
+
if not config.is_paid_user:
|
329 |
+
system_prompt += '- 你會鼓勵用戶升級成為付費用戶就能享有完整問答服務,並且提供訂閱方案連結 \n'
|
330 |
+
system_prompt += f'`https://{subdomain}.macromicro.me/subscribe` \n'
|
331 |
+
|
332 |
+
# Add retrievals based on config
|
333 |
+
if config.has_chart and config.is_paid_user:
|
334 |
+
if retrieval := get_retrieval_from_charts_data_api('chart.csv', user_prompt, knowledge, token_counter):
|
335 |
+
system_prompt += '- MM圖表的資料,當中時間序列(series)包含前值及最新數據,務必引用 \n'
|
336 |
+
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/charts/{{id}}/{{slug}}` \n'
|
337 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
338 |
+
|
339 |
+
if config.has_quickie and config.is_paid_user:
|
340 |
+
if retrieval := get_retrieval('quickie.csv', user_prompt, knowledge, token_counter):
|
341 |
+
system_prompt += '- MM短評的資料 \n'
|
342 |
+
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/quickie?id={{id}}` \n' if subdomain != 'en' else ''
|
343 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
344 |
+
|
345 |
+
if config.has_blog and config.is_paid_user:
|
346 |
+
if retrieval := get_retrieval('post.csv', user_prompt, knowledge, token_counter):
|
347 |
+
system_prompt += '- MM部落格的資料 \n'
|
348 |
+
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/blog/{{slug}}` \n' if subdomain != 'en' else ''
|
349 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
350 |
+
if retrieval := get_retrieval('post_en.csv', user_prompt, knowledge, token_counter):
|
351 |
+
system_prompt += '- MM英文部落格的資料 \n'
|
352 |
+
system_prompt += f'網址規則 `https://{subdomain}.macromicro.me/blog/{{slug}}` \n' if subdomain == 'en' else ''
|
353 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
354 |
+
|
355 |
+
if config.has_edm and config.is_paid_user:
|
356 |
+
if retrieval := get_retrieval('edm.csv', user_prompt, knowledge, token_counter):
|
357 |
+
system_prompt += '- MM獨家報告的資料 \n'
|
358 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
359 |
+
|
360 |
+
if config.has_google_search:
|
361 |
+
if retrieval := get_retrieval_from_google_search(user_prompt, token_counter):
|
362 |
+
system_prompt += '- 網路搜尋的資料 \n'
|
363 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
364 |
+
else:
|
365 |
+
if config.has_hc:
|
366 |
+
lang_route = LANG_ROUTES[site_language_idx]
|
367 |
+
if retrieval := get_retrieval_from_help_center(f'hc/{lang_route}/_log.csv', user_prompt, knowledge, token_counter):
|
368 |
+
system_prompt += '- MM幫助中心的資料 \n'
|
369 |
+
system_prompt += '不要提供來信或來電的客服聯繫方式 \n'
|
370 |
+
system_prompt += f'網址規則 `https://support.macromicro.me/hc/{lang_route}/articles/{{id}}` \n'
|
371 |
+
system_prompt += f'```\n{retrieval}\n```\n'
|
372 |
+
system_prompt += f'- MM幫助中心網址 `https://support.macromicro.me/hc/{lang_route}` \n'
|
373 |
+
system_prompt += '- 若非網站客服相關問題,你會婉拒回答 \n'
|
374 |
+
|
375 |
+
return system_prompt
|
376 |
+
|
377 |
@app.post("/chat", response_model=ChatResponse)
|
378 |
async def chat(request: ChatRequest):
|
379 |
"""Main chat endpoint"""
|
|
|
400 |
user_prompt = request.message
|
401 |
contents.append(types.Content(role="user", parts=[types.Part.from_text(text=user_prompt)]))
|
402 |
|
403 |
+
# Build system prompt using the extracted function
|
404 |
+
system_prompt = build_system_prompt(user_prompt, config, knowledge, token_counter)
|
405 |
+
site_language_idx = get_site_language_idx(user_prompt, token_counter)
|
406 |
site_language = SITE_LANGUAGES[site_language_idx]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
|
408 |
# Generate response
|
409 |
response_type = 'text/plain'
|
|
|
469 |
traceback.print_exc() # Print full stack trace
|
470 |
raise HTTPException(status_code=500, detail=str(e))
|
471 |
|
472 |
+
@app.post("/system-prompt", response_model=SystemPromptResponse)
|
473 |
+
async def get_system_prompt(request: SystemPromptRequest):
|
474 |
+
"""Get the system prompt that would be used for a given message and config"""
|
475 |
+
token_counter = TokenCounter()
|
476 |
+
|
477 |
+
try:
|
478 |
+
print(f"Received system prompt request: {request.message[:50]}...") # Debug log
|
479 |
+
# Parse configuration
|
480 |
+
config = ConfigModel(**request.config) if request.config else ConfigModel()
|
481 |
+
|
482 |
+
# Get knowledge
|
483 |
+
knowledge = get_knowledge()
|
484 |
+
|
485 |
+
# Build system prompt
|
486 |
+
system_prompt = build_system_prompt(request.message, config, knowledge, token_counter)
|
487 |
+
|
488 |
+
return SystemPromptResponse(system_prompt=system_prompt)
|
489 |
+
|
490 |
+
except Exception as e:
|
491 |
+
print(f"System prompt error: {str(e)}") # Debug log
|
492 |
+
import traceback
|
493 |
+
traceback.print_exc() # Print full stack trace
|
494 |
+
raise HTTPException(status_code=500, detail=str(e))
|
495 |
+
|
496 |
@app.get("/")
|
497 |
async def serve_index():
|
498 |
"""Serve the main index.html page"""
|
chat-widget.html
CHANGED
@@ -325,6 +325,109 @@
|
|
325 |
margin-bottom: 20px;
|
326 |
}
|
327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
/* Mobile responsive */
|
329 |
@media (max-width: 768px) {
|
330 |
.mm-chat-widget {
|
@@ -338,6 +441,16 @@
|
|
338 |
bottom: 70px;
|
339 |
right: -10px;
|
340 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
341 |
}
|
342 |
</style>
|
343 |
</head>
|
@@ -350,6 +463,7 @@
|
|
350 |
<h3>👩🏻💼 MM Madam</h3>
|
351 |
</div>
|
352 |
<div>
|
|
|
353 |
<button class="mm-config-toggle" id="mmConfigToggle">⚙️</button>
|
354 |
<button class="mm-chat-close" id="mmChatClose">×</button>
|
355 |
</div>
|
@@ -387,12 +501,6 @@
|
|
387 |
📮 MM獨家報告
|
388 |
</label>
|
389 |
</div>
|
390 |
-
<div class="mm-config-item">
|
391 |
-
<label>
|
392 |
-
<input type="checkbox" id="hasStockEtf" checked>
|
393 |
-
📈 MM美股財報、ETF專區
|
394 |
-
</label>
|
395 |
-
</div>
|
396 |
<div class="mm-config-item">
|
397 |
<label>
|
398 |
<input type="checkbox" id="hasHc" checked>
|
@@ -437,6 +545,20 @@
|
|
437 |
</div>
|
438 |
</div>
|
439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
<script>
|
441 |
class MMChatWidget {
|
442 |
constructor(config = {}) {
|
@@ -460,6 +582,10 @@
|
|
460 |
this.configToggle = document.getElementById('mmConfigToggle');
|
461 |
this.configPanel = document.getElementById('mmConfigPanel');
|
462 |
this.configBack = document.getElementById('mmConfigBack');
|
|
|
|
|
|
|
|
|
463 |
|
464 |
// Config elements
|
465 |
this.configElements = {
|
@@ -468,7 +594,6 @@
|
|
468 |
hasQuickie: document.getElementById('hasQuickie'),
|
469 |
hasBlog: document.getElementById('hasBlog'),
|
470 |
hasEdm: document.getElementById('hasEdm'),
|
471 |
-
hasStockEtf: document.getElementById('hasStockEtf'),
|
472 |
hasHc: document.getElementById('hasHc'),
|
473 |
hasSearch: document.getElementById('hasSearch'),
|
474 |
hasMemory: document.getElementById('hasMemory')
|
@@ -481,6 +606,13 @@
|
|
481 |
this.sendBtn.addEventListener('click', () => this.sendMessage());
|
482 |
this.configToggle.addEventListener('click', () => this.toggleConfig());
|
483 |
this.configBack.addEventListener('click', () => this.toggleConfig());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
484 |
|
485 |
this.input.addEventListener('keydown', (e) => {
|
486 |
if (e.key === 'Enter' && !e.shiftKey) {
|
@@ -529,12 +661,59 @@
|
|
529 |
this.isOpen = false;
|
530 |
this.panel.classList.remove('open');
|
531 |
this.configPanel.classList.remove('open');
|
|
|
532 |
}
|
533 |
|
534 |
toggleConfig() {
|
535 |
this.configPanel.classList.toggle('open');
|
536 |
}
|
537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
538 |
getConfig() {
|
539 |
return {
|
540 |
is_paid_user: this.configElements.isPaidUser.checked,
|
@@ -542,7 +721,6 @@
|
|
542 |
has_quickie: this.configElements.hasQuickie.checked,
|
543 |
has_blog: this.configElements.hasBlog.checked,
|
544 |
has_edm: this.configElements.hasEdm.checked,
|
545 |
-
has_stock_etf: this.configElements.hasStockEtf.checked,
|
546 |
has_hc: this.configElements.hasHc.checked,
|
547 |
has_search: this.configElements.hasSearch.checked,
|
548 |
has_memory: this.configElements.hasMemory.checked,
|
|
|
325 |
margin-bottom: 20px;
|
326 |
}
|
327 |
|
328 |
+
.mm-system-prompt-modal {
|
329 |
+
position: fixed;
|
330 |
+
top: 0;
|
331 |
+
left: 0;
|
332 |
+
width: 100vw;
|
333 |
+
height: 100vh;
|
334 |
+
background: rgba(0, 0, 0, 0.5);
|
335 |
+
z-index: 20000;
|
336 |
+
display: none;
|
337 |
+
align-items: center;
|
338 |
+
justify-content: flex-start;
|
339 |
+
padding-left: 50px;
|
340 |
+
}
|
341 |
+
|
342 |
+
.mm-system-prompt-modal.open {
|
343 |
+
display: flex;
|
344 |
+
}
|
345 |
+
|
346 |
+
.mm-system-prompt-content {
|
347 |
+
background: white;
|
348 |
+
width: 600px;
|
349 |
+
max-width: 90vw;
|
350 |
+
height: 80vh;
|
351 |
+
border-radius: 12px;
|
352 |
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
353 |
+
display: flex;
|
354 |
+
flex-direction: column;
|
355 |
+
overflow: hidden;
|
356 |
+
}
|
357 |
+
|
358 |
+
.mm-system-prompt-header {
|
359 |
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
360 |
+
color: white;
|
361 |
+
padding: 20px 25px;
|
362 |
+
display: flex;
|
363 |
+
align-items: center;
|
364 |
+
justify-content: space-between;
|
365 |
+
}
|
366 |
+
|
367 |
+
.mm-system-prompt-header h3 {
|
368 |
+
margin: 0;
|
369 |
+
font-size: 18px;
|
370 |
+
font-weight: 600;
|
371 |
+
}
|
372 |
+
|
373 |
+
.mm-system-prompt-close {
|
374 |
+
background: none;
|
375 |
+
border: none;
|
376 |
+
color: white;
|
377 |
+
cursor: pointer;
|
378 |
+
font-size: 20px;
|
379 |
+
padding: 0;
|
380 |
+
width: 30px;
|
381 |
+
height: 30px;
|
382 |
+
border-radius: 50%;
|
383 |
+
display: flex;
|
384 |
+
align-items: center;
|
385 |
+
justify-content: center;
|
386 |
+
transition: background 0.2s ease;
|
387 |
+
}
|
388 |
+
|
389 |
+
.mm-system-prompt-close:hover {
|
390 |
+
background: rgba(255, 255, 255, 0.2);
|
391 |
+
}
|
392 |
+
|
393 |
+
.mm-system-prompt-body {
|
394 |
+
flex: 1;
|
395 |
+
padding: 25px;
|
396 |
+
overflow-y: auto;
|
397 |
+
background: #f8f9fa;
|
398 |
+
}
|
399 |
+
|
400 |
+
.mm-system-prompt-text {
|
401 |
+
background: white;
|
402 |
+
border: 1px solid #e0e0e0;
|
403 |
+
border-radius: 8px;
|
404 |
+
padding: 20px;
|
405 |
+
font-family: Monaco, Consolas, monospace;
|
406 |
+
font-size: 12px;
|
407 |
+
line-height: 1.5;
|
408 |
+
white-space: pre-wrap;
|
409 |
+
word-wrap: break-word;
|
410 |
+
color: #333;
|
411 |
+
max-height: 100%;
|
412 |
+
overflow-y: auto;
|
413 |
+
}
|
414 |
+
|
415 |
+
.mm-system-prompt-toggle {
|
416 |
+
background: none;
|
417 |
+
border: none;
|
418 |
+
color: white;
|
419 |
+
cursor: pointer;
|
420 |
+
font-size: 16px;
|
421 |
+
padding: 5px;
|
422 |
+
margin-left: 5px;
|
423 |
+
border-radius: 4px;
|
424 |
+
transition: background 0.2s ease;
|
425 |
+
}
|
426 |
+
|
427 |
+
.mm-system-prompt-toggle:hover {
|
428 |
+
background: rgba(255, 255, 255, 0.2);
|
429 |
+
}
|
430 |
+
|
431 |
/* Mobile responsive */
|
432 |
@media (max-width: 768px) {
|
433 |
.mm-chat-widget {
|
|
|
441 |
bottom: 70px;
|
442 |
right: -10px;
|
443 |
}
|
444 |
+
|
445 |
+
.mm-system-prompt-modal {
|
446 |
+
padding-left: 10px;
|
447 |
+
padding-right: 10px;
|
448 |
+
}
|
449 |
+
|
450 |
+
.mm-system-prompt-content {
|
451 |
+
width: 100%;
|
452 |
+
height: 90vh;
|
453 |
+
}
|
454 |
}
|
455 |
</style>
|
456 |
</head>
|
|
|
463 |
<h3>👩🏻💼 MM Madam</h3>
|
464 |
</div>
|
465 |
<div>
|
466 |
+
<button class="mm-system-prompt-toggle" id="mmSystemPromptToggle">📝</button>
|
467 |
<button class="mm-config-toggle" id="mmConfigToggle">⚙️</button>
|
468 |
<button class="mm-chat-close" id="mmChatClose">×</button>
|
469 |
</div>
|
|
|
501 |
📮 MM獨家報告
|
502 |
</label>
|
503 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
504 |
<div class="mm-config-item">
|
505 |
<label>
|
506 |
<input type="checkbox" id="hasHc" checked>
|
|
|
545 |
</div>
|
546 |
</div>
|
547 |
|
548 |
+
<div class="mm-system-prompt-modal" id="mmSystemPromptModal">
|
549 |
+
<div class="mm-system-prompt-content">
|
550 |
+
<div class="mm-system-prompt-header">
|
551 |
+
<h3>📝 System Prompt</h3>
|
552 |
+
<button class="mm-system-prompt-close" id="mmSystemPromptClose">×</button>
|
553 |
+
</div>
|
554 |
+
<div class="mm-system-prompt-body">
|
555 |
+
<div class="mm-system-prompt-text" id="mmSystemPromptText">
|
556 |
+
Loading system prompt...
|
557 |
+
</div>
|
558 |
+
</div>
|
559 |
+
</div>
|
560 |
+
</div>
|
561 |
+
|
562 |
<script>
|
563 |
class MMChatWidget {
|
564 |
constructor(config = {}) {
|
|
|
582 |
this.configToggle = document.getElementById('mmConfigToggle');
|
583 |
this.configPanel = document.getElementById('mmConfigPanel');
|
584 |
this.configBack = document.getElementById('mmConfigBack');
|
585 |
+
this.systemPromptToggle = document.getElementById('mmSystemPromptToggle');
|
586 |
+
this.systemPromptModal = document.getElementById('mmSystemPromptModal');
|
587 |
+
this.systemPromptClose = document.getElementById('mmSystemPromptClose');
|
588 |
+
this.systemPromptText = document.getElementById('mmSystemPromptText');
|
589 |
|
590 |
// Config elements
|
591 |
this.configElements = {
|
|
|
594 |
hasQuickie: document.getElementById('hasQuickie'),
|
595 |
hasBlog: document.getElementById('hasBlog'),
|
596 |
hasEdm: document.getElementById('hasEdm'),
|
|
|
597 |
hasHc: document.getElementById('hasHc'),
|
598 |
hasSearch: document.getElementById('hasSearch'),
|
599 |
hasMemory: document.getElementById('hasMemory')
|
|
|
606 |
this.sendBtn.addEventListener('click', () => this.sendMessage());
|
607 |
this.configToggle.addEventListener('click', () => this.toggleConfig());
|
608 |
this.configBack.addEventListener('click', () => this.toggleConfig());
|
609 |
+
this.systemPromptToggle.addEventListener('click', () => this.toggleSystemPrompt());
|
610 |
+
this.systemPromptClose.addEventListener('click', () => this.closeSystemPrompt());
|
611 |
+
this.systemPromptModal.addEventListener('click', (e) => {
|
612 |
+
if (e.target === this.systemPromptModal) {
|
613 |
+
this.closeSystemPrompt();
|
614 |
+
}
|
615 |
+
});
|
616 |
|
617 |
this.input.addEventListener('keydown', (e) => {
|
618 |
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
661 |
this.isOpen = false;
|
662 |
this.panel.classList.remove('open');
|
663 |
this.configPanel.classList.remove('open');
|
664 |
+
this.systemPromptModal.classList.remove('open');
|
665 |
}
|
666 |
|
667 |
toggleConfig() {
|
668 |
this.configPanel.classList.toggle('open');
|
669 |
}
|
670 |
|
671 |
+
async toggleSystemPrompt() {
|
672 |
+
const isOpen = this.systemPromptModal.classList.contains('open');
|
673 |
+
if (isOpen) {
|
674 |
+
this.closeSystemPrompt();
|
675 |
+
} else {
|
676 |
+
await this.openSystemPrompt();
|
677 |
+
}
|
678 |
+
}
|
679 |
+
|
680 |
+
async openSystemPrompt() {
|
681 |
+
this.systemPromptModal.classList.add('open');
|
682 |
+
await this.loadSystemPrompt();
|
683 |
+
}
|
684 |
+
|
685 |
+
closeSystemPrompt() {
|
686 |
+
this.systemPromptModal.classList.remove('open');
|
687 |
+
}
|
688 |
+
|
689 |
+
async loadSystemPrompt() {
|
690 |
+
try {
|
691 |
+
this.systemPromptText.textContent = 'Loading system prompt...';
|
692 |
+
|
693 |
+
const response = await fetch(`${this.apiUrl}/system-prompt`, {
|
694 |
+
method: 'POST',
|
695 |
+
headers: {
|
696 |
+
'Content-Type': 'application/json',
|
697 |
+
},
|
698 |
+
body: JSON.stringify({
|
699 |
+
message: this.input.value.trim() || 'Sample message',
|
700 |
+
config: this.getConfig()
|
701 |
+
})
|
702 |
+
});
|
703 |
+
|
704 |
+
if (!response.ok) {
|
705 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
706 |
+
}
|
707 |
+
|
708 |
+
const data = await response.json();
|
709 |
+
this.systemPromptText.textContent = data.system_prompt;
|
710 |
+
|
711 |
+
} catch (error) {
|
712 |
+
console.error('System prompt error:', error);
|
713 |
+
this.systemPromptText.textContent = 'Error loading system prompt. Please try again.';
|
714 |
+
}
|
715 |
+
}
|
716 |
+
|
717 |
getConfig() {
|
718 |
return {
|
719 |
is_paid_user: this.configElements.isPaidUser.checked,
|
|
|
721 |
has_quickie: this.configElements.hasQuickie.checked,
|
722 |
has_blog: this.configElements.hasBlog.checked,
|
723 |
has_edm: this.configElements.hasEdm.checked,
|
|
|
724 |
has_hc: this.configElements.hasHc.checked,
|
725 |
has_search: this.configElements.hasSearch.checked,
|
726 |
has_memory: this.configElements.hasMemory.checked,
|
chat-widget.js
CHANGED
@@ -393,6 +393,109 @@
|
|
393 |
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
394 |
}
|
395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
396 |
@media (max-width: 768px) {
|
397 |
.mm-chat-widget {
|
398 |
bottom: 10px;
|
@@ -405,6 +508,16 @@
|
|
405 |
bottom: 70px;
|
406 |
right: -10px;
|
407 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
408 |
}
|
409 |
`;
|
410 |
|
@@ -447,6 +560,7 @@
|
|
447 |
<h3>👩🏻💼 MM Madam</h3>
|
448 |
</div>
|
449 |
<div>
|
|
|
450 |
<button class="mm-config-toggle" id="mmConfigToggle">⚙️</button>
|
451 |
<button class="mm-chat-close" id="mmChatClose">×</button>
|
452 |
</div>
|
@@ -484,12 +598,6 @@
|
|
484 |
📮 MM獨家報告
|
485 |
</label>
|
486 |
</div>
|
487 |
-
<div class="mm-config-item">
|
488 |
-
<label>
|
489 |
-
<input type="checkbox" id="hasStockEtf" checked>
|
490 |
-
📈 MM美股財報、ETF專區
|
491 |
-
</label>
|
492 |
-
</div>
|
493 |
<div class="mm-config-item">
|
494 |
<label>
|
495 |
<input type="checkbox" id="hasHc" checked>
|
@@ -533,6 +641,20 @@
|
|
533 |
</div>
|
534 |
</div>
|
535 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
536 |
</div>
|
537 |
`;
|
538 |
|
@@ -551,6 +673,10 @@
|
|
551 |
this.configPanel = document.getElementById('mmConfigPanel');
|
552 |
this.configBack = document.getElementById('mmConfigBack');
|
553 |
this.modeSwitch = document.getElementById('mmModeSwitch');
|
|
|
|
|
|
|
|
|
554 |
|
555 |
this.configElements = {
|
556 |
isPaidUser: document.getElementById('isPaidUser'),
|
@@ -558,7 +684,6 @@
|
|
558 |
hasQuickie: document.getElementById('hasQuickie'),
|
559 |
hasBlog: document.getElementById('hasBlog'),
|
560 |
hasEdm: document.getElementById('hasEdm'),
|
561 |
-
hasStockEtf: document.getElementById('hasStockEtf'),
|
562 |
hasHc: document.getElementById('hasHc'),
|
563 |
hasGoogleSearch: document.getElementById('hasGoogleSearch')
|
564 |
};
|
@@ -572,6 +697,13 @@
|
|
572 |
this.sendBtn.addEventListener('click', () => this.sendMessage());
|
573 |
this.configToggle.addEventListener('click', () => this.toggleConfig());
|
574 |
this.configBack.addEventListener('click', () => this.toggleConfig());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
575 |
|
576 |
this.input.addEventListener('keydown', (e) => {
|
577 |
if (e.key === 'Enter' && !e.shiftKey) {
|
@@ -658,6 +790,7 @@
|
|
658 |
this.isOpen = false;
|
659 |
this.panel.classList.remove('open');
|
660 |
this.configPanel.classList.remove('open');
|
|
|
661 |
}
|
662 |
|
663 |
toggleConfig() {
|
@@ -674,6 +807,52 @@
|
|
674 |
}
|
675 |
}
|
676 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
677 |
getConfig() {
|
678 |
return {
|
679 |
is_paid_user: this.configElements.isPaidUser.checked,
|
@@ -681,7 +860,6 @@
|
|
681 |
has_quickie: this.configElements.hasQuickie.checked,
|
682 |
has_blog: this.configElements.hasBlog.checked,
|
683 |
has_edm: this.configElements.hasEdm.checked,
|
684 |
-
has_stock_etf: this.configElements.hasStockEtf.checked,
|
685 |
has_hc: this.configElements.hasHc.checked,
|
686 |
has_google_search: this.configElements.hasGoogleSearch.checked
|
687 |
};
|
|
|
393 |
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
394 |
}
|
395 |
|
396 |
+
.mm-system-prompt-modal {
|
397 |
+
position: fixed;
|
398 |
+
top: 0;
|
399 |
+
left: 0;
|
400 |
+
width: 100vw;
|
401 |
+
height: 100vh;
|
402 |
+
background: rgba(0, 0, 0, 0.5);
|
403 |
+
z-index: 20000;
|
404 |
+
display: none;
|
405 |
+
align-items: center;
|
406 |
+
justify-content: flex-start;
|
407 |
+
padding-left: 50px;
|
408 |
+
}
|
409 |
+
|
410 |
+
.mm-system-prompt-modal.open {
|
411 |
+
display: flex;
|
412 |
+
}
|
413 |
+
|
414 |
+
.mm-system-prompt-content {
|
415 |
+
background: white;
|
416 |
+
width: 600px;
|
417 |
+
max-width: 90vw;
|
418 |
+
height: 80vh;
|
419 |
+
border-radius: 12px;
|
420 |
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
421 |
+
display: flex;
|
422 |
+
flex-direction: column;
|
423 |
+
overflow: hidden;
|
424 |
+
}
|
425 |
+
|
426 |
+
.mm-system-prompt-header {
|
427 |
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
428 |
+
color: white;
|
429 |
+
padding: 20px 25px;
|
430 |
+
display: flex;
|
431 |
+
align-items: center;
|
432 |
+
justify-content: space-between;
|
433 |
+
}
|
434 |
+
|
435 |
+
.mm-system-prompt-header h3 {
|
436 |
+
margin: 0;
|
437 |
+
font-size: 18px;
|
438 |
+
font-weight: 600;
|
439 |
+
}
|
440 |
+
|
441 |
+
.mm-system-prompt-close {
|
442 |
+
background: none;
|
443 |
+
border: none;
|
444 |
+
color: white;
|
445 |
+
cursor: pointer;
|
446 |
+
font-size: 20px;
|
447 |
+
padding: 0;
|
448 |
+
width: 30px;
|
449 |
+
height: 30px;
|
450 |
+
border-radius: 50%;
|
451 |
+
display: flex;
|
452 |
+
align-items: center;
|
453 |
+
justify-content: center;
|
454 |
+
transition: background 0.2s ease;
|
455 |
+
}
|
456 |
+
|
457 |
+
.mm-system-prompt-close:hover {
|
458 |
+
background: rgba(255, 255, 255, 0.2);
|
459 |
+
}
|
460 |
+
|
461 |
+
.mm-system-prompt-body {
|
462 |
+
flex: 1;
|
463 |
+
padding: 25px;
|
464 |
+
overflow-y: auto;
|
465 |
+
background: #f8f9fa;
|
466 |
+
}
|
467 |
+
|
468 |
+
.mm-system-prompt-text {
|
469 |
+
background: white;
|
470 |
+
border: 1px solid #e0e0e0;
|
471 |
+
border-radius: 8px;
|
472 |
+
padding: 20px;
|
473 |
+
font-family: Monaco, Consolas, monospace;
|
474 |
+
font-size: 12px;
|
475 |
+
line-height: 1.5;
|
476 |
+
white-space: pre-wrap;
|
477 |
+
word-wrap: break-word;
|
478 |
+
color: #333;
|
479 |
+
max-height: 100%;
|
480 |
+
overflow-y: auto;
|
481 |
+
}
|
482 |
+
|
483 |
+
.mm-system-prompt-toggle {
|
484 |
+
background: none;
|
485 |
+
border: none;
|
486 |
+
color: white;
|
487 |
+
cursor: pointer;
|
488 |
+
font-size: 16px;
|
489 |
+
padding: 5px;
|
490 |
+
margin-left: 5px;
|
491 |
+
border-radius: 4px;
|
492 |
+
transition: background 0.2s ease;
|
493 |
+
}
|
494 |
+
|
495 |
+
.mm-system-prompt-toggle:hover {
|
496 |
+
background: rgba(255, 255, 255, 0.2);
|
497 |
+
}
|
498 |
+
|
499 |
@media (max-width: 768px) {
|
500 |
.mm-chat-widget {
|
501 |
bottom: 10px;
|
|
|
508 |
bottom: 70px;
|
509 |
right: -10px;
|
510 |
}
|
511 |
+
|
512 |
+
.mm-system-prompt-modal {
|
513 |
+
padding-left: 10px;
|
514 |
+
padding-right: 10px;
|
515 |
+
}
|
516 |
+
|
517 |
+
.mm-system-prompt-content {
|
518 |
+
width: 100%;
|
519 |
+
height: 90vh;
|
520 |
+
}
|
521 |
}
|
522 |
`;
|
523 |
|
|
|
560 |
<h3>👩🏻💼 MM Madam</h3>
|
561 |
</div>
|
562 |
<div>
|
563 |
+
<button class="mm-system-prompt-toggle" id="mmSystemPromptToggle">📝</button>
|
564 |
<button class="mm-config-toggle" id="mmConfigToggle">⚙️</button>
|
565 |
<button class="mm-chat-close" id="mmChatClose">×</button>
|
566 |
</div>
|
|
|
598 |
📮 MM獨家報告
|
599 |
</label>
|
600 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
601 |
<div class="mm-config-item">
|
602 |
<label>
|
603 |
<input type="checkbox" id="hasHc" checked>
|
|
|
641 |
</div>
|
642 |
</div>
|
643 |
</div>
|
644 |
+
|
645 |
+
<div class="mm-system-prompt-modal" id="mmSystemPromptModal">
|
646 |
+
<div class="mm-system-prompt-content">
|
647 |
+
<div class="mm-system-prompt-header">
|
648 |
+
<h3>📝 System Prompt</h3>
|
649 |
+
<button class="mm-system-prompt-close" id="mmSystemPromptClose">��</button>
|
650 |
+
</div>
|
651 |
+
<div class="mm-system-prompt-body">
|
652 |
+
<div class="mm-system-prompt-text" id="mmSystemPromptText">
|
653 |
+
Loading system prompt...
|
654 |
+
</div>
|
655 |
+
</div>
|
656 |
+
</div>
|
657 |
+
</div>
|
658 |
</div>
|
659 |
`;
|
660 |
|
|
|
673 |
this.configPanel = document.getElementById('mmConfigPanel');
|
674 |
this.configBack = document.getElementById('mmConfigBack');
|
675 |
this.modeSwitch = document.getElementById('mmModeSwitch');
|
676 |
+
this.systemPromptToggle = document.getElementById('mmSystemPromptToggle');
|
677 |
+
this.systemPromptModal = document.getElementById('mmSystemPromptModal');
|
678 |
+
this.systemPromptClose = document.getElementById('mmSystemPromptClose');
|
679 |
+
this.systemPromptText = document.getElementById('mmSystemPromptText');
|
680 |
|
681 |
this.configElements = {
|
682 |
isPaidUser: document.getElementById('isPaidUser'),
|
|
|
684 |
hasQuickie: document.getElementById('hasQuickie'),
|
685 |
hasBlog: document.getElementById('hasBlog'),
|
686 |
hasEdm: document.getElementById('hasEdm'),
|
|
|
687 |
hasHc: document.getElementById('hasHc'),
|
688 |
hasGoogleSearch: document.getElementById('hasGoogleSearch')
|
689 |
};
|
|
|
697 |
this.sendBtn.addEventListener('click', () => this.sendMessage());
|
698 |
this.configToggle.addEventListener('click', () => this.toggleConfig());
|
699 |
this.configBack.addEventListener('click', () => this.toggleConfig());
|
700 |
+
this.systemPromptToggle.addEventListener('click', () => this.toggleSystemPrompt());
|
701 |
+
this.systemPromptClose.addEventListener('click', () => this.closeSystemPrompt());
|
702 |
+
this.systemPromptModal.addEventListener('click', (e) => {
|
703 |
+
if (e.target === this.systemPromptModal) {
|
704 |
+
this.closeSystemPrompt();
|
705 |
+
}
|
706 |
+
});
|
707 |
|
708 |
this.input.addEventListener('keydown', (e) => {
|
709 |
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
790 |
this.isOpen = false;
|
791 |
this.panel.classList.remove('open');
|
792 |
this.configPanel.classList.remove('open');
|
793 |
+
this.systemPromptModal.classList.remove('open');
|
794 |
}
|
795 |
|
796 |
toggleConfig() {
|
|
|
807 |
}
|
808 |
}
|
809 |
|
810 |
+
async toggleSystemPrompt() {
|
811 |
+
const isOpen = this.systemPromptModal.classList.contains('open');
|
812 |
+
if (isOpen) {
|
813 |
+
this.closeSystemPrompt();
|
814 |
+
} else {
|
815 |
+
await this.openSystemPrompt();
|
816 |
+
}
|
817 |
+
}
|
818 |
+
|
819 |
+
async openSystemPrompt() {
|
820 |
+
this.systemPromptModal.classList.add('open');
|
821 |
+
await this.loadSystemPrompt();
|
822 |
+
}
|
823 |
+
|
824 |
+
closeSystemPrompt() {
|
825 |
+
this.systemPromptModal.classList.remove('open');
|
826 |
+
}
|
827 |
+
|
828 |
+
async loadSystemPrompt() {
|
829 |
+
try {
|
830 |
+
this.systemPromptText.textContent = 'Loading system prompt...';
|
831 |
+
|
832 |
+
const response = await fetch(`${this.apiUrl}/system-prompt`, {
|
833 |
+
method: 'POST',
|
834 |
+
headers: {
|
835 |
+
'Content-Type': 'application/json',
|
836 |
+
},
|
837 |
+
body: JSON.stringify({
|
838 |
+
message: this.input.value.trim() || 'Sample message',
|
839 |
+
config: this.getConfig()
|
840 |
+
})
|
841 |
+
});
|
842 |
+
|
843 |
+
if (!response.ok) {
|
844 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
845 |
+
}
|
846 |
+
|
847 |
+
const data = await response.json();
|
848 |
+
this.systemPromptText.textContent = data.system_prompt;
|
849 |
+
|
850 |
+
} catch (error) {
|
851 |
+
console.error('System prompt error:', error);
|
852 |
+
this.systemPromptText.textContent = 'Error loading system prompt. Please try again.';
|
853 |
+
}
|
854 |
+
}
|
855 |
+
|
856 |
getConfig() {
|
857 |
return {
|
858 |
is_paid_user: this.configElements.isPaidUser.checked,
|
|
|
860 |
has_quickie: this.configElements.hasQuickie.checked,
|
861 |
has_blog: this.configElements.hasBlog.checked,
|
862 |
has_edm: this.configElements.hasEdm.checked,
|
|
|
863 |
has_hc: this.configElements.hasHc.checked,
|
864 |
has_google_search: this.configElements.hasGoogleSearch.checked
|
865 |
};
|