maltose1 commited on
Commit
92c9b07
·
verified ·
1 Parent(s): 46c4438

Upload 61 files

Browse files
.gitignore ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ config.json
133
+ !config.example.json
134
+ .venv
135
+ env/
136
+ venv/
137
+ ENV/
138
+ env.bak/
139
+ venv.bak/
140
+
141
+ # Spyder project settings
142
+ .spyderproject
143
+ .spyproject
144
+
145
+ # Rope project settings
146
+ .ropeproject
147
+
148
+ # mkdocs documentation
149
+ /site
150
+
151
+ # mypy
152
+ .mypy_cache/
153
+ .dmypy.json
154
+ dmypy.json
155
+
156
+ # Pyre type checker
157
+ .pyre/
158
+
159
+ # pytype static type analyzer
160
+ .pytype/
161
+
162
+ # Cython debug symbols
163
+ cython_debug/
164
+
165
+ # PyCharm
166
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
169
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
170
+ #.idea/
171
+
172
+ # Ruff stuff:
173
+ .ruff_cache/
174
+
175
+ # PyPI configuration file
176
+ .pypirc
177
+
178
+ # 配置文件和密钥
179
+ config.json
180
+ *.key
181
+ *.pem
182
+ *.env
183
+ .env
184
+ secrets.json
185
+
186
+ # Discord 令牌和API密钥
187
+ token.txt
188
+ *_key.txt
189
+ *_token.txt
190
+ gemini_keys.txt
191
+ openai_key.txt
192
+
193
+ # 缓存目录和文件
194
+ __pycache__/
195
+ *.py[cod]
196
+ *$py.class
197
+ .pytest_cache/
198
+ .coverage
199
+ htmlcov/
200
+ .tox/
201
+ .nox/
202
+ .hypothesis/
203
+ .coverage
204
+ .coverage.*
205
+
206
+ # 虚拟环境
207
+ venv/
208
+ env/
209
+ ENV/
210
+ .venv/
211
+ .virtualenv/
212
+
213
+ # 日志文件
214
+ *.log
215
+ logs/
216
+ npm-debug.log*
217
+
218
+ # 临时文件
219
+ *.tmp
220
+ *.bak
221
+ *.swp
222
+ *~
223
+ temp/
224
+ tmp/
225
+
226
+ # 操作系统生成的文件
227
+ .DS_Store
228
+ Thumbs.db
229
+ ehthumbs.db
230
+ Desktop.ini
231
+ $RECYCLE.BIN/
232
+
233
+ # IDE和编辑器文件
234
+ .idea/
235
+ .vscode/
236
+ *.sublime-project
237
+ *.sublime-workspace
238
+ .project
239
+ .classpath
240
+ .settings/
241
+ *.suo
242
+ *.ntvs*
243
+ *.njsproj
244
+ *.sln
245
+ *.sw?
246
+
247
+ # 用户上传的内容和缓存
248
+ uploads/
249
+ cache/
250
+ agent/
251
+
252
+ # 数据库文件
253
+ *.sqlite
254
+ *.sqlite3
255
+ *.db
256
+
257
+ # 忽略预设文件
258
+ presets/*
259
+ !presets/default/
260
+ !presets/anon/
cogs/__pycache__/admin.cpython-313.pyc CHANGED
Binary files a/cogs/__pycache__/admin.cpython-313.pyc and b/cogs/__pycache__/admin.cpython-313.pyc differ
 
cogs/__pycache__/gemini.cpython-313.pyc CHANGED
Binary files a/cogs/__pycache__/gemini.cpython-313.pyc and b/cogs/__pycache__/gemini.cpython-313.pyc differ
 
cogs/__pycache__/openai.cpython-313.pyc CHANGED
Binary files a/cogs/__pycache__/openai.cpython-313.pyc and b/cogs/__pycache__/openai.cpython-313.pyc differ
 
cogs/gemini.py CHANGED
@@ -33,6 +33,17 @@ class Gemini(commands.Cog):
33
  self.config = config
34
  self.context_length = 20
35
  self.target_language = config.get("target_language")
 
 
 
 
 
 
 
 
 
 
 
36
  self.default_gemini_config = types.GenerateContentConfig(
37
  system_instruction="",
38
  top_k=55,
@@ -74,7 +85,24 @@ class Gemini(commands.Cog):
74
  """更新聊天频道配置"""
75
  # 获取所有服务器配置
76
  self.servers = config.get("servers", {})
77
- print(f"Gemini cog 已更新服务器配置")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def get_channel_config(self, guild_id: str, channel_id: str):
80
  """获取频道配置"""
@@ -418,10 +446,14 @@ class Gemini(commands.Cog):
418
  if extra_attachment:
419
  print(f"处理附件: {extra_attachment.filename} ({extra_attachment.content_type})")
420
 
 
 
 
421
  # 发送请求
422
  await self.request_gemini(
423
  ctx,
424
  question, # 直接传递原始问题,预设处理在request_gemini中完成
 
425
  extra_attachment=extra_attachment,
426
  )
427
 
@@ -451,6 +483,9 @@ class Gemini(commands.Cog):
451
  if target_language is None:
452
  target_language = self.target_language
453
 
 
 
 
454
  # 尝试获取翻译预设
455
  agent_manager = self.bot.get_cog("AgentManager")
456
  preset_data = None
@@ -515,198 +550,292 @@ class Gemini(commands.Cog):
515
  reference_user_display_name = reference_message.author.display_name
516
  reference_content = reference_message.content
517
 
518
- # 替换预设中的变量
 
519
  first_user_message = preset_data.get("first_user_message", "")
520
- first_user_message = first_user_message.replace("{context}", context)
521
- first_user_message = first_user_message.replace("{target_language}", target_language)
522
- first_user_message = first_user_message.replace("{reference_content}", reference_content)
523
- first_user_message = first_user_message.replace("{reference_time}", reference_time)
524
- first_user_message = first_user_message.replace("{reference_user_name}", reference_user_name)
525
- first_user_message = first_user_message.replace("{reference_user_display_name}", reference_user_display_name)
526
- first_user_message = first_user_message.replace("{name}", bot_display_name)
527
- first_user_message = first_user_message.replace("{bot_name}", bot_name)
528
- first_user_message = first_user_message.replace("{current_time}", current_time)
529
- first_user_message = first_user_message.replace("{user_display_name}", user_display_name)
530
- first_user_message = first_user_message.replace("{user_name}", user_name)
531
 
532
- main_content = preset_data.get("main_content", "")
533
- main_content = main_content.replace("{context}", context)
534
- main_content = main_content.replace("{target_language}", target_language)
535
- main_content = main_content.replace("{reference_content}", reference_content)
536
- main_content = main_content.replace("{reference_time}", reference_time)
537
- main_content = main_content.replace("{reference_user_name}", reference_user_name)
538
- main_content = main_content.replace("{reference_user_display_name}", reference_user_display_name)
539
- main_content = main_content.replace("{name}", bot_display_name)
540
- main_content = main_content.replace("{bot_name}", bot_name)
541
- main_content = main_content.replace("{current_time}", current_time)
542
- main_content = main_content.replace("{user_display_name}", user_display_name)
543
- main_content = main_content.replace("{user_name}", user_name)
544
-
545
- last_user_message = preset_data.get("last_user_message", "")
546
- last_user_message = last_user_message.replace("{context}", context)
547
- last_user_message = last_user_message.replace("{target_language}", target_language)
548
- last_user_message = last_user_message.replace("{reference_content}", reference_content)
549
- last_user_message = last_user_message.replace("{reference_time}", reference_time)
550
- last_user_message = last_user_message.replace("{reference_user_name}", reference_user_name)
551
- last_user_message = last_user_message.replace("{reference_user_display_name}", reference_user_display_name)
552
- last_user_message = last_user_message.replace("{name}", bot_display_name)
553
- last_user_message = last_user_message.replace("{bot_name}", bot_name)
554
- last_user_message = last_user_message.replace("{current_time}", current_time)
555
- last_user_message = last_user_message.replace("{user_display_name}", user_display_name)
556
- last_user_message = last_user_message.replace("{user_name}", user_name)
557
-
558
- # 构建user-model-user的三个上下文
559
- contents = [
560
- types.Content(
561
- role="user",
562
- parts=[
563
- types.Part.from_text(text=first_user_message),
564
- ],
565
- ),
566
- types.Content(
567
- role="model",
568
- parts=[
569
- types.Part.from_text(text=main_content),
570
- ],
571
- ),
572
- types.Content(
573
- role="user",
574
- parts=[
575
- types.Part.from_text(text=last_user_message),
576
- ],
577
- ),
578
- ]
579
 
580
- # 如果有附件,添加到最后一个用户消息中
581
- if attachment_bytes:
582
- # 使用Pillow和inline_data方式添加图片
583
- image_bytes = BytesIO(attachment_bytes)
584
- image = PIL.Image.open(image_bytes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
585
 
586
- # 转换为字节数据
587
- mime_type = attachment_mime_type or "image/jpeg"
588
- img_byte_arr = BytesIO()
589
- image.save(img_byte_arr, format=image.format or "JPEG")
590
- img_byte_data = img_byte_arr.getvalue()
 
 
 
 
 
 
 
591
 
592
- # 添加到消息中
593
- contents[2].parts.append(
594
- types.Part(
595
- inline_data=types.Blob(
596
- mime_type=mime_type,
597
- data=img_byte_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  )
599
  )
600
- )
601
- print("附件已添加到翻译请求中")
602
-
603
- # 设置安全设置
604
- safety_settings = [
605
- types.SafetySetting(
606
- category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
607
- threshold=types.HarmBlockThreshold.OFF,
608
- ),
609
- types.SafetySetting(
610
- category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
611
- threshold=types.HarmBlockThreshold.OFF,
612
- ),
613
- types.SafetySetting(
614
- category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
615
- threshold=types.HarmBlockThreshold.OFF,
616
- ),
617
- types.SafetySetting(
618
- category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
619
- threshold=types.HarmBlockThreshold.OFF,
620
- ),
621
- types.SafetySetting(
622
- category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
623
- threshold=types.HarmBlockThreshold.OFF,
624
- ),
625
- ]
626
-
627
- # 获取Gemini配置
628
- gemini_config_data = None
629
- if agent_manager:
630
- gemini_config_data = agent_manager.get_preset_json("gemini_config.json", channel_id)
631
-
632
- # 构建配置
633
- generate_content_config = types.GenerateContentConfig(
634
- temperature=gemini_config_data.get("temperature", 1.0) if gemini_config_data else 1.0,
635
- top_p=gemini_config_data.get("top_p", 0.95) if gemini_config_data else 0.95,
636
- top_k=gemini_config_data.get("top_k", 64) if gemini_config_data else 64,
637
- max_output_tokens=gemini_config_data.get("max_output_tokens", 8192) if gemini_config_data else 8192,
638
- safety_settings=safety_settings,
639
- response_mime_type="text/plain",
640
- system_instruction=[
641
- types.Part.from_text(text=preset_data.get("system_prompt", "")),
642
- ],
643
- )
644
-
645
- # 使用已经存在的msg变量,而不是创建新消息
646
- # 如���之前没有创建消息(例如没有附件),现在才创建
647
- if 'msg' not in locals() or msg is None:
648
- msg = await ctx.send("Translating...")
649
 
650
- full = ""
651
- n = config.get("gemini_chunk_per_edit")
652
- every_n_chunk = 1
653
-
654
- try:
655
- # 记录翻译请求内容
656
- log_contents = []
657
- for content in contents:
658
- parts_text = []
659
- for part in content.parts:
660
- if hasattr(part, "text") and part.text:
661
- parts_text.append(f"Text: {part.text}")
662
- else:
663
- parts_text.append(f"Unknown part type: {type(part)}")
664
- log_contents.append(f"Role: {content.role}, Parts: {parts_text}")
 
 
 
 
 
 
 
 
665
 
666
- system_instruction = "None"
667
- if hasattr(generate_content_config, "system_instruction"):
668
- if generate_content_config.system_instruction:
669
- system_instruction = generate_content_config.system_instruction[0].text if generate_content_config.system_instruction else "None"
670
 
671
- # 只记录到日志文件,不再重复打印到控制台
672
- logger.info(
673
- "Gemini翻译请求发送: 模型=gemini-2.0-pro-exp-02-05, 内容=%s, 系统提示=%s, 配置=%s",
674
- log_contents,
675
- system_instruction,
676
- {
677
- "temperature": generate_content_config.temperature,
678
- "top_p": generate_content_config.top_p,
679
- "top_k": generate_content_config.top_k,
680
- "max_tokens": generate_content_config.max_output_tokens,
681
- }
682
  )
683
 
684
- response = client.models.generate_content_stream(
685
- model="gemini-2.0-pro-exp-02-05",
686
- contents=contents,
687
- config=generate_content_config,
688
- )
689
 
690
- async for chunk in async_iter(response):
691
- if chunk.text:
692
- full += chunk.text
693
- if every_n_chunk == n:
694
- await msg.edit(content=full)
695
- every_n_chunk = 1
696
- else:
697
- every_n_chunk += 1
698
- await msg.edit(content=full)
699
- except Exception as e:
700
- logger.error(
701
- "Error when translating with gemini, error: %s",
702
- e,
703
- exc_info=True,
704
- )
705
- if full == "":
706
- await msg.edit(content="Uh oh, something went wrong...")
707
- else:
708
- full += "\nUh oh, something went wrong..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  await msg.edit(content=full)
 
 
 
 
 
 
 
 
 
 
 
710
  else:
711
  # 预设数据不存在,显示错误信息
712
  await ctx.send("无法加载翻译预设,请联系管理员", delete_after=5, ephemeral=True)
@@ -741,6 +870,78 @@ class Gemini(commands.Cog):
741
  except Exception as e:
742
  await ctx.send(f"Invalid timezone.", ephemeral=True, delete_after=5)
743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
  async def setup(bot: commands.Bot):
746
  apikeys = config.get("gemini_keys")
 
33
  self.config = config
34
  self.context_length = 20
35
  self.target_language = config.get("target_language")
36
+
37
+ # 获取Gemini模型配置
38
+ self.gemini_models = config.get("gemini_models", {
39
+ "chat": "gemini-2.0-pro-exp-02-05", # 默认聊天模型
40
+ "translate": "gemini-2.0-pro-exp-02-05" # 默认翻译模型
41
+ })
42
+
43
+ # 如果配置中没有gemini_models,写入默认配置
44
+ if not config.get("gemini_models"):
45
+ config.write("gemini_models", self.gemini_models)
46
+
47
  self.default_gemini_config = types.GenerateContentConfig(
48
  system_instruction="",
49
  top_k=55,
 
85
  """更新聊天频道配置"""
86
  # 获取所有服务器配置
87
  self.servers = config.get("servers", {})
88
+
89
+ # 更新Gemini模型配置
90
+ self.gemini_models = config.get("gemini_models", {
91
+ "chat": "gemini-2.0-pro-exp-02-05",
92
+ "translate": "gemini-2.0-pro-exp-02-05"
93
+ })
94
+
95
+ # 获取可用的Gemini模型列表
96
+ self.available_models = config.get("gemini_available_models", [])
97
+ if not self.available_models:
98
+ # 设置默认模型列表
99
+ self.available_models = [
100
+ {"name": "gemini-2.0-pro-exp-02-05", "description": "默认聊天和翻译模型"},
101
+ {"name": "gemini-pro", "description": "旧版Gemini Pro模型"}
102
+ ]
103
+ config.write("gemini_available_models", self.available_models)
104
+
105
+ print(f"Gemini cog 已更新服务器配置和模型列表")
106
 
107
  def get_channel_config(self, guild_id: str, channel_id: str):
108
  """获取频道配置"""
 
446
  if extra_attachment:
447
  print(f"处理附件: {extra_attachment.filename} ({extra_attachment.content_type})")
448
 
449
+ # 使用聊天模型
450
+ chat_model = self.gemini_models.get("chat", "gemini-2.0-pro-exp-02-05")
451
+
452
  # 发送请求
453
  await self.request_gemini(
454
  ctx,
455
  question, # 直接传递原始问题,预设处理在request_gemini中完成
456
+ model=chat_model,
457
  extra_attachment=extra_attachment,
458
  )
459
 
 
483
  if target_language is None:
484
  target_language = self.target_language
485
 
486
+ # 使用翻译模型
487
+ translate_model = self.gemini_models.get("translate", "gemini-2.0-pro-exp-02-05")
488
+
489
  # 尝试获取翻译预设
490
  agent_manager = self.bot.get_cog("AgentManager")
491
  preset_data = None
 
550
  reference_user_display_name = reference_message.author.display_name
551
  reference_content = reference_message.content
552
 
553
+ # 使用预设JSON结构
554
+ system_prompt = preset_data.get("system_prompt", "")
555
  first_user_message = preset_data.get("first_user_message", "")
 
 
 
 
 
 
 
 
 
 
 
556
 
557
+ # 确保模型已设置且处于传递中
558
+ translate_model = self.gemini_models.get("translate", "gemini-2.0-pro-exp-02-05")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
 
560
+ # 处理不同内容格式情况
561
+ if attachment_bytes and attachment_mime_type:
562
+ # 如果有图片附件
563
+ try:
564
+ # 将字节转换为PIL图像以供Gemini处理
565
+ if attachment_mime_type.startswith("image/"):
566
+ image = PIL.Image.open(BytesIO(attachment_bytes))
567
+
568
+ # 按照Gemini推荐尺寸调整图片,适应大多数模型版本
569
+ if max(image.size) > 3072:
570
+ ratio = 3072 / max(image.size)
571
+ new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio))
572
+ image = image.resize(new_size, PIL.Image.LANCZOS)
573
+
574
+ # 构建内容
575
+ content = [
576
+ {
577
+ "role": "user",
578
+ "parts": [
579
+ types.Part.from_text(first_user_message),
580
+ types.Part.from_image(image),
581
+ ]
582
+ }
583
+ ]
584
+
585
+ # 获取安全设置
586
+ safety_settings = []
587
+ try:
588
+ gemini_config_data = None
589
+ if agent_manager:
590
+ gemini_config_data = agent_manager.get_preset_json("gemini_config.json")
591
+
592
+ if gemini_config_data and "safety_settings" in gemini_config_data:
593
+ safety_settings = self.build_safety_settings(gemini_config_data["safety_settings"])
594
+ except Exception as e:
595
+ logger.error(f"Error loading safety settings: {e}")
596
+ safety_settings = []
597
+
598
+ # 构建和应用生成内容配置
599
+ generate_content_config = types.GenerateContentConfig(
600
+ temperature=0.2,
601
+ top_p=0.95,
602
+ top_k=55,
603
+ max_output_tokens=8192,
604
+ safety_settings=safety_settings,
605
+ response_mime_type="text/plain",
606
+ system_instruction=[
607
+ types.Part.from_text(text=system_prompt),
608
+ ],
609
+ )
610
+
611
+ # 使用流式响应
612
+ full = ""
613
+ n = config.get("gemini_chunk_per_edit")
614
+ every_n_chunk = 1
615
+
616
+ try:
617
+ response = client.models.generate_content_stream(
618
+ model=translate_model, # 使用翻译模型
619
+ contents=content,
620
+ config=generate_content_config,
621
+ )
622
+
623
+ async for chunk in async_iter(response):
624
+ if chunk.text:
625
+ full += chunk.text
626
+ if every_n_chunk == n:
627
+ await msg.edit(content=full)
628
+ every_n_chunk = 1
629
+ else:
630
+ every_n_chunk += 1
631
+ await msg.edit(content=full)
632
+ except Exception as e:
633
+ logger.error(
634
+ "Error when translating with gemini, error: %s",
635
+ e,
636
+ exc_info=True,
637
+ )
638
+ if full == "":
639
+ await msg.edit(content="Uh oh, something went wrong...")
640
+ else:
641
+ full += "\nUh oh, something went wrong..."
642
+ await msg.edit(content=full)
643
+ except Exception as e:
644
+ logger.error(f"Error processing image: {e}")
645
+ await ctx.send("无法处理图片,请检查附件格式", ephemeral=True)
646
+ else:
647
+ # 替换预设中的变量
648
+ first_user_message = preset_data.get("first_user_message", "")
649
+ first_user_message = first_user_message.replace("{context}", context)
650
+ first_user_message = first_user_message.replace("{target_language}", target_language)
651
+ first_user_message = first_user_message.replace("{reference_content}", reference_content)
652
+ first_user_message = first_user_message.replace("{reference_time}", reference_time)
653
+ first_user_message = first_user_message.replace("{reference_user_name}", reference_user_name)
654
+ first_user_message = first_user_message.replace("{reference_user_display_name}", reference_user_display_name)
655
+ first_user_message = first_user_message.replace("{name}", bot_display_name)
656
+ first_user_message = first_user_message.replace("{bot_name}", bot_name)
657
+ first_user_message = first_user_message.replace("{current_time}", current_time)
658
+ first_user_message = first_user_message.replace("{user_display_name}", user_display_name)
659
+ first_user_message = first_user_message.replace("{user_name}", user_name)
660
 
661
+ main_content = preset_data.get("main_content", "")
662
+ main_content = main_content.replace("{context}", context)
663
+ main_content = main_content.replace("{target_language}", target_language)
664
+ main_content = main_content.replace("{reference_content}", reference_content)
665
+ main_content = main_content.replace("{reference_time}", reference_time)
666
+ main_content = main_content.replace("{reference_user_name}", reference_user_name)
667
+ main_content = main_content.replace("{reference_user_display_name}", reference_user_display_name)
668
+ main_content = main_content.replace("{name}", bot_display_name)
669
+ main_content = main_content.replace("{bot_name}", bot_name)
670
+ main_content = main_content.replace("{current_time}", current_time)
671
+ main_content = main_content.replace("{user_display_name}", user_display_name)
672
+ main_content = main_content.replace("{user_name}", user_name)
673
 
674
+ last_user_message = preset_data.get("last_user_message", "")
675
+ last_user_message = last_user_message.replace("{context}", context)
676
+ last_user_message = last_user_message.replace("{target_language}", target_language)
677
+ last_user_message = last_user_message.replace("{reference_content}", reference_content)
678
+ last_user_message = last_user_message.replace("{reference_time}", reference_time)
679
+ last_user_message = last_user_message.replace("{reference_user_name}", reference_user_name)
680
+ last_user_message = last_user_message.replace("{reference_user_display_name}", reference_user_display_name)
681
+ last_user_message = last_user_message.replace("{name}", bot_display_name)
682
+ last_user_message = last_user_message.replace("{bot_name}", bot_name)
683
+ last_user_message = last_user_message.replace("{current_time}", current_time)
684
+ last_user_message = last_user_message.replace("{user_display_name}", user_display_name)
685
+ last_user_message = last_user_message.replace("{user_name}", user_name)
686
+
687
+ # 构建user-model-user的三个上下文
688
+ contents = [
689
+ types.Content(
690
+ role="user",
691
+ parts=[
692
+ types.Part.from_text(text=first_user_message),
693
+ ],
694
+ ),
695
+ types.Content(
696
+ role="model",
697
+ parts=[
698
+ types.Part.from_text(text=main_content),
699
+ ],
700
+ ),
701
+ types.Content(
702
+ role="user",
703
+ parts=[
704
+ types.Part.from_text(text=last_user_message),
705
+ ],
706
+ ),
707
+ ]
708
+
709
+ # 如果有附件,添加到最后一个用户消息中
710
+ if attachment_bytes:
711
+ # 使用Pillow和inline_data方式添加图片
712
+ image_bytes = BytesIO(attachment_bytes)
713
+ image = PIL.Image.open(image_bytes)
714
+
715
+ # 转换为字节数据
716
+ mime_type = attachment_mime_type or "image/jpeg"
717
+ img_byte_arr = BytesIO()
718
+ image.save(img_byte_arr, format=image.format or "JPEG")
719
+ img_byte_data = img_byte_arr.getvalue()
720
+
721
+ # 添加到消息中
722
+ contents[2].parts.append(
723
+ types.Part(
724
+ inline_data=types.Blob(
725
+ mime_type=mime_type,
726
+ data=img_byte_data
727
+ )
728
  )
729
  )
730
+ print("附件已添加到翻译请求中")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
 
732
+ # 设置安全设置
733
+ safety_settings = [
734
+ types.SafetySetting(
735
+ category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
736
+ threshold=types.HarmBlockThreshold.OFF,
737
+ ),
738
+ types.SafetySetting(
739
+ category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
740
+ threshold=types.HarmBlockThreshold.OFF,
741
+ ),
742
+ types.SafetySetting(
743
+ category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
744
+ threshold=types.HarmBlockThreshold.OFF,
745
+ ),
746
+ types.SafetySetting(
747
+ category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
748
+ threshold=types.HarmBlockThreshold.OFF,
749
+ ),
750
+ types.SafetySetting(
751
+ category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
752
+ threshold=types.HarmBlockThreshold.OFF,
753
+ ),
754
+ ]
755
 
756
+ # 获取Gemini配置
757
+ gemini_config_data = None
758
+ if agent_manager:
759
+ gemini_config_data = agent_manager.get_preset_json("gemini_config.json", channel_id)
760
 
761
+ # 构建配置
762
+ generate_content_config = types.GenerateContentConfig(
763
+ temperature=gemini_config_data.get("temperature", 1.0) if gemini_config_data else 1.0,
764
+ top_p=gemini_config_data.get("top_p", 0.95) if gemini_config_data else 0.95,
765
+ top_k=gemini_config_data.get("top_k", 64) if gemini_config_data else 64,
766
+ max_output_tokens=gemini_config_data.get("max_output_tokens", 8192) if gemini_config_data else 8192,
767
+ safety_settings=safety_settings,
768
+ response_mime_type="text/plain",
769
+ system_instruction=[
770
+ types.Part.from_text(text=preset_data.get("system_prompt", "")),
771
+ ],
772
  )
773
 
774
+ # 使用已经存在的msg变量,而不是创建新消息
775
+ # 如果之前没有创建消息(例如没有附件),现在才创建
776
+ if 'msg' not in locals() or msg is None:
777
+ msg = await ctx.send("Translating...")
 
778
 
779
+ full = ""
780
+ n = config.get("gemini_chunk_per_edit")
781
+ every_n_chunk = 1
782
+
783
+ try:
784
+ # 记录翻译请求内容
785
+ log_contents = []
786
+ for content in contents:
787
+ parts_text = []
788
+ for part in content.parts:
789
+ if hasattr(part, "text") and part.text:
790
+ parts_text.append(f"Text: {part.text}")
791
+ else:
792
+ parts_text.append(f"Unknown part type: {type(part)}")
793
+ log_contents.append(f"Role: {content.role}, Parts: {parts_text}")
794
+
795
+ system_instruction = "None"
796
+ if hasattr(generate_content_config, "system_instruction"):
797
+ if generate_content_config.system_instruction:
798
+ system_instruction = generate_content_config.system_instruction[0].text if generate_content_config.system_instruction else "None"
799
+
800
+ # 只记录到日志文件,不再重复打印到控制台
801
+ logger.info(
802
+ "Gemini翻译请求发送: 模型=gemini-2.0-pro-exp-02-05, 内容=%s, 系统提示=%s, 配置=%s",
803
+ log_contents,
804
+ system_instruction,
805
+ {
806
+ "temperature": generate_content_config.temperature,
807
+ "top_p": generate_content_config.top_p,
808
+ "top_k": generate_content_config.top_k,
809
+ "max_tokens": generate_content_config.max_output_tokens,
810
+ }
811
+ )
812
+
813
+ response = client.models.generate_content_stream(
814
+ model=translate_model, # 使用翻译模型
815
+ contents=contents,
816
+ config=generate_content_config,
817
+ )
818
+
819
+ async for chunk in async_iter(response):
820
+ if chunk.text:
821
+ full += chunk.text
822
+ if every_n_chunk == n:
823
+ await msg.edit(content=full)
824
+ every_n_chunk = 1
825
+ else:
826
+ every_n_chunk += 1
827
  await msg.edit(content=full)
828
+ except Exception as e:
829
+ logger.error(
830
+ "Error when translating with gemini, error: %s",
831
+ e,
832
+ exc_info=True,
833
+ )
834
+ if full == "":
835
+ await msg.edit(content="Uh oh, something went wrong...")
836
+ else:
837
+ full += "\nUh oh, something went wrong..."
838
+ await msg.edit(content=full)
839
  else:
840
  # 预设数据不存在,显示错误信息
841
  await ctx.send("无法加载翻译预设,请联系管理员", delete_after=5, ephemeral=True)
 
870
  except Exception as e:
871
  await ctx.send(f"Invalid timezone.", ephemeral=True, delete_after=5)
872
 
873
+ @commands.hybrid_command(name="models", description="列出或切换Gemini模型")
874
+ @commands.has_permissions(administrator=True)
875
+ async def models(self, ctx: commands.Context, model_type: str = None, model_name: str = None):
876
+ """列出或切换Gemini模型
877
+
878
+ 参数:
879
+ model_type: 模型类型(chat或translate)
880
+ model_name: 模型名称
881
+ """
882
+ if not model_type:
883
+ # 如果没有指定模型类型,列出当前使用的模型和所有可用模型
884
+ embed = discord.Embed(
885
+ title="Gemini模型配置",
886
+ description="当前使用的Gemini模型",
887
+ color=discord.Color.blue()
888
+ )
889
+
890
+ chat_model = self.gemini_models.get("chat", "gemini-2.0-pro-exp-02-05")
891
+ translate_model = self.gemini_models.get("translate", "gemini-2.0-pro-exp-02-05")
892
+
893
+ embed.add_field(name="聊天模型", value=f"`{chat_model}`", inline=False)
894
+ embed.add_field(name="翻译模型", value=f"`{translate_model}`", inline=False)
895
+
896
+ # 添加可用模型列表
897
+ available_models_text = ""
898
+ for model in self.available_models:
899
+ model_name = model.get("name", "")
900
+ model_desc = model.get("description", "")
901
+ if model_desc:
902
+ available_models_text += f"• `{model_name}` - {model_desc}\n"
903
+ else:
904
+ available_models_text += f"• `{model_name}`\n"
905
+
906
+ if available_models_text:
907
+ embed.add_field(name="可用模型列表", value=available_models_text, inline=False)
908
+ else:
909
+ embed.add_field(name="可用模型列表", value="没有可用的模型", inline=False)
910
+
911
+ embed.add_field(
912
+ name="使用方法",
913
+ value="使用 `/models chat <模型名>` 更改聊天模型\n使用 `/models translate <模型名>` 更改翻译模型",
914
+ inline=False
915
+ )
916
+
917
+ await ctx.send(embed=embed)
918
+ return
919
+
920
+ if model_type not in ["chat", "translate"]:
921
+ await ctx.send("错误:模型类型必须是 `chat` 或 `translate`", ephemeral=True)
922
+ return
923
+
924
+ if not model_name:
925
+ await ctx.send(f"错误:请指定模型名称", ephemeral=True)
926
+ return
927
+
928
+ # 检查模型是否在可用列表中
929
+ model_exists = any(model.get("name") == model_name for model in self.available_models)
930
+ if not model_exists:
931
+ # 仍然允许用户设置不在列表中的模型,但显示警告
932
+ await ctx.send(f"警告:模型 `{model_name}` 不在可用模型列表中,但仍将设置为当前模型。", ephemeral=True)
933
+
934
+ # 更新模型配置
935
+ self.gemini_models[model_type] = model_name
936
+ config.write("gemini_models", self.gemini_models)
937
+
938
+ await ctx.send(f"已将{model_type}模型设置为:{model_name}", ephemeral=True)
939
+
940
+ @models.error
941
+ async def models_error(self, ctx: commands.Context, error):
942
+ if isinstance(error, commands.MissingPermissions):
943
+ await ctx.send("错误:只有管理员可以更改模型配置", ephemeral=True)
944
+
945
 
946
  async def setup(bot: commands.Bot):
947
  apikeys = config.get("gemini_keys")
cogs/openai.py CHANGED
@@ -31,6 +31,15 @@ class Openai(commands.Cog):
31
  config.write("default_openai_model", self.model)
32
 
33
  self.context_length = 20
 
 
 
 
 
 
 
 
 
34
 
35
  async def cog_load(self):
36
  """当cog被加载时调用,这是一个异步上下文"""
@@ -49,7 +58,16 @@ class Openai(commands.Cog):
49
  """更新聊天频道配置"""
50
  # 获取所有服务器配置
51
  self.servers = config.get("servers", {})
52
- print(f"OpenAI cog 已更新服务器配置")
 
 
 
 
 
 
 
 
 
53
 
54
  def get_channel_config(self, guild_id: str, channel_id: str):
55
  """获取频道配置"""
@@ -121,31 +139,13 @@ class Openai(commands.Cog):
121
  "top_p": openai_config.get("top_p", 0.95),
122
  "top_k": openai_config.get("top_k", 55),
123
  "temperature": openai_config.get("temperature", 1.0),
124
- "stream": False,
125
  }
126
 
127
  # 记录API请求数据
128
  logger.info(f"OpenAI API 请求数据: {json.dumps(data, ensure_ascii=False, indent=2)}")
129
 
130
- try:
131
- # 创建新的ClientSession用于API请求
132
- async with ClientSession() as session:
133
- # 使用会话进行API请求
134
- async with session.post(url, headers=headers, json=data) as resp:
135
- resp.raise_for_status()
136
- response_data = await resp.json()
137
-
138
- # 从响应中提取回复内容
139
- if "choices" in response_data and len(response_data["choices"]) > 0:
140
- content = response_data["choices"][0]["message"]["content"]
141
- if not content or content.strip() == "":
142
- raise ValueError("API返回了空响应")
143
- return content
144
- else:
145
- raise ValueError("API返回的响应格式不正确")
146
- except Exception as e:
147
- logger.error(f"OpenAI API请求失败: {str(e)}")
148
- raise ValueError(f"OpenAI API请求失败: {str(e)}")
149
 
150
  @commands.hybrid_command(name="yo", description="Chat with OpenAI models.")
151
  async def yo(
@@ -283,19 +283,68 @@ class Openai(commands.Cog):
283
  async def stream_openai_response(self, model: str, prompt: str, username: str, message: discord.Message, channel_id=None, guild_id=None, webhook=None):
284
  """流式生成并更新OpenAI响应"""
285
  try:
286
- # 获取完整响应
287
- response = await self.request_openai(model, prompt, username, guild_id, channel_id)
288
-
289
- # 检查响应是否为空
290
- if not response or response.strip() == "":
291
- response = "抱歉,我现在无法生成回复。请稍后再试。"
292
 
293
- # 更新消息
294
- if webhook and isinstance(message, discord.WebhookMessage):
295
- # 不再传递avatar_url,使用默认头像
296
- await message.edit(content=response)
297
- else:
298
- await message.edit(content=response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
  except Exception as e:
301
  error_msg = f"生成响应时发生错误: {str(e)}"
@@ -344,6 +393,88 @@ class Openai(commands.Cog):
344
  if isinstance(error, commands.MissingPermissions):
345
  await ctx.send("错误:只有管理员可以更改默认模型", ephemeral=True)
346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
  async def setup(bot: commands.Bot):
349
  openai_key = config.get("openai_key", "")
 
31
  config.write("default_openai_model", self.model)
32
 
33
  self.context_length = 20
34
+
35
+ # 构建自定义触发指令到模型的映射
36
+ self.trigger_to_model = {}
37
+ for model_name, model_config in self.models.items():
38
+ custom_trigger = model_config.get("custom_trigger", "")
39
+ if custom_trigger:
40
+ self.trigger_to_model[custom_trigger] = model_name
41
+
42
+ logger.info(f"OpenAI cog 已加载自定义触发指令映射: {self.trigger_to_model}")
43
 
44
  async def cog_load(self):
45
  """当cog被加载时调用,这是一个异步上下文"""
 
58
  """更新聊天频道配置"""
59
  # 获取所有服务器配置
60
  self.servers = config.get("servers", {})
61
+
62
+ # 重新获取模型配置并更新自定义触发指令映射
63
+ self.models = config.get("openai_models", {})
64
+ self.trigger_to_model = {}
65
+ for model_name, model_config in self.models.items():
66
+ custom_trigger = model_config.get("custom_trigger", "")
67
+ if custom_trigger:
68
+ self.trigger_to_model[custom_trigger] = model_name
69
+
70
+ logger.info(f"OpenAI cog 已更新服务器配置和自定义触发指令映射: {self.trigger_to_model}")
71
 
72
  def get_channel_config(self, guild_id: str, channel_id: str):
73
  """获取频道配置"""
 
139
  "top_p": openai_config.get("top_p", 0.95),
140
  "top_k": openai_config.get("top_k", 55),
141
  "temperature": openai_config.get("temperature", 1.0),
142
+ "stream": True,
143
  }
144
 
145
  # 记录API请求数据
146
  logger.info(f"OpenAI API 请求数据: {json.dumps(data, ensure_ascii=False, indent=2)}")
147
 
148
+ return url, headers, data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  @commands.hybrid_command(name="yo", description="Chat with OpenAI models.")
151
  async def yo(
 
283
  async def stream_openai_response(self, model: str, prompt: str, username: str, message: discord.Message, channel_id=None, guild_id=None, webhook=None):
284
  """流式生成并更新OpenAI响应"""
285
  try:
286
+ # 准备API请求参数
287
+ url, headers, data = await self.request_openai(model, prompt, username, guild_id, channel_id)
 
 
 
 
288
 
289
+ # 创建新的ClientSession用于API请求
290
+ async with ClientSession() as session:
291
+ # 使用会话进行API请求
292
+ async with session.post(url, headers=headers, json=data) as resp:
293
+ resp.raise_for_status()
294
+
295
+ # 处理流式响应
296
+ response_content = ""
297
+ # 记录上次更新时间,避免过于频繁更新消息
298
+ last_update_time = 0
299
+ # 更新间隔(秒)
300
+ update_interval = 1.0
301
+ # 获取用于块更新的配置参数
302
+ chunk_per_edit = self.models.get(model, {}).get("chunk_per_edit", 10)
303
+ # 累计的块数
304
+ chunk_count = 0
305
+
306
+ async for line in resp.content:
307
+ if not line:
308
+ continue
309
+
310
+ line = line.decode('utf-8').strip()
311
+ if line == "data: [DONE]":
312
+ break
313
+
314
+ if line.startswith("data: "):
315
+ try:
316
+ chunk_data = json.loads(line[6:])
317
+ if "choices" in chunk_data and len(chunk_data["choices"]) > 0:
318
+ delta = chunk_data["choices"][0].get("delta", {})
319
+ if "content" in delta and delta["content"]:
320
+ response_content += delta["content"]
321
+ chunk_count += 1
322
+
323
+ # 根据时间间隔和块数量决定是否更新消息
324
+ current_time = asyncio.get_event_loop().time()
325
+ if (current_time - last_update_time >= update_interval or
326
+ chunk_count >= chunk_per_edit):
327
+ if webhook and isinstance(message, discord.WebhookMessage):
328
+ await message.edit(content=response_content)
329
+ else:
330
+ await message.edit(content=response_content)
331
+ last_update_time = current_time
332
+ chunk_count = 0
333
+ except json.JSONDecodeError:
334
+ continue
335
+
336
+ # 确保最后的内容被更新
337
+ if response_content:
338
+ if webhook and isinstance(message, discord.WebhookMessage):
339
+ await message.edit(content=response_content)
340
+ else:
341
+ await message.edit(content=response_content)
342
+ elif not response_content or response_content.strip() == "":
343
+ error_msg = "抱歉,我现在无法生成回复。请稍后再试。"
344
+ if webhook and isinstance(message, discord.WebhookMessage):
345
+ await message.edit(content=error_msg)
346
+ else:
347
+ await message.edit(content=error_msg)
348
 
349
  except Exception as e:
350
  error_msg = f"生成响应时发生错误: {str(e)}"
 
393
  if isinstance(error, commands.MissingPermissions):
394
  await ctx.send("错误:只有管理员可以更改默认模型", ephemeral=True)
395
 
396
+ @commands.Cog.listener()
397
+ async def on_message(self, message):
398
+ """监听消息,处理自定义触发指令"""
399
+ # 忽略机器人消息
400
+ if message.author.bot:
401
+ return
402
+
403
+ # 检查是否在聊天频道中
404
+ guild_id = str(message.guild.id) if message.guild else None
405
+ channel_id = str(message.channel.id)
406
+
407
+ if not guild_id:
408
+ return # 忽略私信
409
+
410
+ channel_config = self.get_channel_config(guild_id, channel_id)
411
+ if not channel_config:
412
+ return # 不是聊天频道
413
+
414
+ # 获取消息内容
415
+ content = message.content.strip()
416
+
417
+ # 检查是否是自定义触发指令
418
+ for trigger, model_name in self.trigger_to_model.items():
419
+ if content.startswith(trigger):
420
+ # 提取问题
421
+ question = content[len(trigger):].strip()
422
+ if not question:
423
+ # 如果没有内容,忽略
424
+ return
425
+
426
+ # 创建上下文对象,以便复用命令逻辑
427
+ ctx = await self.bot.get_context(message)
428
+ if ctx.valid:
429
+ return # 如果是有效的命令上下文,让命令处理器处理
430
+
431
+ # 处理上下文长度
432
+ context_length = self.context_length
433
+
434
+ # 用户名设为模型名
435
+ username = model_name
436
+
437
+ # 根据是否是回复消息生成prompt
438
+ if message.reference is None:
439
+ prompt = await self.context_prompter.chat_prompt(
440
+ ctx, context_length, question, name=username
441
+ )
442
+ else:
443
+ ref_message = await message.channel.fetch_message(message.reference.message_id)
444
+ prompt = await self.context_prompter.chat_prompt_with_reference(
445
+ ctx, context_length, 5, question, ref_message, name=username
446
+ )
447
+
448
+ try:
449
+ # 使用webhook发送消息
450
+ webhook = await self.get_or_create_webhook(message.channel)
451
+
452
+ # 根据频道类型选择正确的发送方式
453
+ if isinstance(message.channel, discord.Thread):
454
+ # 发送初始消息到线程中
455
+ typing_msg = await webhook.send(
456
+ content="typing...",
457
+ username=username,
458
+ wait=True,
459
+ thread=message.channel
460
+ )
461
+ else:
462
+ # 发送到普通文字频道
463
+ typing_msg = await webhook.send(
464
+ content="typing...",
465
+ username=username,
466
+ wait=True
467
+ )
468
+
469
+ # 使用流式生成并更新消息
470
+ await self.stream_openai_response(model_name, prompt, username, typing_msg, channel_id, guild_id, webhook)
471
+ except Exception as e:
472
+ logger.error(f"自定义触发指令处理失败: {e}")
473
+ await message.reply(f"请求失败: {str(e)}", mention_author=False)
474
+
475
+ # 处理完成,退出函数
476
+ return
477
+
478
 
479
  async def setup(bot: commands.Bot):
480
  openai_key = config.get("openai_key", "")
config.json CHANGED
@@ -1,73 +1,94 @@
1
- {
2
- "token": "MTM1MzA5NjExODk5NjgzMjMyNg.G6GegN.65muNrCalIydteCElYnUpjcoqLzx2lx9GiaPtM",
3
- "webhook_url": "https://discord.com/api/webhooks/1353097385798275223/FSvIa4TYztnLUf4K20WJdVnTv_TthZGoX1FdHHSKx-Qc3ZhVgjcsFS704rK68SRpS8DF",
4
- "target_language": "Chinese",
5
- "servers": {
6
- "main": {
7
- "guild_id": "1225874935050797207",
8
- "source_channel_id": "1350113928499433502",
9
- "target_channel_id": "1350113406388142111",
10
- "main_channel_id": "1350642349000233180",
11
- "backup_channel_id": "1350547121232936980",
12
- "chat_channels": {
13
- "1350113928499433502": {
14
- "preset": "default"
15
- },
16
- "1350642349000233180": {
17
- "preset": "default"
18
- },
19
- "1353159493935956029": {
20
- "preset": "default"
21
- },
22
- "1353172097664421899": {
23
- "preset": "default"
24
- }
25
- }
26
- },
27
- "server2": {
28
- "guild_id": "1297598690734899241",
29
- "source_channel_id": "1345815626035363912",
30
- "target_channel_id": "1345815626035363912",
31
- "main_channel_id": "1346477511961346130",
32
- "backup_channel_id": "1345798271347589233",
33
- "chat_channels": {
34
- "1345815626035363912": {
35
- "preset": "default"
36
- },
37
- "1346477511961346130": {
38
- "preset": "default"
39
- },
40
- "1353087178896310433": {
41
- "preset": "default"
42
- }
43
- }
44
- }
45
- },
46
- "current_key": 3,
47
- "gemini_chunk_per_edit": 2,
48
- "gemini_keys": [
49
- "AIzaSyBKNuMl39O7a5cForM62J0wzJ9Xa5DS0_c",
50
- "AIzaSyBP12paErT-QURQBqG8CLGJjrwESJbn6KQ",
51
- "AIzaSyDwDhAABYz152pnz8nsl21qcLXdV3lrdHQ",
52
- "AIzaSyCFC7IMYoxj8Lng6GZdn6ZmXMiiAmDapfE",
53
- "AIzaSyDOpJSy2QPZKvpt0hv6lJ7gVZ-cTQsB12I",
54
- "AIzaSyCq5C93S-U2RTJmfwt5YicnFhIUY7Km80E"
55
- ],
56
- "openai_key": "sk-Xu9jqxHVLDqkrbry7hbIzyOkv5VnUrto4VBNYHvg8h6ZcZs1",
57
- "openai_endpoint": "https://esotlam-new-api.hf.space/v1",
58
- "openai_models": {
59
- "claude": {
60
- "id": "cursor/claude-3.7-sonnet",
61
- "chunk_per_edit": 10
62
- },
63
- "gpt": {
64
- "id": "gpt-4o-mini",
65
- "chunk_per_edit": 10
66
- },
67
- "grok": {
68
- "id": "grok.com/grok-3",
69
- "chunk_per_edit": 10
70
- }
71
- },
72
- "default_openai_model": "claude"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
 
1
+ {
2
+ "token": "MTM1MzA5NjExODk5NjgzMjMyNg.G6GegN.65muNrCalIydteCElYnUpjcoqLzx2lx9GiaPtM",
3
+ "webhook_url": "https://discord.com/api/webhooks/1353097385798275223/FSvIa4TYztnLUf4K20WJdVnTv_TthZGoX1FdHHSKx-Qc3ZhVgjcsFS704rK68SRpS8DF",
4
+ "target_language": "Chinese",
5
+ "password": "123456",
6
+ "servers": {
7
+ "main": {
8
+ "guild_id": "1225874935050797207",
9
+ "source_channel_id": "1350113928499433502",
10
+ "target_channel_id": "1350113406388142111",
11
+ "main_channel_id": "1350642349000233180",
12
+ "backup_channel_id": "1350547121232936980",
13
+ "chat_channels": {
14
+ "1350113928499433502": {
15
+ "preset": "default"
16
+ },
17
+ "1350642349000233180": {
18
+ "preset": "default"
19
+ },
20
+ "1353159493935956029": {
21
+ "preset": "default"
22
+ },
23
+ "1353172097664421899": {
24
+ "preset": "default"
25
+ },
26
+ "1353304668503015485": {
27
+ "preset": "default"
28
+ }
29
+ }
30
+ },
31
+ "server2": {
32
+ "guild_id": "1297598690734899241",
33
+ "source_channel_id": "1345815626035363912",
34
+ "target_channel_id": "1345815626035363912",
35
+ "main_channel_id": "1346477511961346130",
36
+ "backup_channel_id": "1345798271347589233",
37
+ "chat_channels": {
38
+ "1345815626035363912": {
39
+ "preset": "default"
40
+ },
41
+ "1346477511961346130": {
42
+ "preset": "default"
43
+ },
44
+ "1353087178896310433": {
45
+ "preset": "default"
46
+ }
47
+ }
48
+ }
49
+ },
50
+ "current_key": 5,
51
+ "gemini_chunk_per_edit": 2,
52
+ "gemini_keys": [
53
+ "AIzaSyBKNuMl39O7a5cForM62J0wzJ9Xa5DS0_c",
54
+ "AIzaSyBP12paErT-QURQBqG8CLGJjrwESJbn6KQ",
55
+ "AIzaSyDwDhAABYz152pnz8nsl21qcLXdV3lrdHQ",
56
+ "AIzaSyCFC7IMYoxj8Lng6GZdn6ZmXMiiAmDapfE",
57
+ "AIzaSyDOpJSy2QPZKvpt0hv6lJ7gVZ-cTQsB12I",
58
+ "AIzaSyCq5C93S-U2RTJmfwt5YicnFhIUY7Km80E"
59
+ ],
60
+ "gemini_models": {
61
+ "chat": "gemini-2.0-pro-exp-02-05",
62
+ "translate": "gemini-2.0-pro-exp-02-05"
63
+ },
64
+ "gemini_available_models": [
65
+ {
66
+ "name": "gemini-2.0-pro-exp-02-05",
67
+ "description": "默认聊天和翻译模型"
68
+ },
69
+ {
70
+ "name": "gemini-pro",
71
+ "description": "旧版Gemini Pro模型"
72
+ }
73
+ ],
74
+ "openai_key": "sk-Xu9jqxHVLDqkrbry7hbIzyOkv5VnUrto4VBNYHvg8h6ZcZs1",
75
+ "openai_endpoint": "https://esotlam-new-api.hf.space/v1",
76
+ "openai_models": {
77
+ "claude": {
78
+ "id": "genspark/claude-3-7-sonnet",
79
+ "chunk_per_edit": 10,
80
+ "custom_trigger": ".cl"
81
+ },
82
+ "gpt": {
83
+ "id": "gpt-4o-mini",
84
+ "chunk_per_edit": 10,
85
+ "custom_trigger": ""
86
+ },
87
+ "grok": {
88
+ "id": "grok.com/grok-3",
89
+ "chunk_per_edit": 10,
90
+ "custom_trigger": ".grok"
91
+ }
92
+ },
93
+ "default_openai_model": "claude"
94
  }
main.py CHANGED
@@ -12,17 +12,28 @@ import threading
12
  import webbrowser
13
  import time
14
  import requests
15
- from flask import Flask, render_template, request, jsonify, redirect, url_for, send_from_directory
16
  from werkzeug.utils import secure_filename
17
  from PIL import Image, ImageDraw, ImageFont
18
  import io
19
  import shutil
20
  from utils.config import config
 
21
 
22
  # 配置Flask应用
23
  app = Flask(__name__,
24
  static_folder='static',
25
  template_folder='templates')
 
 
 
 
 
 
 
 
 
 
26
 
27
  # 配置日志级别,过滤掉 google_genai.models 的 INFO 级别日志
28
  logging.getLogger('google_genai.models').setLevel(logging.WARNING)
@@ -67,30 +78,79 @@ def create_default_avatar():
67
  img.save(avatar_path)
68
 
69
  # Flask路由
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @app.route('/')
 
71
  def index():
72
  return render_template('index.html')
73
 
74
  @app.route('/config')
 
75
  def config_page():
76
  return render_template('config.html')
77
 
78
  @app.route('/presets')
 
79
  def presets_page():
80
  presets = os.listdir('agent/presets')
81
  return render_template('presets.html', presets=presets)
82
 
83
  @app.route('/api/config', methods=['GET'])
 
84
  def get_config():
85
  with open('config.json', 'r', encoding='utf-8') as f:
86
  config_data = json.load(f)
87
  return jsonify(config_data)
88
 
89
  @app.route('/api/config', methods=['POST'])
 
90
  def update_config():
91
  config_data = request.json
 
 
 
 
 
 
92
  with open('config.json', 'w', encoding='utf-8') as f:
93
  json.dump(config_data, f, indent=4, ensure_ascii=False)
 
 
 
 
94
  return jsonify({"status": "success"})
95
 
96
  @app.route('/api/presets', methods=['GET'])
 
12
  import webbrowser
13
  import time
14
  import requests
15
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, send_from_directory, session
16
  from werkzeug.utils import secure_filename
17
  from PIL import Image, ImageDraw, ImageFont
18
  import io
19
  import shutil
20
  from utils.config import config
21
+ from functools import wraps
22
 
23
  # 配置Flask应用
24
  app = Flask(__name__,
25
  static_folder='static',
26
  template_folder='templates')
27
+ app.secret_key = os.urandom(24) # 设置session密钥
28
+
29
+ # 登录验证装饰器
30
+ def login_required(f):
31
+ @wraps(f)
32
+ def decorated_function(*args, **kwargs):
33
+ if not session.get('logged_in'):
34
+ return redirect(url_for('login'))
35
+ return f(*args, **kwargs)
36
+ return decorated_function
37
 
38
  # 配置日志级别,过滤掉 google_genai.models 的 INFO 级别日志
39
  logging.getLogger('google_genai.models').setLevel(logging.WARNING)
 
78
  img.save(avatar_path)
79
 
80
  # Flask路由
81
+ @app.route('/login')
82
+ def login():
83
+ if session.get('logged_in'):
84
+ return redirect(url_for('index'))
85
+ return render_template('login.html')
86
+
87
+ @app.route('/api/login', methods=['POST'])
88
+ def api_login():
89
+ data = request.json
90
+ password = data.get('password')
91
+
92
+ # 直接从config.json读取最新密码
93
+ try:
94
+ with open('config.json', 'r', encoding='utf-8') as f:
95
+ config_data = json.load(f)
96
+ config_password = config_data.get('password')
97
+ except:
98
+ config_password = None
99
+
100
+ if not config_password:
101
+ # 如果配置中没有密码,设置默认密码为 "admin"
102
+ config_password = "admin"
103
+ config.write("password", config_password)
104
+
105
+ if password == config_password:
106
+ session['logged_in'] = True
107
+ return jsonify({"status": "success"})
108
+ return jsonify({"status": "error", "message": "密码错误"})
109
+
110
+ @app.route('/logout')
111
+ def logout():
112
+ session.pop('logged_in', None)
113
+ return redirect(url_for('login'))
114
+
115
  @app.route('/')
116
+ @login_required
117
  def index():
118
  return render_template('index.html')
119
 
120
  @app.route('/config')
121
+ @login_required
122
  def config_page():
123
  return render_template('config.html')
124
 
125
  @app.route('/presets')
126
+ @login_required
127
  def presets_page():
128
  presets = os.listdir('agent/presets')
129
  return render_template('presets.html', presets=presets)
130
 
131
  @app.route('/api/config', methods=['GET'])
132
+ @login_required
133
  def get_config():
134
  with open('config.json', 'r', encoding='utf-8') as f:
135
  config_data = json.load(f)
136
  return jsonify(config_data)
137
 
138
  @app.route('/api/config', methods=['POST'])
139
+ @login_required
140
  def update_config():
141
  config_data = request.json
142
+
143
+ # 检查密码是否被修改
144
+ current_password = config.get('password')
145
+ password_changed = current_password != config_data.get('password')
146
+
147
+ # 保存配置
148
  with open('config.json', 'w', encoding='utf-8') as f:
149
  json.dump(config_data, f, indent=4, ensure_ascii=False)
150
+
151
+ # 如果密码被修改,返回特殊状态码
152
+ if password_changed:
153
+ return jsonify({"status": "password_changed"})
154
  return jsonify({"status": "success"})
155
 
156
  @app.route('/api/presets', methods=['GET'])
readme.md CHANGED
@@ -9,6 +9,14 @@ pinned: false
9
 
10
  # Img Forwarder (图片转发机器人)
11
 
 
 
 
 
 
 
 
 
12
  ## [中文版]
13
 
14
  这是一个功能丰富的 Discord 机器人,最初设计用于将图片从一个频道转发到图库频道,但现已演变为包含多种 AI 集成和实用功能的综合工具。
@@ -24,6 +32,13 @@ pinned: false
24
  - **关键词响应**:自动回复配置的触发词
25
  - **管理命令**:提供多种机器人管理的管理员命令
26
  - **Web 界面**:提供直观的配置和预设管理界面
 
 
 
 
 
 
 
27
 
28
  ### 安装设置
29
 
@@ -126,10 +141,34 @@ python main.py # 或 ./start.bat
126
  ```json
127
  {
128
  "token": "你的机器人令牌",
129
- "target_channel_id": 123, // 图库频道
130
- "source_channel_id": 123, // 图片分享频道
131
- "chat_channel_id": 123, // 自动回复、备份和 AI 功能工作的频道
132
- "backup_channel_id": 123,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  "gemini_keys": [
134
  "你的_GEMINI_密钥",
135
  "你的_GEMINI_密钥",
 
9
 
10
  # Img Forwarder (图片转发机器人)
11
 
12
+ [![Hugging Face Spaces](https://img.shields.io/badge/Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/fuwei99/img_forwarder)
13
+ [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue)](https://github.com/fuwei99/img_forwarder)
14
+
15
+ ## 最新更新
16
+
17
+ * **多服务支持**:现在支持多个Discord服务器的配置管理
18
+ * **网络配置**:增加了网络端口5000的配置功能,通过Web界面可更方便地管理机器人
19
+
20
  ## [中文版]
21
 
22
  这是一个功能丰富的 Discord 机器人,最初设计用于将图片从一个频道转发到图库频道,但现已演变为包含多种 AI 集成和实用功能的综合工具。
 
32
  - **关键词响应**:自动回复配置的触发词
33
  - **管理命令**:提供多种机器人管理的管理员命令
34
  - **Web 界面**:提供直观的配置和预设管理界面
35
+ - **多服务器支持**:支持在多个Discord服务器上同时工作
36
+
37
+ ### 一键部署
38
+
39
+ 你可以通过以下链接一键部署到 Hugging Face Spaces:
40
+
41
+ [![Deploy to Hugging Face](https://huggingface.co/datasets/huggingface/badges/raw/main/deploy-to-huggingface.svg)](https://huggingface.co/spaces/fuwei99/img_forwarder)
42
 
43
  ### 安装设置
44
 
 
141
  ```json
142
  {
143
  "token": "你的机器人令牌",
144
+ "servers": {
145
+ "server_1": {
146
+ "name": "主服务器",
147
+ "discord_guild_id": "服务器ID",
148
+ "target_channel_id": 123456789, // 图库频道
149
+ "source_channel_id": 123456789, // 图片分享频道
150
+ "main_channel_id": 123456789, // 自动回复、备份和 AI 功能工作的频道
151
+ "backup_channel_id": 123456789,
152
+ "chat_channels": {
153
+ "频道ID": {
154
+ "preset": "default"
155
+ }
156
+ }
157
+ },
158
+ "server_2": {
159
+ "name": "第二服务器",
160
+ "discord_guild_id": "服务器ID",
161
+ "target_channel_id": 123456789,
162
+ "source_channel_id": 123456789,
163
+ "main_channel_id": 123456789,
164
+ "backup_channel_id": 123456789,
165
+ "chat_channels": {
166
+ "频道ID": {
167
+ "preset": "default"
168
+ }
169
+ }
170
+ }
171
+ },
172
  "gemini_keys": [
173
  "你的_GEMINI_密钥",
174
  "你的_GEMINI_密钥",
static/css/style.css CHANGED
@@ -1,157 +1,157 @@
1
- /* 全局样式 */
2
- body {
3
- background-color: #f8f9fa;
4
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5
- }
6
-
7
- /* 导航栏品牌名称 */
8
- .navbar-brand {
9
- font-weight: 600;
10
- }
11
-
12
- /* 卡片悬停效果 */
13
- .hover-card {
14
- transition: transform 0.3s, box-shadow 0.3s;
15
- }
16
-
17
- .hover-card:hover {
18
- transform: translateY(-5px);
19
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1) !important;
20
- }
21
-
22
- /* 预设列表样式 */
23
- .preset-item {
24
- border-left: 3px solid transparent;
25
- transition: all 0.2s;
26
- }
27
-
28
- .preset-item:hover {
29
- border-left-color: #0d6efd;
30
- }
31
-
32
- .preset-item.active {
33
- border-left-color: #0d6efd;
34
- background-color: rgba(13, 110, 253, 0.1);
35
- }
36
-
37
- /* 表单元素增强 */
38
- .form-control:focus, .form-select:focus {
39
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
40
- border-color: #86b7fe;
41
- }
42
-
43
- textarea.form-control {
44
- resize: vertical;
45
- min-height: 80px;
46
- }
47
-
48
- /* 头像上传按钮 */
49
- #changeAvatarBtn {
50
- transition: opacity 0.3s;
51
- opacity: 0.7;
52
- }
53
-
54
- #changeAvatarBtn:hover {
55
- opacity: 1;
56
- }
57
-
58
- /* Cropper样式 */
59
- .img-container {
60
- max-height: 500px;
61
- margin-bottom: 20px;
62
- }
63
-
64
- /* Tab内容区域 */
65
- .tab-content {
66
- min-height: 400px;
67
- }
68
-
69
- /* 导航标签样式增强 */
70
- .nav-tabs .nav-link {
71
- color: #495057;
72
- border-radius: 0;
73
- padding: 0.75rem 1rem;
74
- }
75
-
76
- .nav-tabs .nav-link.active {
77
- font-weight: 600;
78
- border-bottom: 3px solid #0d6efd;
79
- }
80
-
81
- .nav-pills .nav-link.active {
82
- background-color: #0d6efd;
83
- }
84
-
85
- /* 预设编辑器标题 */
86
- .preset-file-title {
87
- font-size: 0.9rem;
88
- color: #6c757d;
89
- margin-bottom: 0.5rem;
90
- }
91
-
92
- /* 按钮动画 */
93
- .btn {
94
- transition: all 0.2s;
95
- }
96
-
97
- .btn:hover {
98
- transform: translateY(-2px);
99
- }
100
-
101
- /* 聊天频道卡片样式 */
102
- .chat-channel-item {
103
- transition: all 0.2s;
104
- border-left: 3px solid transparent;
105
- }
106
-
107
- .chat-channel-item:hover {
108
- border-left-color: #0d6efd;
109
- background-color: rgba(13, 110, 253, 0.05);
110
- }
111
-
112
- /* 表格样式增强 */
113
- .table {
114
- border-color: #dee2e6;
115
- }
116
-
117
- .table thead th {
118
- background-color: #f8f9fa;
119
- font-weight: 600;
120
- }
121
-
122
- /* 自定义滚动条 */
123
- ::-webkit-scrollbar {
124
- width: 8px;
125
- height: 8px;
126
- }
127
-
128
- ::-webkit-scrollbar-track {
129
- background: #f1f1f1;
130
- border-radius: 4px;
131
- }
132
-
133
- ::-webkit-scrollbar-thumb {
134
- background: #c1c1c1;
135
- border-radius: 4px;
136
- }
137
-
138
- ::-webkit-scrollbar-thumb:hover {
139
- background: #a8a8a8;
140
- }
141
-
142
- /* 响应式调整 */
143
- @media (max-width: 767.98px) {
144
- .preset-item .preset-avatar img {
145
- width: 32px;
146
- height: 32px;
147
- }
148
-
149
- .card-header h5 {
150
- font-size: 1rem;
151
- }
152
-
153
- .btn-sm {
154
- padding: 0.25rem 0.5rem;
155
- font-size: 0.75rem;
156
- }
157
  }
 
