Spaces:
Runtime error
Runtime error
Upload 61 files
Browse files- .gitignore +260 -0
- cogs/__pycache__/admin.cpython-313.pyc +0 -0
- cogs/__pycache__/gemini.cpython-313.pyc +0 -0
- cogs/__pycache__/openai.cpython-313.pyc +0 -0
- cogs/gemini.py +380 -179
- cogs/openai.py +164 -33
- config.json +93 -72
- main.py +61 -1
- readme.md +43 -4
- static/css/style.css +156 -156
- static/model-avatars/model-1742947288.png +0 -0
- static/model_avatars/claude.png +0 -0
- templates/config.html +228 -6
- templates/index.html +48 -48
- templates/login.html +310 -0
- templates/presets.html +368 -368
- utils/__pycache__/__init__.cpython-313.pyc +0 -0
- utils/__pycache__/color_printer.cpython-313.pyc +0 -0
- utils/__pycache__/config.cpython-313.pyc +0 -0
- utils/__pycache__/context_prompter.cpython-313.pyc +0 -0
- utils/__pycache__/decorator.cpython-313.pyc +0 -0
- utils/__pycache__/func.cpython-313.pyc +0 -0
- utils/__pycache__/logger.cpython-313.pyc +0 -0
.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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 533 |
-
|
| 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 |
-
#
|
| 583 |
-
|
| 584 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
|
| 671 |
-
#
|
| 672 |
-
|
| 673 |
-
"
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
"
|
| 681 |
-
|
| 682 |
)
|
| 683 |
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
)
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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":
|
| 125 |
}
|
| 126 |
|
| 127 |
# 记录API请求数据
|
| 128 |
logger.info(f"OpenAI API 请求数据: {json.dumps(data, ensure_ascii=False, indent=2)}")
|
| 129 |
|
| 130 |
-
|
| 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 |
-
|
| 288 |
-
|
| 289 |
-
# 检查响应是否为空
|
| 290 |
-
if not response or response.strip() == "":
|
| 291 |
-
response = "抱歉,我现在无法生成回复。请稍后再试。"
|
| 292 |
|
| 293 |
-
#
|
| 294 |
-
|
| 295 |
-
#
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
"
|
| 33 |
-
"
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
"gemini_keys": [
|
| 134 |
"你的_GEMINI_密钥",
|
| 135 |
"你的_GEMINI_密钥",
|
|
|
|
| 9 |
|
| 10 |
# Img Forwarder (图片转发机器人)
|
| 11 |
|
| 12 |
+
[](https://huggingface.co/spaces/fuwei99/img_forwarder)
|
| 13 |
+
[](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 |
+
[](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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|