1
+ /* 全局样式 */
2
+ body {
3
+ background-color: #f8f9fa;
4
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5
+ }
6
+
7
+ /* 导航栏品牌名称 */
8
+ .navbar-brand {
9
+ font-weight: 600;
10
+ }
11
+
12
+ /* 卡片悬停效果 */
13
+ .hover-card {
14
+ transition: transform 0.3s, box-shadow 0.3s;
15
+ }
16
+
17
+ .hover-card:hover {
18
+ transform: translateY(-5px);
19
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1) !important;
20
+ }
21
+
22
+ /* 预设列表样式 */
23
+ .preset-item {
24
+ border-left: 3px solid transparent;
25
+ transition: all 0.2s;
26
+ }
27
+
28
+ .preset-item:hover {
29
+ border-left-color: #0d6efd;
30
+ }
31
+
32
+ .preset-item.active {
33
+ border-left-color: #0d6efd;
34
+ background-color: rgba(13, 110, 253, 0.1);
35
+ }
36
+
37
+ /* 表单元素增强 */
38
+ .form-control:focus, .form-select:focus {
39
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
40
+ border-color: #86b7fe;
41
+ }
42
+
43
+ textarea.form-control {
44
+ resize: vertical;
45
+ min-height: 80px;
46
+ }
47
+
48
+ /* 头像上传按钮 */
49
+ #changeAvatarBtn {
50
+ transition: opacity 0.3s;
51
+ opacity: 0.7;
52
+ }
53
+
54
+ #changeAvatarBtn:hover {
55
+ opacity: 1;
56
+ }
57
+
58
+ /* Cropper样式 */
59
+ .img-container {
60
+ max-height: 500px;
61
+ margin-bottom: 20px;
62
+ }
63
+
64
+ /* Tab内容区域 */
65
+ .tab-content {
66
+ min-height: 400px;
67
+ }
68
+
69
+ /* 导航标签样式增强 */
70
+ .nav-tabs .nav-link {
71
+ color: #495057;
72
+ border-radius: 0;
73
+ padding: 0.75rem 1rem;
74
+ }
75
+
76
+ .nav-tabs .nav-link.active {
77
+ font-weight: 600;
78
+ border-bottom: 3px solid #0d6efd;
79
+ }
80
+
81
+ .nav-pills .nav-link.active {
82
+ background-color: #0d6efd;
83
+ }
84
+
85
+ /* 预设编辑器标题 */
86
+ .preset-file-title {
87
+ font-size: 0.9rem;
88
+ color: #6c757d;
89
+ margin-bottom: 0.5rem;
90
+ }
91
+
92
+ /* 按钮动画 */
93
+ .btn {
94
+ transition: all 0.2s;
95
+ }
96
+
97
+ .btn:hover {
98
+ transform: translateY(-2px);
99
+ }
100
+
101
+ /* 聊天频道卡片样式 */
102
+ .chat-channel-item {
103
+ transition: all 0.2s;
104
+ border-left: 3px solid transparent;
105
+ }
106
+
107
+ .chat-channel-item:hover {
108
+ border-left-color: #0d6efd;
109
+ background-color: rgba(13, 110, 253, 0.05);
110
+ }
111
+
112
+ /* 表格样式增强 */
113
+ .table {
114
+ border-color: #dee2e6;
115
+ }
116
+
117
+ .table thead th {
118
+ background-color: #f8f9fa;
119
+ font-weight: 600;
120
+ }
121
+
122
+ /* 自定义滚动条 */
123
+ ::-webkit-scrollbar {
124
+ width: 8px;
125
+ height: 8px;
126
+ }
127
+
128
+ ::-webkit-scrollbar-track {
129
+ background: #f1f1f1;
130
+ border-radius: 4px;
131
+ }
132
+
133
+ ::-webkit-scrollbar-thumb {
134
+ background: #c1c1c1;
135
+ border-radius: 4px;
136
+ }
137
+
138
+ ::-webkit-scrollbar-thumb:hover {
139
+ background: #a8a8a8;
140
+ }
141
+
142
+ /* 响应式调整 */
143
+ @media (max-width: 767.98px) {
144
+ .preset-item .preset-avatar img {
145
+ width: 32px;
146
+ height: 32px;
147
+ }
148
+
149
+ .card-header h5 {
150
+ font-size: 1rem;
151
+ }
152
+
153
+ .btn-sm {
154
+ padding: 0.25rem 0.5rem;
155
+ font-size: 0.75rem;
156
+ }
157
  }
static/model-avatars/model-1742947288.png ADDED
static/model_avatars/claude.png ADDED
templates/config.html CHANGED
@@ -18,7 +18,7 @@
18
  <span class="navbar-toggler-icon"></span>
19
  </button>
20
  <div class="collapse navbar-collapse" id="navbarNav">
21
- <ul class="navbar-nav">
22
  <li class="nav-item">
23
  <a class="nav-link" href="/">首页</a>
24
  </li>
@@ -29,6 +29,13 @@
29
  <a class="nav-link" href="/presets">预设管理</a>
30
  </li>
31
  </ul>
 
 
 
 
 
 
 
32
  </div>
33
  </div>
34
  </nav>
@@ -77,6 +84,13 @@
77
  <div class="form-text">机器人响应使用的默认语言</div>
78
  </div>
79
  </div>
 
 
 
 
 
 
 
80
  <div class="col-12">
81
  <div class="form-floating mb-3">
82
  <input type="text" class="form-control" id="webhook_url" name="webhook_url">
@@ -130,6 +144,44 @@
130
  <i class="bi bi-plus"></i> 添加密钥
131
  </button>
132
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  </div>
134
 
135
  <!-- OpenAI 设置 -->
@@ -274,6 +326,11 @@
274
  <label for="new_model_chunk_per_edit" class="form-label">每次编辑的块数</label>
275
  <input type="number" class="form-control" id="new_model_chunk_per_edit" value="10" min="1">
276
  </div>
 
 
 
 
 
277
  </div>
278
  <div class="modal-footer">
279
  <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
@@ -283,6 +340,32 @@
283
  </div>
284
  </div>
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  <!-- 保存成功提示 -->
287
  <div class="toast-container position-fixed top-0 end-0 p-3">
288
  <div id="saveToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
@@ -317,6 +400,7 @@
317
  $('#token').val(config.token || '');
318
  $('#webhook_url').val(config.webhook_url || '');
319
  $('#target_language').val(config.target_language || 'Chinese');
 
320
 
321
  // 服务器设置
322
  $('#servers_container').empty();
@@ -338,6 +422,20 @@
338
  });
339
  }
340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  // OpenAI设置
342
  $('#openai_key').val(config.openai_key || '');
343
  $('#openai_endpoint').val(config.openai_endpoint || '');
@@ -427,7 +525,9 @@
427
  </div>
428
  </div>
429
  </div>
430
- `;
 
 
431
  $('#servers_container').append(serverHtml);
432
  }
433
 
@@ -437,10 +537,16 @@
437
  token: $('#token').val(),
438
  webhook_url: $('#webhook_url').val(),
439
  target_language: $('#target_language').val(),
 
440
  servers: {},
441
  current_key: parseInt($('#current_key').val()) || 0,
442
  gemini_chunk_per_edit: parseInt($('#gemini_chunk_per_edit').val()) || 2,
443
  gemini_keys: [],
 
 
 
 
 
444
  openai_key: $('#openai_key').val(),
445
  openai_endpoint: $('#openai_endpoint').val(),
446
  openai_models: {}
@@ -476,12 +582,23 @@
476
  }
477
  });
478
 
 
 
 
 
 
 
 
 
 
 
479
  // 收集OpenAI模型配置
480
  $('.model-card').each(function() {
481
  const modelName = $(this).data('model-name');
482
  config.openai_models[modelName] = {
483
  id: $(this).find('.model-id').val(),
484
- chunk_per_edit: parseInt($(this).find('.chunk-per-edit').val()) || 10
 
485
  };
486
  });
487
 
@@ -491,8 +608,15 @@
491
  type: 'POST',
492
  contentType: 'application/json',
493
  data: JSON.stringify(config),
494
- success: function() {
495
- alert('配置已保存');
 
 
 
 
 
 
 
496
  },
497
  error: function() {
498
  alert('保存失败');
@@ -583,6 +707,13 @@
583
  <label>每次编辑的块数</label>
584
  </div>
585
  </div>
 
 
 
 
 
 
 
586
  </div>
587
  </div>
588
  </div>
@@ -612,6 +743,7 @@
612
  const modelName = $('#new_model_name').val();
613
  const modelId = $('#new_model_id').val();
614
  const chunkPerEdit = $('#new_model_chunk_per_edit').val();
 
615
 
616
  if (!modelName || !modelId) {
617
  alert('模型名称和ID不能为空');
@@ -620,7 +752,8 @@
620
 
621
  const modelConfig = {
622
  id: modelId,
623
- chunk_per_edit: parseInt(chunkPerEdit) || 10
 
624
  };
625
 
626
  addModelToUI(modelName, modelConfig);
@@ -630,6 +763,7 @@
630
  $('#new_model_name').val('');
631
  $('#new_model_id').val('');
632
  $('#new_model_chunk_per_edit').val('10');
 
633
  });
634
 
635
  // 删除模型
@@ -646,6 +780,94 @@
646
  $('#addChannelModal').modal('show');
647
  });
648
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  // 页面加载完成后执行
650
  $(document).ready(function() {
651
  loadConfig();
 
18
  <span class="navbar-toggler-icon"></span>
19
  </button>
20
  <div class="collapse navbar-collapse" id="navbarNav">
21
+ <ul class="navbar-nav me-auto">
22
  <li class="nav-item">
23
  <a class="nav-link" href="/">首页</a>
24
  </li>
 
29
  <a class="nav-link" href="/presets">预设管理</a>
30
  </li>
31
  </ul>
32
+ <ul class="navbar-nav">
33
+ <li class="nav-item">
34
+ <a class="nav-link" href="/logout">
35
+ <i class="bi bi-box-arrow-right"></i> 退出
36
+ </a>
37
+ </li>
38
+ </ul>
39
  </div>
40
  </div>
41
  </nav>
 
84
  <div class="form-text">机器人响应使用的默认语言</div>
85
  </div>
86
  </div>
87
+ <div class="col-md-6">
88
+ <div class="form-floating mb-3">
89
+ <input type="password" class="form-control" id="password" name="password">
90
+ <label for="password">管理密码</label>
91
+ <div class="form-text">用于登录管理界面的密码</div>
92
+ </div>
93
+ </div>
94
  <div class="col-12">
95
  <div class="form-floating mb-3">
96
  <input type="text" class="form-control" id="webhook_url" name="webhook_url">
 
144
  <i class="bi bi-plus"></i> 添加密钥
145
  </button>
146
  </div>
147
+
148
+ <div class="card mb-3">
149
+ <div class="card-header bg-light d-flex justify-content-between align-items-center">
150
+ <h5 class="mb-0">Gemini 模型配置</h5>
151
+ </div>
152
+ <div class="card-body">
153
+ <div class="row g-3">
154
+ <div class="col-md-6">
155
+ <div class="form-floating mb-3">
156
+ <input type="text" class="form-control" id="gemini_chat_model" name="gemini_chat_model">
157
+ <label for="gemini_chat_model">聊天模型</label>
158
+ <div class="form-text">用于聊天的Gemini模型,例如: gemini-2.0-pro-exp-02-05</div>
159
+ </div>
160
+ </div>
161
+ <div class="col-md-6">
162
+ <div class="form-floating mb-3">
163
+ <input type="text" class="form-control" id="gemini_translate_model" name="gemini_translate_model">
164
+ <label for="gemini_translate_model">翻译模型</label>
165
+ <div class="form-text">用于翻译的Gemini模型,例如: gemini-2.0-pro-exp-02-05</div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <div class="card mb-3">
173
+ <div class="card-header bg-light d-flex justify-content-between align-items-center">
174
+ <h5 class="mb-0">Gemini 可用模型列表</h5>
175
+ <button type="button" class="btn btn-sm btn-primary" id="addGeminiModelBtn">
176
+ <i class="bi bi-plus"></i> 添加模型
177
+ </button>
178
+ </div>
179
+ <div class="card-body">
180
+ <div id="gemini_models_container" class="mb-3">
181
+ <!-- 这里会动态添加Gemini模型配置 -->
182
+ </div>
183
+ </div>
184
+ </div>
185
  </div>
186
 
187
  <!-- OpenAI 设置 -->
 
326
  <label for="new_model_chunk_per_edit" class="form-label">每次编辑的块数</label>
327
  <input type="number" class="form-control" id="new_model_chunk_per_edit" value="10" min="1">
328
  </div>
329
+ <div class="mb-3">
330
+ <label for="new_model_custom_trigger" class="form-label">自定义触发指令</label>
331
+ <input type="text" class="form-control" id="new_model_custom_trigger" placeholder="例如:.cl">
332
+ <div class="form-text">留空表示不使用自定义触发指令</div>
333
+ </div>
334
  </div>
335
  <div class="modal-footer">
336
  <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
 
340
  </div>
341
  </div>
342
 
343
+ <!-- 添加Gemini模型模态框 -->
344
+ <div class="modal fade" id="addGeminiModelModal" tabindex="-1" aria-hidden="true">
345
+ <div class="modal-dialog">
346
+ <div class="modal-content">
347
+ <div class="modal-header">
348
+ <h5 class="modal-title">添加 Gemini 模型</h5>
349
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
350
+ </div>
351
+ <div class="modal-body">
352
+ <div class="mb-3">
353
+ <label for="new_gemini_model_name" class="form-label">模型名称</label>
354
+ <input type="text" class="form-control" id="new_gemini_model_name" placeholder="例如: gemini-2.0-pro-exp-02-05">
355
+ </div>
356
+ <div class="mb-3">
357
+ <label for="new_gemini_model_description" class="form-label">模型描述</label>
358
+ <input type="text" class="form-control" id="new_gemini_model_description" placeholder="例如: Gemini Pro Vision">
359
+ </div>
360
+ </div>
361
+ <div class="modal-footer">
362
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
363
+ <button type="button" class="btn btn-primary" id="confirmAddGeminiModelBtn">添加</button>
364
+ </div>
365
+ </div>
366
+ </div>
367
+ </div>
368
+
369
  <!-- 保存成功提示 -->
370
  <div class="toast-container position-fixed top-0 end-0 p-3">
371
  <div id="saveToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
 
400
  $('#token').val(config.token || '');
401
  $('#webhook_url').val(config.webhook_url || '');
402
  $('#target_language').val(config.target_language || 'Chinese');
403
+ $('#password').val(config.password || '');
404
 
405
  // 服务器设置
406
  $('#servers_container').empty();
 
422
  });
423
  }
424
 
425
+ // Gemini模型配置
426
+ if (config.gemini_models) {
427
+ $('#gemini_chat_model').val(config.gemini_models.chat || 'gemini-2.0-pro-exp-02-05');
428
+ $('#gemini_translate_model').val(config.gemini_models.translate || 'gemini-2.0-pro-exp-02-05');
429
+ }
430
+
431
+ // Gemini可用模型列表
432
+ $('#gemini_models_container').empty();
433
+ if (config.gemini_available_models && Array.isArray(config.gemini_available_models)) {
434
+ config.gemini_available_models.forEach((model) => {
435
+ addGeminiModelToUI(model);
436
+ });
437
+ }
438
+
439
  // OpenAI设置
440
  $('#openai_key').val(config.openai_key || '');
441
  $('#openai_endpoint').val(config.openai_endpoint || '');
 
525
  </div>
526
  </div>
527
  </div>
528
+ </div>
529
+ </div>
530
+ `;
531
  $('#servers_container').append(serverHtml);
532
  }
533
 
 
537
  token: $('#token').val(),
538
  webhook_url: $('#webhook_url').val(),
539
  target_language: $('#target_language').val(),
540
+ password: $('#password').val(),
541
  servers: {},
542
  current_key: parseInt($('#current_key').val()) || 0,
543
  gemini_chunk_per_edit: parseInt($('#gemini_chunk_per_edit').val()) || 2,
544
  gemini_keys: [],
545
+ gemini_models: {
546
+ chat: $('#gemini_chat_model').val() || 'gemini-2.0-pro-exp-02-05',
547
+ translate: $('#gemini_translate_model').val() || 'gemini-2.0-pro-exp-02-05'
548
+ },
549
+ gemini_available_models: [],
550
  openai_key: $('#openai_key').val(),
551
  openai_endpoint: $('#openai_endpoint').val(),
552
  openai_models: {}
 
582
  }
583
  });
584
 
585
+ // 收集Gemini可用模型
586
+ $('.gemini-model-card').each(function() {
587
+ const modelName = $(this).data('model-name');
588
+ const modelDescription = $(this).find('.model-description').val();
589
+ config.gemini_available_models.push({
590
+ name: modelName,
591
+ description: modelDescription || ""
592
+ });
593
+ });
594
+
595
  // 收集OpenAI模型配置
596
  $('.model-card').each(function() {
597
  const modelName = $(this).data('model-name');
598
  config.openai_models[modelName] = {
599
  id: $(this).find('.model-id').val(),
600
+ chunk_per_edit: parseInt($(this).find('.chunk-per-edit').val()) || 10,
601
+ custom_trigger: $(this).find('.custom-trigger').val() || ''
602
  };
603
  });
604
 
 
608
  type: 'POST',
609
  contentType: 'application/json',
610
  data: JSON.stringify(config),
611
+ success: function(response) {
612
+ if (response.status === 'password_changed') {
613
+ alert('密码已更改,请使用新密码重新登录');
614
+ window.location.href = '/logout'; // 重定向到登出页面
615
+ } else {
616
+ // 显示保存成功的提示
617
+ const toast = new bootstrap.Toast($('#saveToast'));
618
+ toast.show();
619
+ }
620
  },
621
  error: function() {
622
  alert('保存失败');
 
707
  <label>每次编辑的块数</label>
708
  </div>
709
  </div>
710
+ <div class="col-md-12">
711
+ <div class="form-floating">
712
+ <input type="text" class="form-control custom-trigger" value="${modelConfig.custom_trigger || ''}" placeholder="自定义触发指令">
713
+ <label>自定义触发指令(例如:.cl)</label>
714
+ <div class="form-text">留空表示不使用自定义触发指令</div>
715
+ </div>
716
+ </div>
717
  </div>
718
  </div>
719
  </div>
 
743
  const modelName = $('#new_model_name').val();
744
  const modelId = $('#new_model_id').val();
745
  const chunkPerEdit = $('#new_model_chunk_per_edit').val();
746
+ const customTrigger = $('#new_model_custom_trigger').val();
747
 
748
  if (!modelName || !modelId) {
749
  alert('模型名称和ID不能为空');
 
752
 
753
  const modelConfig = {
754
  id: modelId,
755
+ chunk_per_edit: parseInt(chunkPerEdit) || 10,
756
+ custom_trigger: customTrigger || ''
757
  };
758
 
759
  addModelToUI(modelName, modelConfig);
 
763
  $('#new_model_name').val('');
764
  $('#new_model_id').val('');
765
  $('#new_model_chunk_per_edit').val('10');
766
+ $('#new_model_custom_trigger').val('');
767
  });
768
 
769
  // 删除模型
 
780
  $('#addChannelModal').modal('show');
781
  });
782
 
783
+ // 添加Gemini模型按钮点击事件
784
+ $('#addGeminiModelBtn').click(function() {
785
+ $('#addGeminiModelModal').modal('show');
786
+ });
787
+
788
+ // 确认添加Gemini模型
789
+ $('#confirmAddGeminiModelBtn').click(function() {
790
+ const modelName = $('#new_gemini_model_name').val();
791
+ const modelDescription = $('#new_gemini_model_description').val();
792
+
793
+ if (!modelName) {
794
+ alert('模型名称不能为空');
795
+ return;
796
+ }
797
+
798
+ const modelConfig = {
799
+ name: modelName,
800
+ description: modelDescription
801
+ };
802
+
803
+ addGeminiModelToUI(modelConfig);
804
+ $('#addGeminiModelModal').modal('hide');
805
+
806
+ // 清空表单
807
+ $('#new_gemini_model_name').val('');
808
+ $('#new_gemini_model_description').val('');
809
+ });
810
+
811
+ // 添加Gemini模型到UI
812
+ function addGeminiModelToUI(modelConfig) {
813
+ const modelHtml = `
814
+ <div class="card mb-3 gemini-model-card" data-model-name="${modelConfig.name}">
815
+ <div class="card-header bg-light d-flex justify-content-between align-items-center">
816
+ <h6 class="mb-0">${modelConfig.name}</h6>
817
+ <div class="btn-group">
818
+ <button type="button" class="btn btn-sm btn-success set-as-chat-model-btn" title="设为聊天模型">
819
+ <i class="bi bi-chat"></i>
820
+ </button>
821
+ <button type="button" class="btn btn-sm btn-info set-as-translate-model-btn" title="设为翻译模型">
822
+ <i class="bi bi-translate"></i>
823
+ </button>
824
+ <button type="button" class="btn btn-sm btn-danger delete-gemini-model-btn">
825
+ <i class="bi bi-trash"></i>
826
+ </button>
827
+ </div>
828
+ </div>
829
+ <div class="card-body">
830
+ <div class="row g-3">
831
+ <div class="col-md-12">
832
+ <div class="form-floating">
833
+ <input type="text" class="form-control model-name" value="${modelConfig.name}" readonly>
834
+ <label>模型名称</label>
835
+ </div>
836
+ </div>
837
+ <div class="col-md-12">
838
+ <div class="form-floating">
839
+ <input type="text" class="form-control model-description" value="${modelConfig.description || ''}" placeholder="模型描述">
840
+ <label>模型描述</label>
841
+ </div>
842
+ </div>
843
+ </div>
844
+ </div>
845
+ </div>
846
+ `;
847
+ $('#gemini_models_container').append(modelHtml);
848
+ }
849
+
850
+ // 删除Gemini模型
851
+ $(document).on('click', '.delete-gemini-model-btn', function() {
852
+ if (confirm('确定要删除此模型吗?')) {
853
+ $(this).closest('.gemini-model-card').remove();
854
+ }
855
+ });
856
+
857
+ // 设置为聊天模型
858
+ $(document).on('click', '.set-as-chat-model-btn', function() {
859
+ const modelName = $(this).closest('.gemini-model-card').data('model-name');
860
+ $('#gemini_chat_model').val(modelName);
861
+ alert(`已将 ${modelName} 设置为聊天模型`);
862
+ });
863
+
864
+ // 设置为翻译模型
865
+ $(document).on('click', '.set-as-translate-model-btn', function() {
866
+ const modelName = $(this).closest('.gemini-model-card').data('model-name');
867
+ $('#gemini_translate_model').val(modelName);
868
+ alert(`已将 ${modelName} 设置为翻译模型`);
869
+ });
870
+
871
  // 页面加载完成后执行
872
  $(document).ready(function() {
873
  loadConfig();
templates/index.html CHANGED
@@ -1,49 +1,49 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Discord 机器人管理</title>
7
- <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
9
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
- </head>
11
- <body>
12
- <div class="container">
13
- <header class="py-4 text-center">
14
- <h1 class="display-4 fw-bold text-primary">Discord 机器人管理</h1>
15
- <p class="lead">管理您的机器人配置和预设</p>
16
- </header>
17
-
18
- <div class="row g-4 py-4">
19
- <div class="col-md-6">
20
- <div class="card h-100 shadow hover-card">
21
- <div class="card-body text-center p-5">
22
- <i class="bi bi-gear-fill text-primary mb-3" style="font-size: 3rem;"></i>
23
- <h2 class="card-title mb-3">配置设置</h2>
24
- <p class="card-text">管理机器人的配置,包括令牌、频道ID、API密钥等。</p>
25
- <a href="/config" class="btn btn-primary btn-lg mt-3">管理配置</a>
26
- </div>
27
- </div>
28
- </div>
29
- <div class="col-md-6">
30
- <div class="card h-100 shadow hover-card">
31
- <div class="card-body text-center p-5">
32
- <i class="bi bi-braces-asterisk text-primary mb-3" style="font-size: 3rem;"></i>
33
- <h2 class="card-title mb-3">预设管理</h2>
34
- <p class="card-text">创建、编辑和删除机器人预设,自定义机器人行为和设置。</p>
35
- <a href="/presets" class="btn btn-primary btn-lg mt-3">管理预设</a>
36
- </div>
37
- </div>
38
- </div>
39
- </div>
40
-
41
- <footer class="py-4 text-center text-muted">
42
- <p>© 2024 Discord 机器人管理</p>
43
- </footer>
44
- </div>
45
-
46
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
47
- <script src="{{ url_for('static', filename='js/main.js') }}"></script>
48
- </body>
49
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Discord 机器人管理</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
+ </head>
11
+ <body>
12
+ <div class="container">
13
+ <header class="py-4 text-center">
14
+ <h1 class="display-4 fw-bold text-primary">Discord 机器人管理</h1>
15
+ <p class="lead">管理您的机器人配置和预设</p>
16
+ </header>
17
+
18
+ <div class="row g-4 py-4">
19
+ <div class="col-md-6">
20
+ <div class="card h-100 shadow hover-card">
21
+ <div class="card-body text-center p-5">
22
+ <i class="bi bi-gear-fill text-primary mb-3" style="font-size: 3rem;"></i>
23
+ <h2 class="card-title mb-3">配置设置</h2>
24
+ <p class="card-text">管理机器人的配置,包括令牌、频道ID、API密钥等。</p>
25
+ <a href="/config" class="btn btn-primary btn-lg mt-3">管理配置</a>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <div class="col-md-6">
30
+ <div class="card h-100 shadow hover-card">
31
+ <div class="card-body text-center p-5">
32
+ <i class="bi bi-braces-asterisk text-primary mb-3" style="font-size: 3rem;"></i>
33
+ <h2 class="card-title mb-3">预设管理</h2>
34
+ <p class="card-text">创建、编辑和删除机器人预设,自定义机器人行为和设置。</p>
35
+ <a href="/presets" class="btn btn-primary btn-lg mt-3">管理预设</a>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <footer class="py-4 text-center text-muted">
42
+ <p>© 2024 Discord 机器人管理</p>
43
+ </footer>
44
+ </div>
45
+
46
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
47
+ <script src="{{ url_for('static', filename='js/main.js') }}"></script>
48
+ </body>
49
  </html>
templates/login.html ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>登录 - Discord 机器人管理</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ min-height: 100vh;
15
+ background: linear-gradient(45deg, #1a1a1a, #2d2d2d);
16
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ overflow: hidden;
21
+ position: relative;
22
+ }
23
+
24
+ .particles {
25
+ position: absolute;
26
+ top: 0;
27
+ left: 0;
28
+ width: 100%;
29
+ height: 100%;
30
+ pointer-events: none;
31
+ z-index: 0;
32
+ }
33
+
34
+ .login-container {
35
+ position: relative;
36
+ z-index: 1;
37
+ width: 100%;
38
+ max-width: 420px;
39
+ margin: 20px;
40
+ perspective: 1000px;
41
+ }
42
+
43
+ .card {
44
+ background: rgba(255, 255, 255, 0.1);
45
+ backdrop-filter: blur(10px);
46
+ border: 1px solid rgba(255, 255, 255, 0.1);
47
+ border-radius: 20px;
48
+ padding: 2rem;
49
+ transform-style: preserve-3d;
50
+ transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1);
51
+ }
52
+
53
+ .card:hover {
54
+ transform: translateY(-5px) rotateX(5deg);
55
+ }
56
+
57
+ .card-header {
58
+ background: transparent;
59
+ border: none;
60
+ padding: 0 0 2rem 0;
61
+ text-align: center;
62
+ }
63
+
64
+ .card-title {
65
+ color: #fff;
66
+ font-size: 2rem;
67
+ font-weight: 600;
68
+ margin: 0;
69
+ text-shadow: 0 0 10px rgba(0, 157, 255, 0.5);
70
+ }
71
+
72
+ .form-label {
73
+ color: rgba(255, 255, 255, 0.8);
74
+ font-size: 0.9rem;
75
+ margin-bottom: 0.5rem;
76
+ }
77
+
78
+ .form-control {
79
+ background: rgba(255, 255, 255, 0.05);
80
+ border: 1px solid rgba(255, 255, 255, 0.1);
81
+ border-radius: 12px;
82
+ color: #fff;
83
+ padding: 0.8rem 1rem;
84
+ transition: all 0.3s ease;
85
+ }
86
+
87
+ .form-control:focus {
88
+ background: rgba(255, 255, 255, 0.1);
89
+ border-color: rgba(0, 157, 255, 0.5);
90
+ box-shadow: 0 0 0 2px rgba(0, 157, 255, 0.2);
91
+ }
92
+
93
+ .btn-login {
94
+ background: linear-gradient(45deg, #00c6ff, #0072ff);
95
+ border: none;
96
+ border-radius: 12px;
97
+ color: #fff;
98
+ font-weight: 600;
99
+ padding: 0.8rem;
100
+ position: relative;
101
+ overflow: hidden;
102
+ transition: all 0.3s ease;
103
+ }
104
+
105
+ .btn-login:hover {
106
+ transform: translateY(-2px);
107
+ box-shadow: 0 4px 15px rgba(0, 157, 255, 0.4);
108
+ }
109
+
110
+ .btn-login:before {
111
+ content: '';
112
+ position: absolute;
113
+ top: 0;
114
+ left: -100%;
115
+ width: 100%;
116
+ height: 100%;
117
+ background: linear-gradient(
118
+ 120deg,
119
+ transparent,
120
+ rgba(255, 255, 255, 0.2),
121
+ transparent
122
+ );
123
+ transition: 0.5s;
124
+ }
125
+
126
+ .btn-login:hover:before {
127
+ left: 100%;
128
+ }
129
+
130
+ .error-message {
131
+ background: rgba(255, 0, 0, 0.1);
132
+ border-left: 4px solid #ff0000;
133
+ color: #fff;
134
+ margin-top: 1rem;
135
+ padding: 0.8rem;
136
+ border-radius: 8px;
137
+ display: none;
138
+ }
139
+
140
+ @keyframes float {
141
+ 0% { transform: translateY(0px); }
142
+ 50% { transform: translateY(-20px); }
143
+ 100% { transform: translateY(0px); }
144
+ }
145
+
146
+ .floating-icon {
147
+ position: absolute;
148
+ opacity: 0.1;
149
+ animation: float 6s ease-in-out infinite;
150
+ }
151
+
152
+ .icon-1 { top: 10%; left: 10%; font-size: 4rem; animation-delay: 0s; }
153
+ .icon-2 { top: 60%; right: 10%; font-size: 3rem; animation-delay: -2s; }
154
+ .icon-3 { bottom: 10%; left: 20%; font-size: 2.5rem; animation-delay: -4s; }
155
+ </style>
156
+ </head>
157
+ <body>
158
+ <div class="particles" id="particles-js"></div>
159
+
160
+ <i class="bi bi-discord floating-icon icon-1"></i>
161
+ <i class="bi bi-robot floating-icon icon-2"></i>
162
+ <i class="bi bi-cpu floating-icon icon-3"></i>
163
+
164
+ <div class="login-container">
165
+ <div class="card">
166
+ <div class="card-header">
167
+ <h4 class="card-title">Discord 机器人管理</h4>
168
+ </div>
169
+ <form id="loginForm">
170
+ <div class="mb-4">
171
+ <label for="password" class="form-label">输入密码</label>
172
+ <div class="input-group">
173
+ <input type="password" class="form-control" id="password" required
174
+ placeholder="请输入访问密码">
175
+ <button class="btn btn-outline-light" type="button" id="togglePassword">
176
+ <i class="bi bi-eye"></i>
177
+ </button>
178
+ </div>
179
+ </div>
180
+ <div class="d-grid">
181
+ <button type="submit" class="btn btn-login">
182
+ <i class="bi bi-box-arrow-in-right me-2"></i>登录
183
+ </button>
184
+ </div>
185
+ <div class="error-message" id="errorMessage">
186
+ <i class="bi bi-exclamation-triangle me-2"></i>
187
+ <span>密码错误,请重试</span>
188
+ </div>
189
+ </form>
190
+ </div>
191
+ </div>
192
+
193
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
194
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
195
+ <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
196
+ <script>
197
+ $(document).ready(function() {
198
+ // 初始化粒子效果
199
+ particlesJS('particles-js', {
200
+ particles: {
201
+ number: { value: 80, density: { enable: true, value_area: 800 } },
202
+ color: { value: '#ffffff' },
203
+ shape: { type: 'circle' },
204
+ opacity: { value: 0.5, random: true },
205
+ size: { value: 3, random: true },
206
+ line_linked: {
207
+ enable: true,
208
+ distance: 150,
209
+ color: '#ffffff',
210
+ opacity: 0.2,
211
+ width: 1
212
+ },
213
+ move: {
214
+ enable: true,
215
+ speed: 2,
216
+ direction: 'none',
217
+ random: true,
218
+ straight: false,
219
+ out_mode: 'out',
220
+ bounce: false,
221
+ }
222
+ },
223
+ interactivity: {
224
+ detect_on: 'canvas',
225
+ events: {
226
+ onhover: { enable: true, mode: 'repulse' },
227
+ onclick: { enable: true, mode: 'push' },
228
+ resize: true
229
+ }
230
+ },
231
+ retina_detect: true
232
+ });
233
+
234
+ // 切换密码可见性
235
+ $('#togglePassword').on('click', function() {
236
+ const passwordInput = $('#password');
237
+ const icon = $(this).find('i');
238
+
239
+ if (passwordInput.attr('type') === 'password') {
240
+ passwordInput.attr('type', 'text');
241
+ icon.removeClass('bi-eye').addClass('bi-eye-slash');
242
+ } else {
243
+ passwordInput.attr('type', 'password');
244
+ icon.removeClass('bi-eye-slash').addClass('bi-eye');
245
+ }
246
+ });
247
+
248
+ // 表单提交处理
249
+ $('#loginForm').on('submit', function(e) {
250
+ e.preventDefault();
251
+ const password = $('#password').val();
252
+ const errorMessage = $('#errorMessage');
253
+ const submitBtn = $('.btn-login');
254
+
255
+ // 添加加载状态
256
+ submitBtn.prop('disabled', true)
257
+ .html('<span class="spinner-border spinner-border-sm me-2"></span>登录中...');
258
+
259
+ $.ajax({
260
+ url: '/api/login',
261
+ type: 'POST',
262
+ contentType: 'application/json',
263
+ data: JSON.stringify({ password: password }),
264
+ success: function(response) {
265
+ if (response.status === 'success') {
266
+ // 添加成功动画
267
+ submitBtn.html('<i class="bi bi-check-lg me-2"></i>登录成功')
268
+ .removeClass('btn-login')
269
+ .addClass('btn-success');
270
+
271
+ setTimeout(() => {
272
+ window.location.href = '/';
273
+ }, 1000);
274
+ } else {
275
+ errorMessage.slideDown();
276
+ submitBtn.prop('disabled', false)
277
+ .html('<i class="bi bi-box-arrow-in-right me-2"></i>登录');
278
+
279
+ // 添加抖动效果
280
+ $('.card').addClass('shake');
281
+ setTimeout(() => {
282
+ $('.card').removeClass('shake');
283
+ }, 500);
284
+ }
285
+ },
286
+ error: function() {
287
+ errorMessage.find('span').text('登录失败,请重试');
288
+ errorMessage.slideDown();
289
+ submitBtn.prop('disabled', false)
290
+ .html('<i class="bi bi-box-arrow-in-right me-2"></i>登录');
291
+ }
292
+ });
293
+ });
294
+
295
+ // 输入框获得焦点时隐藏错误信息
296
+ $('#password').on('focus', function() {
297
+ $('#errorMessage').slideUp();
298
+ });
299
+ });
300
+
301
+ // 添加3D视差效果
302
+ document.addEventListener('mousemove', function(e) {
303
+ const card = document.querySelector('.card');
304
+ const xAxis = (window.innerWidth / 2 - e.pageX) / 25;
305
+ const yAxis = (window.innerHeight / 2 - e.pageY) / 25;
306
+ card.style.transform = `rotateY(${xAxis}deg) rotateX(${yAxis}deg)`;
307
+ });
308
+ </script>
309
+ </body>
310
+ </html>
templates/presets.html CHANGED
@@ -1,369 +1,369 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>预设管理 - Discord 机器人管理</title>
7
- <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
9
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cropperjs@1.6.1/dist/cropper.min.css">
11
- </head>
12
- <body>
13
- <div class="container">
14
- <nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
15
- <div class="container-fluid">
16
- <a class="navbar-brand" href="/">Discord 机器人管理</a>
17
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
18
- <span class="navbar-toggler-icon"></span>
19
- </button>
20
- <div class="collapse navbar-collapse" id="navbarNav">
21
- <ul class="navbar-nav">
22
- <li class="nav-item">
23
- <a class="nav-link" href="/">首页</a>
24
- </li>
25
- <li class="nav-item">
26
- <a class="nav-link" href="/config">配置设置</a>
27
- </li>
28
- <li class="nav-item">
29
- <a class="nav-link active" href="/presets">预设管理</a>
30
- </li>
31
- </ul>
32
- </div>
33
- </div>
34
- </nav>
35
-
36
- <div class="row">
37
- <!-- 预设列表 -->
38
- <div class="col-md-4 mb-4">
39
- <div class="card shadow h-100">
40
- <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
41
- <h5 class="mb-0">预设列表</h5>
42
- <button class="btn btn-sm btn-light" id="addNewPresetBtn">
43
- <i class="bi bi-plus-lg"></i> 新增
44
- </button>
45
- </div>
46
- <div class="card-body">
47
- <div class="list-group" id="presetsListGroup">
48
- {% for preset in presets %}
49
- <button type="button" class="list-group-item list-group-item-action preset-item" data-preset="{{ preset }}">
50
- <div class="d-flex align-items-center">
51
- <div class="preset-avatar me-3">
52
- <img src="/static/img/default-avatar.png" alt="{{ preset }}" class="rounded-circle" width="40" height="40" id="avatar-{{ preset }}">
53
- </div>
54
- <div>
55
- <h6 class="mb-0">{{ preset }}</h6>
56
- </div>
57
- </div>
58
- </button>
59
- {% endfor %}
60
- </div>
61
- </div>
62
- </div>
63
- </div>
64
-
65
- <!-- 预设编辑区 -->
66
- <div class="col-md-8 mb-4">
67
- <div class="card shadow h-100">
68
- <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
69
- <h5 class="mb-0" id="presetNameHeader">请选择预设</h5>
70
- <div>
71
- <button class="btn btn-sm btn-danger me-2" id="deletePresetBtn" style="display: none;">
72
- <i class="bi bi-trash"></i> 删除
73
- </button>
74
- <button class="btn btn-sm btn-success" id="savePresetBtn" style="display: none;">
75
- <i class="bi bi-save"></i> 保存
76
- </button>
77
- </div>
78
- </div>
79
- <div class="card-body">
80
- <div id="presetEditorContainer" style="display: none;">
81
- <div class="row mb-4">
82
- <div class="col-md-3 text-center">
83
- <div class="position-relative mb-3" style="width: 150px; height: 150px; margin: 0 auto;">
84
- <img src="/static/img/default-avatar.png" alt="预设头像" class="img-fluid rounded-circle" id="presetAvatar" style="width: 150px; height: 150px; object-fit: cover;">
85
- <button class="btn btn-sm btn-primary position-absolute bottom-0 end-0" id="changeAvatarBtn">
86
- <i class="bi bi-camera"></i>
87
- </button>
88
- </div>
89
- <input type="file" id="avatarFileInput" accept="image/*" style="display: none;">
90
- </div>
91
- <div class="col-md-9">
92
- <ul class="nav nav-tabs" id="presetTabs" role="tablist">
93
- <li class="nav-item" role="presentation">
94
- <button class="nav-link active" id="chat-tab" data-bs-toggle="tab" data-bs-target="#chat-tab-pane" type="button" role="tab">聊天设置</button>
95
- </li>
96
- <li class="nav-item" role="presentation">
97
- <button class="nav-link" id="translate-tab" data-bs-toggle="tab" data-bs-target="#translate-tab-pane" type="button" role="tab">翻译设置</button>
98
- </li>
99
- <li class="nav-item" role="presentation">
100
- <button class="nav-link" id="attachment-tab" data-bs-toggle="tab" data-bs-target="#attachment-tab-pane" type="button" role="tab">附件设置</button>
101
- </li>
102
- <li class="nav-item" role="presentation">
103
- <button class="nav-link" id="reference-tab" data-bs-toggle="tab" data-bs-target="#reference-tab-pane" type="button" role="tab">引用设置</button>
104
- </li>
105
- <li class="nav-item" role="presentation">
106
- <button class="nav-link" id="model-tab" data-bs-toggle="tab" data-bs-target="#model-tab-pane" type="button" role="tab">模型设置</button>
107
- </li>
108
- </ul>
109
- <div class="tab-content p-3 border border-top-0 rounded-bottom" id="presetTabContent">
110
- <!-- 聊天设置 -->
111
- <div class="tab-pane fade show active" id="chat-tab-pane" role="tabpanel" aria-labelledby="chat-tab">
112
- <form id="chatPresetForm">
113
- <div class="mb-3">
114
- <label for="chat_system_prompt" class="form-label">系统提示词</label>
115
- <textarea class="form-control" id="chat_system_prompt" rows="2"></textarea>
116
- </div>
117
- <div class="mb-3">
118
- <label for="chat_first_user_message" class="form-label">第一条用户消息</label>
119
- <input type="text" class="form-control" id="chat_first_user_message">
120
- </div>
121
- <div class="mb-3">
122
- <label for="chat_main_content" class="form-label">主要内容</label>
123
- <textarea class="form-control" id="chat_main_content" rows="10"></textarea>
124
- </div>
125
- <div class="mb-3">
126
- <label for="chat_last_user_message" class="form-label">最后一条用户消息</label>
127
- <input type="text" class="form-control" id="chat_last_user_message">
128
- </div>
129
- </form>
130
- </div>
131
-
132
- <!-- 翻译设置 -->
133
- <div class="tab-pane fade" id="translate-tab-pane" role="tabpanel" aria-labelledby="translate-tab">
134
- <form id="translatePresetForm">
135
- <div class="mb-3">
136
- <label for="translate_system_prompt" class="form-label">系统提示词</label>
137
- <textarea class="form-control" id="translate_system_prompt" rows="2"></textarea>
138
- </div>
139
- <div class="mb-3">
140
- <label for="translate_first_user_message" class="form-label">第一条用户消息</label>
141
- <input type="text" class="form-control" id="translate_first_user_message">
142
- </div>
143
- <div class="mb-3">
144
- <label for="translate_main_content" class="form-label">主要内容</label>
145
- <textarea class="form-control" id="translate_main_content" rows="10"></textarea>
146
- </div>
147
- <div class="mb-3">
148
- <label for="translate_last_user_message" class="form-label">最后一条用户消息</label>
149
- <input type="text" class="form-control" id="translate_last_user_message">
150
- </div>
151
- </form>
152
- </div>
153
-
154
- <!-- 附件设置 -->
155
- <div class="tab-pane fade" id="attachment-tab-pane" role="tabpanel" aria-labelledby="attachment-tab">
156
- <form id="attachmentPresetForm">
157
- <div class="mb-3">
158
- <label for="attachment_system_prompt" class="form-label">系统提示词</label>
159
- <textarea class="form-control" id="attachment_system_prompt" rows="2"></textarea>
160
- </div>
161
- <div class="mb-3">
162
- <label for="attachment_first_user_message" class="form-label">第一条用户消息</label>
163
- <input type="text" class="form-control" id="attachment_first_user_message">
164
- </div>
165
- <div class="mb-3">
166
- <label for="attachment_main_content" class="form-label">主要内容</label>
167
- <textarea class="form-control" id="attachment_main_content" rows="10"></textarea>
168
- </div>
169
- <div class="mb-3">
170
- <label for="attachment_last_user_message" class="form-label">最后一条用户消息</label>
171
- <input type="text" class="form-control" id="attachment_last_user_message">
172
- </div>
173
- </form>
174
- </div>
175
-
176
- <!-- 引用设置 -->
177
- <div class="tab-pane fade" id="reference-tab-pane" role="tabpanel" aria-labelledby="reference-tab">
178
- <form id="referencePresetForm">
179
- <div class="mb-3">
180
- <label for="reference_system_prompt" class="form-label">系统提示词</label>
181
- <textarea class="form-control" id="reference_system_prompt" rows="2"></textarea>
182
- </div>
183
- <div class="mb-3">
184
- <label for="reference_first_user_message" class="form-label">第一条用户消息</label>
185
- <input type="text" class="form-control" id="reference_first_user_message">
186
- </div>
187
- <div class="mb-3">
188
- <label for="reference_main_content" class="form-label">主要内容</label>
189
- <textarea class="form-control" id="reference_main_content" rows="10"></textarea>
190
- </div>
191
- <div class="mb-3">
192
- <label for="reference_last_user_message" class="form-label">最后一条用户消息</label>
193
- <input type="text" class="form-control" id="reference_last_user_message">
194
- </div>
195
- </form>
196
- </div>
197
-
198
- <!-- 模型设置 -->
199
- <div class="tab-pane fade" id="model-tab-pane" role="tabpanel" aria-labelledby="model-tab">
200
- <ul class="nav nav-pills mb-3" id="modelSettingsTabs" role="tablist">
201
- <li class="nav-item" role="presentation">
202
- <button class="nav-link active" id="gemini-config-tab" data-bs-toggle="pill" data-bs-target="#gemini-config-pane" type="button" role="tab">Gemini 设置</button>
203
- </li>
204
- <li class="nav-item" role="presentation">
205
- <button class="nav-link" id="openai-config-tab" data-bs-toggle="pill" data-bs-target="#openai-config-pane" type="button" role="tab">OpenAI 设置</button>
206
- </li>
207
- </ul>
208
- <div class="tab-content" id="modelSettingsContent">
209
- <!-- Gemini 配置 -->
210
- <div class="tab-pane fade show active" id="gemini-config-pane" role="tabpanel" aria-labelledby="gemini-config-tab">
211
- <form id="geminiConfigForm">
212
- <div class="mb-3">
213
- <label for="gemini_system_instruction" class="form-label">系统指令</label>
214
- <textarea class="form-control" id="gemini_system_instruction" rows="3"></textarea>
215
- </div>
216
- <div class="row g-3">
217
- <div class="col-md-4">
218
- <label for="gemini_top_k" class="form-label">Top K</label>
219
- <input type="number" class="form-control" id="gemini_top_k" min="1" max="100">
220
- </div>
221
- <div class="col-md-4">
222
- <label for="gemini_top_p" class="form-label">Top P</label>
223
- <input type="number" class="form-control" id="gemini_top_p" min="0" max="1" step="0.01">
224
- </div>
225
- <div class="col-md-4">
226
- <label for="gemini_temperature" class="form-label">温度</label>
227
- <input type="number" class="form-control" id="gemini_temperature" min="0" max="2" step="0.01">
228
- </div>
229
- </div>
230
-
231
- <div class="mt-4">
232
- <label class="form-label">安全设置</label>
233
- <div class="table-responsive">
234
- <table class="table table-bordered">
235
- <thead>
236
- <tr>
237
- <th>类别</th>
238
- <th>阈值</th>
239
- </tr>
240
- </thead>
241
- <tbody id="safetySettingsTable">
242
- <!-- 安全设置会动态添加 -->
243
- </tbody>
244
- </table>
245
- </div>
246
- </div>
247
- </form>
248
- </div>
249
-
250
- <!-- OpenAI 配置 -->
251
- <div class="tab-pane fade" id="openai-config-pane" role="tabpanel" aria-labelledby="openai-config-tab">
252
- <form id="openaiConfigForm">
253
- <!-- OpenAI 配置表单 -->
254
- <div class="alert alert-info">
255
- 此处可以添加 OpenAI 特定的配置设置
256
- </div>
257
- </form>
258
- </div>
259
- </div>
260
- </div>
261
- </div>
262
- </div>
263
- </div>
264
- </div>
265
- <div id="presetPlaceholder" class="text-center py-5">
266
- <i class="bi bi-arrow-left-circle" style="font-size: 3rem; color: #ccc;"></i>
267
- <h5 class="mt-3 text-muted">请从左侧列表选择一个预设</h5>
268
- </div>
269
- </div>
270
- </div>
271
- </div>
272
- </div>
273
-
274
- <!-- 新预设模态框 -->
275
- <div class="modal fade" id="newPresetModal" tabindex="-1" aria-hidden="true">
276
- <div class="modal-dialog">
277
- <div class="modal-content">
278
- <div class="modal-header">
279
- <h5 class="modal-title">创建新预设</h5>
280
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
281
- </div>
282
- <div class="modal-body">
283
- <div class="mb-3">
284
- <label for="newPresetName" class="form-label">预设名称</label>
285
- <input type="text" class="form-control" id="newPresetName" placeholder="输入预设名称">
286
- </div>
287
- <div class="mb-3">
288
- <label for="templatePreset" class="form-label">基于模板</label>
289
- <select class="form-select" id="templatePreset">
290
- {% for preset in presets %}
291
- <option value="{{ preset }}">{{ preset }}</option>
292
- {% endfor %}
293
- </select>
294
- </div>
295
- </div>
296
- <div class="modal-footer">
297
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
298
- <button type="button" class="btn btn-primary" id="createPresetBtn">创建</button>
299
- </div>
300
- </div>
301
- </div>
302
- </div>
303
-
304
- <!-- 删除确认模态框 -->
305
- <div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-hidden="true">
306
- <div class="modal-dialog">
307
- <div class="modal-content">
308
- <div class="modal-header">
309
- <h5 class="modal-title">确认删除</h5>
310
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
311
- </div>
312
- <div class="modal-body">
313
- <p>确定要删除预设 <span id="deletePresetName" class="fw-bold"></span> 吗?此操作不可撤销。</p>
314
- </div>
315
- <div class="modal-footer">
316
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
317
- <button type="button" class="btn btn-danger" id="confirmDeleteBtn">删除</button>
318
- </div>
319
- </div>
320
- </div>
321
- </div>
322
-
323
- <!-- 图片裁剪模态框 -->
324
- <div class="modal fade" id="imageCropperModal" tabindex="-1" aria-hidden="true">
325
- <div class="modal-dialog modal-lg">
326
- <div class="modal-content">
327
- <div class="modal-header">
328
- <h5 class="modal-title">裁剪头像</h5>
329
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
330
- </div>
331
- <div class="modal-body">
332
- <div class="img-container">
333
- <img id="cropperImage" src="" alt="待裁剪图片" style="max-width: 100%;">
334
- </div>
335
- </div>
336
- <div class="modal-footer">
337
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
338
- <button type="button" class="btn btn-primary" id="cropImageBtn">裁剪并保存</button>
339
- </div>
340
- </div>
341
- </div>
342
- </div>
343
-
344
- <!-- 保存成功提示 -->
345
- <div class="toast-container position-fixed top-0 end-0 p-3">
346
- <div id="saveToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
347
- <div class="toast-header bg-success text-white">
348
- <i class="bi bi-check-circle me-2"></i>
349
- <strong class="me-auto">成功</strong>
350
- <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
351
- </div>
352
- <div class="toast-body" id="toastMessage">
353
- 操作成功!
354
- </div>
355
- </div>
356
- </div>
357
-
358
- <footer class="py-4 text-center text-muted">
359
- <p>© 2024 Discord 机器人管理</p>
360
- </footer>
361
- </div>
362
-
363
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
364
- <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.js"></script>
365
- <script src="https://cdn.jsdelivr.net/npm/cropperjs@1.6.1/dist/cropper.min.js"></script>
366
- <script src="{{ url_for('static', filename='js/main.js') }}"></script>
367
- <script src="{{ url_for('static', filename='js/presets.js') }}"></script>
368
- </body>
369
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>预设管理 - Discord 机器人管理</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cropperjs@1.6.1/dist/cropper.min.css">
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
15
+ <div class="container-fluid">
16
+ <a class="navbar-brand" href="/">Discord 机器人管理</a>
17
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
18
+ <span class="navbar-toggler-icon"></span>
19
+ </button>
20
+ <div class="collapse navbar-collapse" id="navbarNav">
21
+ <ul class="navbar-nav">
22
+ <li class="nav-item">
23
+ <a class="nav-link" href="/">首页</a>
24
+ </li>
25
+ <li class="nav-item">
26
+ <a class="nav-link" href="/config">配置设置</a>
27
+ </li>
28
+ <li class="nav-item">
29
+ <a class="nav-link active" href="/presets">预设管理</a>
30
+ </li>
31
+ </ul>
32
+ </div>
33
+ </div>
34
+ </nav>
35
+
36
+ <div class="row">
37
+ <!-- 预设列表 -->
38
+ <div class="col-md-4 mb-4">
39
+ <div class="card shadow h-100">
40
+ <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
41
+ <h5 class="mb-0">预设列表</h5>
42
+ <button class="btn btn-sm btn-light" id="addNewPresetBtn">
43
+ <i class="bi bi-plus-lg"></i> 新增
44
+ </button>
45
+ </div>
46
+ <div class="card-body">
47
+ <div class="list-group" id="presetsListGroup">
48
+ {% for preset in presets %}
49
+ <button type="button" class="list-group-item list-group-item-action preset-item" data-preset="{{ preset }}">
50
+ <div class="d-flex align-items-center">
51
+ <div class="preset-avatar me-3">
52
+ <img src="/static/img/default-avatar.png" alt="{{ preset }}" class="rounded-circle" width="40" height="40" id="avatar-{{ preset }}">
53
+ </div>
54
+ <div>
55
+ <h6 class="mb-0">{{ preset }}</h6>
56
+ </div>
57
+ </div>
58
+ </button>
59
+ {% endfor %}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- 预设编辑区 -->
66
+ <div class="col-md-8 mb-4">
67
+ <div class="card shadow h-100">
68
+ <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
69
+ <h5 class="mb-0" id="presetNameHeader">请选择预设</h5>
70
+ <div>
71
+ <button class="btn btn-sm btn-danger me-2" id="deletePresetBtn" style="display: none;">
72
+ <i class="bi bi-trash"></i> 删除
73
+ </button>
74
+ <button class="btn btn-sm btn-success" id="savePresetBtn" style="display: none;">
75
+ <i class="bi bi-save"></i> 保存
76
+ </button>
77
+ </div>
78
+ </div>
79
+ <div class="card-body">
80
+ <div id="presetEditorContainer" style="display: none;">
81
+ <div class="row mb-4">
82
+ <div class="col-md-3 text-center">
83
+ <div class="position-relative mb-3" style="width: 150px; height: 150px; margin: 0 auto;">
84
+ <img src="/static/img/default-avatar.png" alt="预设头像" class="img-fluid rounded-circle" id="presetAvatar" style="width: 150px; height: 150px; object-fit: cover;">
85
+ <button class="btn btn-sm btn-primary position-absolute bottom-0 end-0" id="changeAvatarBtn">
86
+ <i class="bi bi-camera"></i>
87
+ </button>
88
+ </div>
89
+ <input type="file" id="avatarFileInput" accept="image/*" style="display: none;">
90
+ </div>
91
+ <div class="col-md-9">
92
+ <ul class="nav nav-tabs" id="presetTabs" role="tablist">
93
+ <li class="nav-item" role="presentation">
94
+ <button class="nav-link active" id="chat-tab" data-bs-toggle="tab" data-bs-target="#chat-tab-pane" type="button" role="tab">聊天设置</button>
95
+ </li>
96
+ <li class="nav-item" role="presentation">
97
+ <button class="nav-link" id="translate-tab" data-bs-toggle="tab" data-bs-target="#translate-tab-pane" type="button" role="tab">翻译设置</button>
98
+ </li>
99
+ <li class="nav-item" role="presentation">
100
+ <button class="nav-link" id="attachment-tab" data-bs-toggle="tab" data-bs-target="#attachment-tab-pane" type="button" role="tab">附件设置</button>
101
+ </li>
102
+ <li class="nav-item" role="presentation">
103
+ <button class="nav-link" id="reference-tab" data-bs-toggle="tab" data-bs-target="#reference-tab-pane" type="button" role="tab">引用设置</button>
104
+ </li>
105
+ <li class="nav-item" role="presentation">
106
+ <button class="nav-link" id="model-tab" data-bs-toggle="tab" data-bs-target="#model-tab-pane" type="button" role="tab">模型设置</button>
107
+ </li>
108
+ </ul>
109
+ <div class="tab-content p-3 border border-top-0 rounded-bottom" id="presetTabContent">
110
+ <!-- 聊天设置 -->
111
+ <div class="tab-pane fade show active" id="chat-tab-pane" role="tabpanel" aria-labelledby="chat-tab">
112
+ <form id="chatPresetForm">
113
+ <div class="mb-3">
114
+ <label for="chat_system_prompt" class="form-label">系统提示词</label>
115
+ <textarea class="form-control" id="chat_system_prompt" rows="2"></textarea>
116
+ </div>
117
+ <div class="mb-3">
118
+ <label for="chat_first_user_message" class="form-label">第一条用户消息</label>
119
+ <input type="text" class="form-control" id="chat_first_user_message">
120
+ </div>
121
+ <div class="mb-3">
122
+ <label for="chat_main_content" class="form-label">主要内容</label>
123
+ <textarea class="form-control" id="chat_main_content" rows="10"></textarea>
124
+ </div>
125
+ <div class="mb-3">
126
+ <label for="chat_last_user_message" class="form-label">最后一条用户消息</label>
127
+ <input type="text" class="form-control" id="chat_last_user_message">
128
+ </div>
129
+ </form>
130
+ </div>
131
+
132
+ <!-- 翻译设置 -->
133
+ <div class="tab-pane fade" id="translate-tab-pane" role="tabpanel" aria-labelledby="translate-tab">
134
+ <form id="translatePresetForm">
135
+ <div class="mb-3">
136
+ <label for="translate_system_prompt" class="form-label">系统提示词</label>
137
+ <textarea class="form-control" id="translate_system_prompt" rows="2"></textarea>
138
+ </div>
139
+ <div class="mb-3">
140
+ <label for="translate_first_user_message" class="form-label">第一条用户消息</label>
141
+ <input type="text" class="form-control" id="translate_first_user_message">
142
+ </div>
143
+ <div class="mb-3">
144
+ <label for="translate_main_content" class="form-label">主要内容</label>
145
+ <textarea class="form-control" id="translate_main_content" rows="10"></textarea>
146
+ </div>
147
+ <div class="mb-3">
148
+ <label for="translate_last_user_message" class="form-label">最后一条用户消息</label>
149
+ <input type="text" class="form-control" id="translate_last_user_message">
150
+ </div>
151
+ </form>
152
+ </div>
153
+
154
+ <!-- 附件设置 -->
155
+ <div class="tab-pane fade" id="attachment-tab-pane" role="tabpanel" aria-labelledby="attachment-tab">
156
+ <form id="attachmentPresetForm">
157
+ <div class="mb-3">
158
+ <label for="attachment_system_prompt" class="form-label">系统提示词</label>
159
+ <textarea class="form-control" id="attachment_system_prompt" rows="2"></textarea>
160
+ </div>
161
+ <div class="mb-3">
162
+ <label for="attachment_first_user_message" class="form-label">第一条用户消息</label>
163
+ <input type="text" class="form-control" id="attachment_first_user_message">
164
+ </div>
165
+ <div class="mb-3">
166
+ <label for="attachment_main_content" class="form-label">主要内容</label>
167
+ <textarea class="form-control" id="attachment_main_content" rows="10"></textarea>
168
+ </div>
169
+ <div class="mb-3">
170
+ <label for="attachment_last_user_message" class="form-label">最后一条用户消息</label>
171
+ <input type="text" class="form-control" id="attachment_last_user_message">
172
+ </div>
173
+ </form>
174
+ </div>
175
+
176
+ <!-- 引用设置 -->
177
+ <div class="tab-pane fade" id="reference-tab-pane" role="tabpanel" aria-labelledby="reference-tab">
178
+ <form id="referencePresetForm">
179
+ <div class="mb-3">
180
+ <label for="reference_system_prompt" class="form-label">系统提示词</label>
181
+ <textarea class="form-control" id="reference_system_prompt" rows="2"></textarea>
182
+ </div>
183
+ <div class="mb-3">
184
+ <label for="reference_first_user_message" class="form-label">第一条用户消息</label>
185
+ <input type="text" class="form-control" id="reference_first_user_message">
186
+ </div>
187
+ <div class="mb-3">
188
+ <label for="reference_main_content" class="form-label">主要内容</label>
189
+ <textarea class="form-control" id="reference_main_content" rows="10"></textarea>
190
+ </div>
191
+ <div class="mb-3">
192
+ <label for="reference_last_user_message" class="form-label">最后一条用户消息</label>
193
+ <input type="text" class="form-control" id="reference_last_user_message">
194
+ </div>
195
+ </form>
196
+ </div>
197
+
198
+ <!-- 模型设置 -->
199
+ <div class="tab-pane fade" id="model-tab-pane" role="tabpanel" aria-labelledby="model-tab">
200
+ <ul class="nav nav-pills mb-3" id="modelSettingsTabs" role="tablist">
201
+ <li class="nav-item" role="presentation">
202
+ <button class="nav-link active" id="gemini-config-tab" data-bs-toggle="pill" data-bs-target="#gemini-config-pane" type="button" role="tab">Gemini 设置</button>
203
+ </li>
204
+ <li class="nav-item" role="presentation">
205
+ <button class="nav-link" id="openai-config-tab" data-bs-toggle="pill" data-bs-target="#openai-config-pane" type="button" role="tab">OpenAI 设置</button>
206
+ </li>
207
+ </ul>
208
+ <div class="tab-content" id="modelSettingsContent">
209
+ <!-- Gemini 配置 -->
210
+ <div class="tab-pane fade show active" id="gemini-config-pane" role="tabpanel" aria-labelledby="gemini-config-tab">
211
+ <form id="geminiConfigForm">
212
+ <div class="mb-3">
213
+ <label for="gemini_system_instruction" class="form-label">系统指令</label>
214
+ <textarea class="form-control" id="gemini_system_instruction" rows="3"></textarea>
215
+ </div>
216
+ <div class="row g-3">
217
+ <div class="col-md-4">
218
+ <label for="gemini_top_k" class="form-label">Top K</label>
219
+ <input type="number" class="form-control" id="gemini_top_k" min="1" max="100">
220
+ </div>
221
+ <div class="col-md-4">
222
+ <label for="gemini_top_p" class="form-label">Top P</label>
223
+ <input type="number" class="form-control" id="gemini_top_p" min="0" max="1" step="0.01">
224
+ </div>
225
+ <div class="col-md-4">
226
+ <label for="gemini_temperature" class="form-label">温度</label>
227
+ <input type="number" class="form-control" id="gemini_temperature" min="0" max="2" step="0.01">
228
+ </div>
229
+ </div>
230
+
231
+ <div class="mt-4">
232
+ <label class="form-label">安全设置</label>
233
+ <div class="table-responsive">
234
+ <table class="table table-bordered">
235
+ <thead>
236
+ <tr>
237
+ <th>类别</th>
238
+ <th>阈值</th>
239
+ </tr>
240
+ </thead>
241
+ <tbody id="safetySettingsTable">
242
+ <!-- 安全设置会动态添加 -->
243
+ </tbody>
244
+ </table>
245
+ </div>
246
+ </div>
247
+ </form>
248
+ </div>
249
+
250
+ <!-- OpenAI 配置 -->
251
+ <div class="tab-pane fade" id="openai-config-pane" role="tabpanel" aria-labelledby="openai-config-tab">
252
+ <form id="openaiConfigForm">
253
+ <!-- OpenAI 配置表单 -->
254
+ <div class="alert alert-info">
255
+ 此处可以添加 OpenAI 特定的配置设置
256
+ </div>
257
+ </form>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ <div id="presetPlaceholder" class="text-center py-5">
266
+ <i class="bi bi-arrow-left-circle" style="font-size: 3rem; color: #ccc;"></i>
267
+ <h5 class="mt-3 text-muted">请从左侧列表选择一个预设</h5>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+
274
+ <!-- 新预设模态框 -->
275
+ <div class="modal fade" id="newPresetModal" tabindex="-1" aria-hidden="true">
276
+ <div class="modal-dialog">
277
+ <div class="modal-content">
278
+ <div class="modal-header">
279
+ <h5 class="modal-title">创建新预设</h5>
280
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
281
+ </div>
282
+ <div class="modal-body">
283
+ <div class="mb-3">
284
+ <label for="newPresetName" class="form-label">预设名称</label>
285
+ <input type="text" class="form-control" id="newPresetName" placeholder="输入预设名称">
286
+ </div>
287
+ <div class="mb-3">
288
+ <label for="templatePreset" class="form-label">基于模板</label>
289
+ <select class="form-select" id="templatePreset">
290
+ {% for preset in presets %}
291
+ <option value="{{ preset }}">{{ preset }}</option>
292
+ {% endfor %}
293
+ </select>
294
+ </div>
295
+ </div>
296
+ <div class="modal-footer">
297
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
298
+ <button type="button" class="btn btn-primary" id="createPresetBtn">创建</button>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- 删除确认模态框 -->
305
+ <div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-hidden="true">
306
+ <div class="modal-dialog">
307
+ <div class="modal-content">
308
+ <div class="modal-header">
309
+ <h5 class="modal-title">确认删除</h5>
310
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
311
+ </div>
312
+ <div class="modal-body">
313
+ <p>确定要删除预设 <span id="deletePresetName" class="fw-bold"></span> 吗?此操作不可撤销。</p>
314
+ </div>
315
+ <div class="modal-footer">
316
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
317
+ <button type="button" class="btn btn-danger" id="confirmDeleteBtn">删除</button>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+
323
+ <!-- 图片裁剪模态框 -->
324
+ <div class="modal fade" id="imageCropperModal" tabindex="-1" aria-hidden="true">
325
+ <div class="modal-dialog modal-lg">
326
+ <div class="modal-content">
327
+ <div class="modal-header">
328
+ <h5 class="modal-title">裁剪头像</h5>
329
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
330
+ </div>
331
+ <div class="modal-body">
332
+ <div class="img-container">
333
+ <img id="cropperImage" src="" alt="待裁剪图片" style="max-width: 100%;">
334
+ </div>
335
+ </div>
336
+ <div class="modal-footer">
337
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
338
+ <button type="button" class="btn btn-primary" id="cropImageBtn">裁剪并保存</button>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ </div>
343
+
344
+ <!-- 保存成功提示 -->
345
+ <div class="toast-container position-fixed top-0 end-0 p-3">
346
+ <div id="saveToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
347
+ <div class="toast-header bg-success text-white">
348
+ <i class="bi bi-check-circle me-2"></i>
349
+ <strong class="me-auto">成功</strong>
350
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
351
+ </div>
352
+ <div class="toast-body" id="toastMessage">
353
+ 操作成功!
354
+ </div>
355
+ </div>
356
+ </div>
357
+
358
+ <footer class="py-4 text-center text-muted">
359
+ <p>© 2024 Discord 机器人管理</p>
360
+ </footer>
361
+ </div>
362
+
363
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
364
+ <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.2/dist/jsoneditor.min.js"></script>
365
+ <script src="https://cdn.jsdelivr.net/npm/cropperjs@1.6.1/dist/cropper.min.js"></script>
366
+ <script src="{{ url_for('static', filename='js/main.js') }}"></script>
367
+ <script src="{{ url_for('static', filename='js/presets.js') }}"></script>
368
+ </body>
369
  </html>
utils/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (184 Bytes). View file
 
utils/__pycache__/color_printer.cpython-313.pyc ADDED
Binary file (13.6 kB). View file
 
utils/__pycache__/config.cpython-313.pyc ADDED
Binary file (2.74 kB). View file
 
utils/__pycache__/context_prompter.cpython-313.pyc ADDED
Binary file (15.6 kB). View file
 
utils/__pycache__/decorator.cpython-313.pyc ADDED
Binary file (1.36 kB). View file
 
utils/__pycache__/func.cpython-313.pyc ADDED
Binary file (3.77 kB). View file
 
utils/__pycache__/logger.cpython-313.pyc ADDED
Binary file (941 Bytes). View file