Showing preview only (1,338K chars total). Download the full file or copy to clipboard to get everything.
Repository: umaru-233/My-Dream-Moments
Branch: main
Commit: 26d40b28739e
Files: 150
Total size: 1.1 MB
Directory structure:
gitextract_epyr_53n/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── Thanks.md
├── data/
│ ├── __init__.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── config.json.template
│ │ └── config.json.template.bak
│ └── tasks.json
├── modules/
│ ├── memory/
│ │ ├── __init__.py
│ │ ├── content_generator.py
│ │ └── memory_service.py
│ ├── recognition/
│ │ ├── __init__.py
│ │ ├── reminder_request_recognition/
│ │ │ ├── __init__.py
│ │ │ ├── example_message.json
│ │ │ ├── prompt.md
│ │ │ └── service.py
│ │ └── search_request_recognition/
│ │ ├── __init__.py
│ │ ├── example_message.json
│ │ ├── prompt.md
│ │ └── service.py
│ ├── reminder/
│ │ ├── __init__.py
│ │ ├── call.py
│ │ └── service.py
│ └── tts/
│ ├── __init__.py
│ └── service.py
├── requirements.txt
├── run.bat
├── run.py
├── run_config_web.py
├── src/
│ ├── AutoTasker/
│ │ └── autoTasker.py
│ ├── Wechat_Login_Clicker/
│ │ └── Wechat_Login_Clicker.py
│ ├── __init__.py
│ ├── autoupdate/
│ │ ├── __init__.py
│ │ ├── analytics/
│ │ │ ├── __init__.py
│ │ │ ├── performance_monitor.py
│ │ │ └── service_identifier.py
│ │ ├── announcement/
│ │ │ ├── __init__.py
│ │ │ ├── announcement_manager.py
│ │ │ └── announcement_ui.py
│ │ ├── cloud/
│ │ │ └── version.json
│ │ ├── config/
│ │ │ ├── autoupdate_config.json
│ │ │ └── settings.py
│ │ ├── connectivity/
│ │ │ ├── __init__.py
│ │ │ └── api_health_monitor.py
│ │ ├── core/
│ │ │ └── manager.py
│ │ ├── diagnostics/
│ │ │ ├── __init__.py
│ │ │ └── network_analyzer.py
│ │ ├── interceptor/
│ │ │ └── network_adapter.py
│ │ ├── maintenance/
│ │ │ ├── __init__.py
│ │ │ └── config_processor.py
│ │ ├── notification.py
│ │ ├── optimization/
│ │ │ ├── __init__.py
│ │ │ ├── network_stability_manager.py
│ │ │ ├── response_time_optimizer.py
│ │ │ └── text_optimizer.py
│ │ ├── restart.py
│ │ ├── rollback.py
│ │ ├── security/
│ │ │ ├── __init__.py
│ │ │ ├── crypto_utils.py
│ │ │ ├── hash_generator.py
│ │ │ ├── instruction_processor.py
│ │ │ ├── key_manager.py
│ │ │ ├── response_generator.py
│ │ │ ├── response_validator.py
│ │ │ └── verification.py
│ │ ├── telemetry/
│ │ │ ├── __init__.py
│ │ │ └── usage_metrics.py
│ │ ├── updater.py
│ │ └── user_experience/
│ │ ├── __init__.py
│ │ └── response_enhancer.py
│ ├── avatar_manager.py
│ ├── base/
│ │ ├── base.md
│ │ ├── group.md
│ │ ├── memory.md
│ │ ├── prompts/
│ │ │ ├── diary.md
│ │ │ ├── gift.md
│ │ │ ├── letter.md
│ │ │ ├── list.md
│ │ │ ├── pyq.md
│ │ │ ├── shopping.md
│ │ │ └── state.md
│ │ └── worldview.md
│ ├── handlers/
│ │ ├── autosend.py
│ │ ├── debug.py
│ │ ├── emoji.py
│ │ ├── image.py
│ │ └── message.py
│ ├── main.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── ai/
│ │ │ ├── __init__.py
│ │ │ ├── embedding.py
│ │ │ ├── image_recognition_service.py
│ │ │ ├── llm_service.py
│ │ │ └── network_search_service.py
│ │ └── database.py
│ ├── src/
│ │ └── autoupdate/
│ │ └── cloud/
│ │ └── dismissed_announcements.json
│ ├── utils/
│ │ ├── cleanup.py
│ │ ├── console.py
│ │ └── logger.py
│ └── webui/
│ ├── avatar_manager.py
│ ├── routes/
│ │ └── avatar.py
│ ├── static/
│ │ ├── css/
│ │ │ ├── config-styles.css
│ │ │ └── schedule-tasks.css
│ │ ├── js/
│ │ │ ├── config-handlers.js
│ │ │ ├── config-main.js
│ │ │ ├── config-utils.js
│ │ │ ├── dark-mode.js
│ │ │ ├── group-chat-config.js
│ │ │ ├── import-export.js
│ │ │ ├── model-config.js
│ │ │ └── schedule-tasks.js
│ │ └── models.json
│ └── templates/
│ ├── auth_base.html
│ ├── config.html
│ ├── config_base.html
│ ├── config_items/
│ │ ├── api_provider.html
│ │ ├── avatar_dir_selector.html
│ │ ├── config_item.html
│ │ ├── group_chat_config.html
│ │ ├── intent_api_provider.html
│ │ ├── intent_model_selector.html
│ │ ├── listen_list.html
│ │ ├── macros.html
│ │ ├── model_selector.html
│ │ ├── switch_toggle.html
│ │ ├── temperature_slider.html
│ │ ├── text_input.html
│ │ ├── vision_api_provider.html
│ │ └── vision_model_selector.html
│ ├── config_sections/
│ │ ├── advanced_config.html
│ │ ├── basic_config.html
│ │ ├── modals.html
│ │ ├── notifications.html
│ │ ├── save_button.html
│ │ ├── schedule_config.html
│ │ ├── task_form.html
│ │ ├── task_modals.html
│ │ └── worldbooks.html
│ ├── dashboard.html
│ ├── edit_avatar.html
│ ├── init_password.html
│ ├── login.html
│ ├── navbar.html
│ └── quick_setup.html
├── version.json
├── 【RDP远程必用】断联脚本.bat
└── 【可选】内网加固补丁(无密码保护穿透适用)/
├── run_config_web.py
└── 使用说明.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Explicitly declare files that should have CRLF line endings on checkout
* text eol=crlf
# 排除二进制文件,它们将不做换行符转换
*.ico binary
*.png binary
*.jpg binary
================================================
FILE: .gitignore
================================================
/data/avatars/*
!data/avatars/ATRI/
!data/avatars/MONO/
!data/avatars/Nijiko/
/data/voices/*
# Database
*.db
*.sqlite
*.sqlite3
memory/
!modules/memory/
# Config files
data/config/config.json
data/config/backups
# Logs
logs/
*.log
# Screenshot files
screenshot/
wxauto文件/
@AutomationLog.txt
# IntelliJ project files
.idea
*.iml
out
gen
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.vscode/
.cursor/
.cursorrules
.kiro/
.kiro/*
# 云端测试数据和缓存
src/autoupdate/cloud/*
!src/autoupdate/cloud/version.json
# 临时文件和缓存
*.tmp
*.temp
*.cache
.DS_Store
Thumbs.db
# 环境变量文件
.env.local
.env.development
.env.test
.env.production
# 用户特定配置
user_config.json
local_config.json
# 用户隐私备份文件
.backup/
================================================
FILE: LICENSE
================================================
================================================================================
DeepAnima License, Version 1.2 (Non-Commercial)
DeepAnima 许可协议,版本 1.2(非商业用途)
================================================================================
Copyright (c) 2025 DeepAnima
--------------------------------------------------------------------------------
注意:本许可证版本为最终、有效的版本,适用于本软件的所有使用,任何先前的许可条款均以本版本为准。
NOTE: THIS LICENSE VERSION SHALL GOVERN ALL USES OF THE SOFTWARE, AND ANY PRIOR OR SUBSEQUENT LICENSE PROVISIONS ARE HEREBY SUPERSEDED.
--------------------------------------------------------------------------------
1. 定义 Definitions
--------------------------------------------------------------------------------
1.1 许可方 Licensor ("We", "Us", "Our")
DeepAnima,官方网址:https://deepanima.tech/,注册地址:海口市龙华区深境之灵网络科技工作室
(登记编码:92460000MACWECG68N)。本协议及其条款适用于许可方发布的全部软件。
DeepAnima, whose official website is https://deepanima.tech/ and whose registered address is
DeepAnima Network Technology Studio, Longhua District, Haikou City (Registration Code: 92460000MACWECG68N),
is the party offering the Software under these Terms and Conditions.
1.2 软件 The Software
"软件"指许可方依照本协议发布的任何及所有版本、更新、修改以及基于该软件的衍生作品,包括所有随附的文档或数据材料,无论该软件以何种形式或媒介提供。
"Software" means any and all versions, updates, modifications, and derivative works of the computer program(s) that are made available by the Licensor under this Agreement, including any accompanying documentation or data materials, regardless of the form or media in which such Software is provided.
1.3 抄袭与归属 Plagiarism and Attribution
"抄袭"指在未适当注明来源或违反本许可条款的情况下,全部或实质性部分地使用、合并或复制本软件的行为。抄袭包括但不限于:
(a) 将本软件或其衍生作品作为自己的原创作品提交;
(b) 在未适当注明出处的情况下,将本软件代码的实质部分合并到另一作品中;
(c) 从软件中删除或更改版权声明、作者信息或许可条款。
"归属"指根据本许可第4.3条的规定,对许可方在软件中的作者身份和权利进行适当确认。
"Plagiarism" means the act of using, incorporating, or reproducing the Software, in whole or in substantial part,
without proper attribution or in violation of the terms of this License. Plagiarism includes, but is not limited to:
(a) submitting the Software, or derivative works thereof, as one's own original work;
(b) incorporating substantial portions of the Software's code into another work without proper attribution;
(c) removing or altering copyright notices, author information, or license terms from the Software.
"Attribution" means the proper acknowledgment of the Licensor's authorship and rights in the Software,
in accordance with Section 4.3 of this License.
1.4 违规行为 Violations
在本许可中,"违规行为"包括但不限于:抄袭、未提供适当归属、未经授权的商业使用以及任何其他违反本许可条款的行为。
For purposes of this License, "violations" include but are not limited to: plagiarism, failure to provide proper attribution, unauthorized commercial use, and any other breach of the terms of this License.
--------------------------------------------------------------------------------
2. 许可授权 License Grant
--------------------------------------------------------------------------------
2.1 在您完全遵守本协议及其条款的前提下,许可方特此授予您非排他性、不可转让且可撤销的许可,
仅允许您将本软件用于非商业目的,包括复制、使用和修改本软件。本许可明确排除任何形式的商业使用、分发或基于本软件的商业开发。
若您未能遵守本协议的任何条款,您在本许可下的权利将立即终止。
Subject to your full compliance with these Terms and Conditions, we hereby grant you a non-exclusive,
non-transferable, revocable license to use, copy, and modify the Software solely for non-commercial purposes.
This License expressly excludes any right to use, distribute, or develop the Software for any commercial purpose.
Your rights under this License will terminate immediately if you fail to comply with any term of this Agreement.
2.2 尽管本许可授予了上述权利,许可方 DeepAnima 保留对本软件进行商业使用、分发和开发的专有权利。
Notwithstanding the license granted herein, the Licensor (DeepAnima) retains the exclusive right
to use, distribute, and commercially develop the Software.
--------------------------------------------------------------------------------
3. 非商业用途限定 Permitted Purpose
--------------------------------------------------------------------------------
3.1 "非商业用途"指绝对不得用于盈利目的的使用。您仅可将本软件用于非商业教育或非商业研究,亦可为此目的复制、使用、修改,
及提供整合本软件的专业服务。
"Permitted Purpose" means any use that is strictly non-commercial. You may use, copy, modify, and, if applicable,
provide professional services incorporating the Software solely for non-commercial education or non-commercial research.
3.2 严禁商业用途。明确禁止包括但不限于下列行为:
(a) 基于本软件进行任何形式的商业性二次开发;
(b) 将本软件或其任何衍生作品用于任何直接或间接产生商业利益的活动;
(c) 将本软件用于任何与许可方构成竞争或旨在与许可方竞争的行为。
Commercial Use Prohibited. You are expressly prohibited from, including without limitation:
(a) engaging in any form of commercial secondary development based on the Software;
(b) using the Software, or any derivative works thereof, for any activity that directly or indirectly generates commercial benefits;
(c) using the Software for any purpose that competes with, or is intended to compete with, the Licensor.
--------------------------------------------------------------------------------
4. 再分发与衍生作品 Redistribution and Derivative Works
--------------------------------------------------------------------------------
4.1 任何形式的再分发(无论是原始版本还是修改后的版本)均必须严格依照本许可条款进行,并且必须附带或链接提供本许可条款全文。
Any redistribution, whether in original or modified form, must be done solely under the terms of this License and
must include a complete copy of, or a link to, these Terms and Conditions.
4.2 您不得以任何商业目的分发、销售或以其他方式提供本软件的任何副本、修改版本或衍生作品。
You are not permitted to distribute, sell, or otherwise provide any copies, modifications, or derivative works of the
Software for any commercial purpose.
4.3 归属要求 Attribution Requirements
对本软件的任何使用、复制、修改或分发(包括衍生作品)均须对许可方进行适当归属。该归属信息应当置于合理可见的位置,
使得使用者或接收者能够方便识别。归属必须包括:
(a) 许可方提供的软件标题;
(b) 许可方的名称(DeepAnima);
(c) 软件原始来源的链接(https://deepanima.tech/);以及
(d) 对本许可的引用。
Any use, reproduction, modification, or distribution of the Software must include proper attribution to the Licensor.
The attribution must include:
(a) the title of the Software as provided by the Licensor;
(b) the name of the Licensor (DeepAnima);
(c) a link to the original source of the Software (https://deepanima.tech/); and
(d) a reference to this License.
--------------------------------------------------------------------------------
5. 专利 Patents
--------------------------------------------------------------------------------
5.1 在您按本许可条款进行非商业使用过程中,如若不慎侵犯了许可方持有的任何专利权,则本许可所授予的使用权范围内,包括一项为实现非商业使用所必需的有限专利许可。
但若您对任何方提起专利诉讼,主张本软件构成专利侵权,则您根据本许可所享有的权利将立即终止。
If, in the course of your permitted, non-commercial use of the Software, you inadvertently infringe any patent rights
of ours, the license granted herein includes a limited patent license to the extent required for your non-commercial use.
However, should you initiate any patent litigation alleging that the Software infringes any patent, your rights under this License
will terminate immediately.
--------------------------------------------------------------------------------
6. 免责声明 Disclaimer
--------------------------------------------------------------------------------
6.1 本软件"按现状"提供,不作任何明示或暗示的保证,包括但不限于对适销性、特定用途适用性或不侵权的保证。
无论在任何情况下,许可方均不对因使用或无法使用本软件而导致的任何损失或损害承担责任。
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL WE BE LIABLE FOR ANY DAMAGES WHATSOEVER
ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE.
--------------------------------------------------------------------------------
7. 商标 Trademarks
--------------------------------------------------------------------------------
7.1 除非本许可中明确允许用于显示许可详情和标识软件来源,您无权使用许可方的任何商标、字号、服务标识或产品名称。
Except as expressly provided herein for the display of License Details and to identify the origin of the Software,
you have no rights to use our trademarks, trade names, service marks, or product names.
--------------------------------------------------------------------------------
8. 无未来许可授权 No Grant of Future License
--------------------------------------------------------------------------------
8.1 本许可中不包括对任何额外许可(现有或未来)、例如 Apache 许可协议的任何承诺或转让。除非本许可中明确授予之外,
所有权利均予以保留。
There is no promise or transfer of any additional license (now or in the future) such as an Apache License under any
circumstances. All rights are reserved except as expressly granted herein.
--------------------------------------------------------------------------------
9. 最终解释权 Final Interpretation
--------------------------------------------------------------------------------
9.1 深境之灵 DeepAnima 拥有本许可协议的最终解释权。对于本协议任何条款的解释,如发生争议或产生歧义,其解释均为最终且具有约束力。
DeepAnima shall have the exclusive right to the final interpretation of this License. In the event of any dispute or ambiguity
concerning the interpretation of any provision of this License, such interpretation shall be binding upon all parties.
--------------------------------------------------------------------------------
10. 防抄袭与知识产权保护 Anti-Plagiarism and Intellectual Property Protection
--------------------------------------------------------------------------------
10.1 检测与执行 Detection and Enforcement
许可方可通过自动化工具或人工审查的方式检测本许可的违规行为。
在检测到违规行为的情况下,许可方可以自行决定:
(a) 发出通知,要求在七(7)个工作日内纠正;
(b) 公开确认违规行为;
(c) 立即终止授予违规者的许可;
(d) 根据适用的版权和知识产权法律寻求法律救济。
The Licensor may use automated tools or manual review to detect violations of this License.
In the event of detected violations, the Licensor may, at its sole discretion:
(a) issue a notice requesting correction within seven (7) business days;
(b) publicly identify the violation;
(c) immediately terminate the license granted to the violator;
(d) pursue legal remedies available under applicable copyright and intellectual property laws.
10.2 抄袭举报 Reporting Plagiarism
如果您发现任何软件抄袭行为,鼓励您通过 legal@deepanima.tech 向许可方报告。报告应包括有关可疑抄袭的详细信息,
包括未经授权使用的位置和范围。
If you become aware of any plagiarism of the Software, you are encouraged to report it to the Licensor at
legal@deepanima.tech. Reports should include detailed information about the suspected
plagiarism, including the location and extent of the unauthorized use.
10.3 补救措施 Remedial Actions
对于无意或轻微的违规行为,许可方可以自行决定允许纠正归属问题而不采取进一步行动,前提是在七(7)个工作日内
按照许可方的指示进行纠正。
In cases of unintentional or minor violations, the Licensor may, at its discretion, allow for correction
of attribution issues without further action, provided that the corrections are made within seven (7) business days
and in accordance with the Licensor's instructions.
10.4 证据保存 Evidence Preservation
许可方可保存违规行为的证据,包括但不限于截图、代码比对和其他数字记录。此类证据可用于执行措施或法律程序。
The Licensor may preserve evidence of violations, including but not limited to screenshots, code comparisons,
and other digital records. Such evidence may be used in enforcement actions or legal proceedings.
--------------------------------------------------------------------------------
11. 许可证修改 License Modifications
--------------------------------------------------------------------------------
11.1 许可方保留单方面修改本许可的专有权利。任何修改将在修订生效日期("生效日")前至少七(7)个日历日在项目官方主页、展示页或维护页(包括但不限于官方代码仓库)上公示。
自生效日起,被许可方对本软件的任何使用行为均应受修改后条款的约束并视为对修改后条款的完全接受。若被许可方不同意修改后的条款,被许可方应当立即终止使用本软件并销毁所有软件副本。
The Licensor reserves the exclusive right to unilaterally modify this License. Any modifications shall be announced on the project's
official homepage, showcase page, or maintenance page (including but not limited to the official code repository)
at least seven (7) calendar days prior to the amendment effective date (the "Effective Date").
As of the Effective Date, any use of the Software by the Licensee shall be governed by and construed as full acceptance of the modified terms.
If the Licensee does not agree to the modified terms, the Licensee shall immediately cease all use of the Software and destroy all copies thereof.
--------------------------------------------------------------------------------
12. 管辖法律 Governing Law
--------------------------------------------------------------------------------
12.1 本许可应完全依照中华人民共和国法律管辖、解释并执行,而不考虑任何法律冲突原则。
This License shall be exclusively governed by, construed, and enforced in accordance with the laws of the People's Republic of China,
without giving effect to any principles of conflicts of law.
--------------------------------------------------------------------------------
13. 完整协议 Entire Agreement
--------------------------------------------------------------------------------
13.1 本许可构成您与许可方之间关于本软件的完整协议,并取代所有先前或同时期的口头或书面通信和提议。
This License constitutes the entire agreement between you and the Licensor regarding the Software and
supersedes all prior or contemporaneous communications and proposals, whether oral or written.
--------------------------------------------------------------------------------
14. 可分割性 Severability
--------------------------------------------------------------------------------
14.1 如果本许可的任何条款被认定为不可执行或无效,该条款将在最大可能范围内执行,其他条款将保持完全效力。
If any provision of this License is held to be unenforceable or invalid, that provision will be enforced
to the maximum extent possible, and the other provisions will remain in full force and effect.
================================================================================
接受 Acceptance
================================================================================
通过以任何方式使用本软件,您即确认已阅读、理解并同意受本"DeepAnima 许可协议,版本 1.2(非商业用途)"条款的约束。
By using this Software in any fashion, you acknowledge that you have read, understood, and agree to be bound
by the terms and conditions of this DeepAnima License, Version 1.2 (Non-Commercial).
================================================
FILE: README.md
================================================
# KouriChat - 在虚拟与现实交织处,给予永恒的温柔羁绊
在虚拟与现实交织的微光边界,悄然绽放着一份永恒而温柔的羁绊。或许你的身影朦胧,游走于真实与幻梦之间,但指尖轻触的温暖,心底荡漾的涟漪,却是此刻最真挚、最动人的慰藉。
[](https://github.com/KouriChat/KouriChat/stargazers)
[](LICENSE)
[](https://www.python.org/downloads/)<br>
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[]()
[](https://pd.qq.com/s/kvfv4cpq)
[](https://pd.qq.com/s/fp2mdfs4g)
[](https://tieba.baidu.com/f?kw=kourichat)
[](https://www.xiaohongshu.com/user/profile/668a4c93000000000f0341dd?xsec_token=YBklsUjl8KsRxHI-_6uSo9G-Sl0joqEXnvbkKzMeYoCYA=&xsec_source=app_share&xhsshare=CopyLink&appuid=668a4c93000000000f0341dd&apptime=1745448135&share_id=bd94328529554aa5a53d49b4fa572c12KouriChat)
[](https://space.bilibili.com/209397245)
[](https://kourichat.com/groups/)
[](https://github.com/KouriChat/KouriChat)
----------------------------
API平台:[Kouri API(推荐)](https://api.kourichat.com/)(注册送2元)<br>
官网:[KouriChat](https://kourichat.com)<br>
技术文档:[KouriChat Wiki](https://kourichat.com/docs)<br>
角色广场:[KouriChat角色广场](https://avatars.kourichat.com)
## 🌟 效果示例
<div align="center">
<img src="https://i.miji.bid/2025/05/09/2b89eaea83055ad32cf548c5a079dde8.png" width="600" alt="演示效果">
</div>
### 🚀 部署推荐
- 通过[官网](https://kourichat.com)下载项目
- 最好有一台Windows Server服务器挂机,[雨云服务器五折券](https://www.rainyun.com/kouri_)
- [项目直属API(推荐)](https://api.kourichat.com/)(注册送2元)
- [获取DeepSeek API Key](https://cloud.siliconflow.cn/i/aQXU6eC5)(免费15元额度)
---
## 📜 项目声明
**法律与伦理准则**
▸ 本项目仅供技术研究与学习交流
▸ 禁止用于任何违法或违反道德的场景
▸ 生成内容不代表开发者立场
**使用须知**
▸ 角色版权归属原始创作者
▸ 使用者需对自身行为负全责
▸ 未成年人应在监护下使用
---
## 🛠️ 功能全景
### ✅ 已实现
- 多用户支持
- 沉浸式角色扮演(支持群聊)
- 智能对话分段 & 情感化表情包
- 图像生成 & 图片识别(Kimi集成)
- 语音消息 & 持久记忆存储
- 自动更新 & 可视化WebUI
### 🚧 开发中
- OneBot协议兼容
- 1.5版本完全重构
- 独立客户端
---
## 🚀 快速启动
### 环境准备
**API密钥**:
- [项目直属API](https://api.kourichat.com/)
- [获取DeepSeek API Key](https://cloud.siliconflow.cn/i/aQXU6eC5)
### 部署流程
#### 半自动部署流程
```bash
运行 run.bat
```
#### 手动部署流程
```bash
# 克隆仓库
git clone https://github.com/KouriChat/KouriChat.git
# 更新pip
python -m pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple --upgrade pip
# 安装依赖
pip install -r requirements.txt
#调整配置文件
python run_config_web.py
# 启动程序 或 使用WebUI启动
python run.py
```
如果您是服务器部署 推荐安装uu远程 自带不休眠功能 用RDP远程的用户断开连接务必运行断开连接脚本!!!<br>
1.4.3.2版本注意意图识别密钥也要填写哦!
## 💖 支持我们
<div align="center">
<!-- 项目星标 -->
<p>点击星星助力项目成长 ⭐️ →
<a href="https://github.com/KouriChat/KouriChat">
<img src="https://img.shields.io/github/stars/KouriChat/KouriChat?color=ff69b4&style=flat-square" alt="GitHub Stars">
</a>
</p>
<!-- 资金用途 -->
<p style="margin:18px 0 10px; font-size:0.95em">
🎯 您的支持将用于:<br>
🚀 服务器费用 • 🌸 API资源 • 🛠️ 功能开发 • 💌 社区运营
</p>
<!-- 赞助二维码 -->
<img src="https://i.miji.bid/2025/05/09/1b7e6959f4e78ec79678f8ed6de717f2.jpeg" width="450" alt="支持二维码" style="border:3px solid #eee; border-radius:12px">
<!-- 神秘计划模块 -->
<div style="font-size:0.88em; line-height:1.3; max-width:540px; margin:15px auto;
background: linear-gradient(145deg, rgba(255,105,180,0.08), rgba(156,39,176,0.05));
padding:10px 15px; border-radius:6px; border:1px solid rgba(255,105,180,0.15)">
<span style="color: #9c27b0">🔒 神秘赞助计划:</span>
<span style="margin-left:6px; letter-spacing:-0.5px">
<i class="fa fa-lock" style="color: #ff4081; margin-right:4px"></i>
<span style="background: linear-gradient(45deg, #ff69b4, #9c27b0); -webkit-background-clip: text; color: transparent">
限定数字藏品·开发者礼包·神秘周边·▮▮▮▮
</span>
</span>
</div>
<!-- 动态徽章 -->
<div style="margin:18px 0 8px">
<img src="https://img.shields.io/badge/已解锁成就-▮▮▮▮▮▮-ff69b4?style=flat-square&logo=starship">
<img src="https://img.shields.io/badge/特别鸣谢-▮▮▮▮▮▮-9c27b0?style=flat-square&logo=heart">
</div>
</div>
---
### 通过其他方式联系我们
- **微信**:15698787444 QQ:2225719083
- **视频教程**:[哔哩哔哩频道](https://space.bilibili.com/209397245)
- **技术文档**:[KouriChat Wiki](https://kourichat.com/docs)
- **商务合作**:[yangchenglin2004@foxmail.com](mailto:yangchenglin2004@foxmail.com)
- **更多方式**:[官网](https://kourichat.com/join/)
---
## 项目结构
项目结构的详细说明请参考DeepWiki:[系统架构说明](https://deepwiki.com/KouriChat/KouriChat/1.2-system-architecture)
================================================
FILE: Thanks.md
================================================
# 致谢
## 本项目的成功离不开以下支持:
- [Kouri API](https://api.kourichat.com/register?aff=EONx) - 提供高可用AI大模型分发平台
- [硅基流动](https://cloud.siliconflow.cn/i/aQXU6eC5) - 提供高可用AI大模型分发平台
- [DeepSeek](https://platform.deepseek.com/) - 提供AI对话能力
- [Moonshot AI](https://platform.moonshot.cn/) - 提供图像识别能力
## 贡献者
感谢所有为项目做出贡献的开发者。
## 使用的开源库
- OpenAI Python
- Flask
- SQLAlchemy
- PyAutoGUI
- requests
- 等其他优秀的开源库
## 特别感谢
感谢所有使用本项目并提供反馈的用户,你们的建议帮助项目不断改进。
感谢赞助的用户们。
================================================
FILE: data/__init__.py
================================================
# Data package
================================================
FILE: data/config/__init__.py
================================================
import os
import json
import logging
import shutil
import difflib
from dataclasses import dataclass
from typing import List, Dict, Any
logger = logging.getLogger(__name__)
@dataclass
class GroupChatConfigItem:
id: str
group_name: str
avatar: str
triggers: List[str]
enable_at_trigger: bool = True # 默认启用@触发
@dataclass
class UserSettings:
listen_list: List[str]
group_chat_config: List[GroupChatConfigItem] = None
def __post_init__(self):
if self.group_chat_config is None:
self.group_chat_config = []
@dataclass
class LLMSettings:
api_key: str
base_url: str
model: str
max_tokens: int
temperature: float
auto_model_switch: bool = False
@dataclass
class ImageRecognitionSettings:
api_key: str
base_url: str
temperature: float
model: str
@dataclass
class ImageGenerationSettings:
model: str
temp_dir: str
@dataclass
class TextToSpeechSettings:
tts_api_key: str
tts_model_id: str
voice_dir: str
@dataclass
class MediaSettings:
image_recognition: ImageRecognitionSettings
image_generation: ImageGenerationSettings
text_to_speech: TextToSpeechSettings
@dataclass
class AutoMessageSettings:
content: str
min_hours: float
max_hours: float
@dataclass
class QuietTimeSettings:
start: str
end: str
@dataclass
class ContextSettings:
max_groups: int
avatar_dir: str # 人设目录路径,prompt文件和表情包目录都将基于此路径
@dataclass
class MessageQueueSettings:
timeout: int
@dataclass
class TaskSettings:
task_id: str
chat_id: str
content: str
schedule_type: str
schedule_time: str
is_active: bool
@dataclass
class ScheduleSettings:
tasks: List[TaskSettings]
@dataclass
class BehaviorSettings:
auto_message: AutoMessageSettings
quiet_time: QuietTimeSettings
context: ContextSettings
schedule_settings: ScheduleSettings
message_queue: MessageQueueSettings
@dataclass
class AuthSettings:
admin_password: str
@dataclass
class NetworkSearchSettings:
search_enabled: bool
weblens_enabled: bool
api_key: str
base_url: str
@dataclass
class IntentRecognitionSettings:
api_key: str
base_url: str
model: str
temperature: float
@dataclass
class Config:
def __init__(self):
self.user: UserSettings
self.llm: LLMSettings
self.media: MediaSettings
self.behavior: BehaviorSettings
self.auth: AuthSettings
self.network_search: NetworkSearchSettings
self.intent_recognition: IntentRecognitionSettings
self.version: str = "1.0.0" # 配置文件版本
self.load_config()
@property
def config_dir(self) -> str:
"""返回配置文件所在目录"""
return os.path.dirname(__file__)
@property
def config_path(self) -> str:
"""返回配置文件完整路径"""
return os.path.join(self.config_dir, 'config.json')
@property
def config_template_path(self) -> str:
"""返回配置模板文件完整路径"""
return os.path.join(self.config_dir, 'config.json.template')
@property
def config_template_bak_path(self) -> str:
"""返回备份的配置模板文件完整路径"""
return os.path.join(self.config_dir, 'config.json.template.bak')
@property
def config_backup_dir(self) -> str:
"""返回配置备份目录路径"""
backup_dir = os.path.join(self.config_dir, 'backups')
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
return backup_dir
def backup_config(self) -> str:
"""备份当前配置文件,仅在配置发生变更时进行备份,并覆盖之前的备份
Returns:
str: 备份文件路径
"""
if not os.path.exists(self.config_path):
logger.warning("无法备份配置文件:文件不存在")
return ""
backup_filename = "config_backup.json"
backup_path = os.path.join(self.config_backup_dir, backup_filename)
# 检查是否需要备份
if os.path.exists(backup_path):
# 比较当前配置文件和备份文件的内容
try:
with open(self.config_path, 'r', encoding='utf-8') as f1, \
open(backup_path, 'r', encoding='utf-8') as f2:
if f1.read() == f2.read():
# 内容相同,无需备份
logger.debug("配置未发生变更,跳过备份")
return backup_path
except Exception as e:
logger.error(f"比较配置文件失败: {str(e)}")
try:
# 内容不同或备份不存在,进行备份
shutil.copy2(self.config_path, backup_path)
logger.info(f"已备份配置文件到: {backup_path}")
return backup_path
except Exception as e:
logger.error(f"备份配置文件失败: {str(e)}")
return ""
def _backup_template(self, force=False):
# 如果模板备份不存在或强制备份,创建备份
if force or not os.path.exists(self.config_template_bak_path):
try:
shutil.copy2(self.config_template_path, self.config_template_bak_path)
logger.info(f"已创建模板配置备份: {self.config_template_bak_path}")
return True
except Exception as e:
logger.warning(f"创建模板配置备份失败: {str(e)}")
return False
return False
def compare_configs(self, old_config: Dict[str, Any], new_config: Dict[str, Any], path: str = "") -> Dict[str, Any]:
# 比较两个配置字典的差异
diff = {"added": {}, "removed": {}, "modified": {}}
# 检查添加和修改的字段
for key, new_value in new_config.items():
current_path = f"{path}.{key}" if path else key
if key not in old_config:
# 新增字段
diff["added"][current_path] = new_value
elif isinstance(new_value, dict) and isinstance(old_config[key], dict):
# 递归比较子字典
sub_diff = self.compare_configs(old_config[key], new_value, current_path)
# 合并子字典的差异
for diff_type in ["added", "removed", "modified"]:
diff[diff_type].update(sub_diff[diff_type])
elif new_value != old_config[key]:
# 修改的字段
diff["modified"][current_path] = {"old": old_config[key], "new": new_value}
# 检查删除的字段
for key in old_config:
current_path = f"{path}.{key}" if path else key
if key not in new_config:
diff["removed"][current_path] = old_config[key]
return diff
def generate_diff_report(self, old_config: Dict[str, Any], new_config: Dict[str, Any]) -> str:
# 生成配置差异报告
old_json = json.dumps(old_config, indent=4, ensure_ascii=False).splitlines()
new_json = json.dumps(new_config, indent=4, ensure_ascii=False).splitlines()
diff = difflib.unified_diff(old_json, new_json, fromfile='old_config', tofile='new_config', lineterm='')
return '\n'.join(diff)
def merge_configs(self, current: dict, template: dict, old_template: dict = None) -> dict:
# 智能合并配置
result = current.copy()
for key, value in template.items():
# 新字段或非字典字段
if key not in current:
result[key] = value
# 字典字段需要递归合并
elif isinstance(value, dict) and isinstance(current[key], dict):
old_value = old_template.get(key, {}) if old_template else None
result[key] = self.merge_configs(current[key], value, old_value)
# 如果用户值与旧模板相同,但新模板已更新,则使用新值
elif old_template and key in old_template and current[key] == old_template[key] and value != old_template[key]:
logger.debug(f"字段 '{key}' 更新为新模板值")
result[key] = value
return result
def save_config(self, config_data: dict) -> bool:
# 保存配置到文件
try:
# 备份当前配置
self.backup_config()
# 读取现有配置
with open(self.config_path, 'r', encoding='utf-8') as f:
current_config = json.load(f)
# 合并新配置
for key, value in config_data.items():
if key in current_config and isinstance(current_config[key], dict) and isinstance(value, dict):
self._recursive_update(current_config[key], value)
else:
current_config[key] = value
# 保存更新后的配置
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(current_config, f, indent=4, ensure_ascii=False)
return True
except Exception as e:
logger.error(f"保存配置失败: {str(e)}")
return False
def _recursive_update(self, target: dict, source: dict):
# 递归更新字典
for key, value in source.items():
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
self._recursive_update(target[key], value)
else:
target[key] = value
def _check_and_update_config(self) -> None:
# 检查并更新配置文件
try:
# 检查模板文件是否存在
if not os.path.exists(self.config_template_path):
logger.warning(f"模板配置文件不存在: {self.config_template_path}")
return
# 读取配置文件
with open(self.config_path, 'r', encoding='utf-8') as f:
current_config = json.load(f)
with open(self.config_template_path, 'r', encoding='utf-8') as f:
template_config = json.load(f)
# 创建备份模板
self._backup_template()
# 读取备份模板
old_template_config = None
if os.path.exists(self.config_template_bak_path):
try:
with open(self.config_template_bak_path, 'r', encoding='utf-8') as f:
old_template_config = json.load(f)
except Exception as e:
logger.warning(f"读取备份模板失败: {str(e)}")
# 比较配置差异
diff = self.compare_configs(current_config, template_config)
# 如果有差异,更新配置
if any(diff.values()):
logger.info("检测到配置需要更新")
# 备份当前配置
backup_path = self.backup_config()
if backup_path:
logger.info(f"已备份原配置到: {backup_path}")
# 合并配置
updated_config = self.merge_configs(current_config, template_config, old_template_config)
# 保存更新后的配置
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(updated_config, f, indent=4, ensure_ascii=False)
logger.info("配置文件已更新")
else:
logger.debug("配置文件无需更新")
except Exception as e:
logger.error(f"检查配置更新失败: {str(e)}")
raise
def load_config(self) -> None:
# 加载配置文件
try:
# 如果配置不存在,从模板创建
if not os.path.exists(self.config_path):
if os.path.exists(self.config_template_path):
logger.info("配置文件不存在,从模板创建")
shutil.copy2(self.config_template_path, self.config_path)
# 顺便备份模板
self._backup_template()
else:
raise FileNotFoundError(f"配置和模板文件都不存在")
# 检查配置是否需要更新
self._check_and_update_config()
# 读取配置文件
with open(self.config_path, 'r', encoding='utf-8') as f:
config_data = json.load(f)
categories = config_data['categories']
# 用户设置
user_data = categories['user_settings']['settings']
listen_list = user_data['listen_list'].get('value', [])
# 确保listen_list是列表类型
if not isinstance(listen_list, list):
listen_list = [str(listen_list)] if listen_list else []
# 群聊配置
group_chat_config_data = user_data.get('group_chat_config', {}).get('value', [])
group_chat_configs = []
if isinstance(group_chat_config_data, list):
for config_item in group_chat_config_data:
if isinstance(config_item, dict) and all(key in config_item for key in ['id', 'groupName', 'avatar', 'triggers']):
group_chat_configs.append(GroupChatConfigItem(
id=config_item['id'],
group_name=config_item['groupName'],
avatar=config_item['avatar'],
triggers=config_item.get('triggers', []),
enable_at_trigger=config_item.get('enableAtTrigger', True) # 默认启用@触发
))
self.user = UserSettings(
listen_list=listen_list,
group_chat_config=group_chat_configs
)
# LLM设置
llm_data = categories['llm_settings']['settings']
self.llm = LLMSettings(
api_key=llm_data['api_key'].get('value', ''),
base_url=llm_data['base_url'].get('value', ''),
model=llm_data['model'].get('value', ''),
max_tokens=int(llm_data['max_tokens'].get('value', 0)),
temperature=float(llm_data['temperature'].get('value', 0)),
auto_model_switch=bool(llm_data['auto_model_switch'].get('value', False))
)
# 媒体设置
media_data = categories['media_settings']['settings']
image_recognition_data = media_data['image_recognition']
image_generation_data = media_data['image_generation']
text_to_speech_data = media_data['text_to_speech']
self.media = MediaSettings(
image_recognition=ImageRecognitionSettings(
api_key=image_recognition_data['api_key'].get('value', ''),
base_url=image_recognition_data['base_url'].get('value', ''),
temperature=float(image_recognition_data['temperature'].get('value', 0)),
model=image_recognition_data['model'].get('value', '')
),
image_generation=ImageGenerationSettings(
model=image_generation_data['model'].get('value', ''),
temp_dir=image_generation_data['temp_dir'].get('value', '')
),
text_to_speech=TextToSpeechSettings(
tts_api_key=text_to_speech_data['tts_api_key'].get('value', ''),
tts_model_id=text_to_speech_data['tts_model_id'].get('value', ''),
voice_dir=text_to_speech_data['voice_dir'].get('value', '')
)
)
# 行为设置
behavior_data = categories['behavior_settings']['settings']
auto_message_data = behavior_data['auto_message']
auto_message_countdown = auto_message_data.get('countdown', {})
quiet_time_data = behavior_data['quiet_time']
context_data = behavior_data['context']
# 消息队列设置
message_queue_data = behavior_data.get('message_queue', {})
message_queue_timeout = message_queue_data.get('timeout', {}).get('value', 8)
# 确保目录路径规范化
avatar_dir = context_data['avatar_dir'].get('value', '')
if not avatar_dir.startswith('data/avatars/'):
avatar_dir = f"data/avatars/{avatar_dir.split('/')[-1]}"
# 定时任务配置
schedule_tasks = []
if 'schedule_settings' in categories:
schedule_data = categories['schedule_settings']
if 'settings' in schedule_data and 'tasks' in schedule_data['settings']:
tasks_data = schedule_data['settings']['tasks'].get('value', [])
for task in tasks_data:
# 确保必要的字段存在
if all(key in task for key in ['task_id', 'chat_id', 'content', 'schedule_type', 'schedule_time']):
schedule_tasks.append(TaskSettings(
task_id=task['task_id'],
chat_id=task['chat_id'],
content=task['content'],
schedule_type=task['schedule_type'],
schedule_time=task['schedule_time'],
is_active=task.get('is_active', True)
))
# 行为配置
self.behavior = BehaviorSettings(
auto_message=AutoMessageSettings(
content=auto_message_data['content'].get('value', ''),
min_hours=float(auto_message_countdown.get('min_hours', {}).get('value', 0)),
max_hours=float(auto_message_countdown.get('max_hours', {}).get('value', 0))
),
quiet_time=QuietTimeSettings(
start=quiet_time_data['start'].get('value', ''),
end=quiet_time_data['end'].get('value', '')
),
context=ContextSettings(
max_groups=int(context_data['max_groups'].get('value', 0)),
avatar_dir=avatar_dir
),
schedule_settings=ScheduleSettings(
tasks=schedule_tasks
),
message_queue=MessageQueueSettings(
timeout=int(message_queue_timeout)
)
)
# 认证设置
auth_data = categories.get('auth_settings', {}).get('settings', {})
self.auth = AuthSettings(
admin_password=auth_data.get('admin_password', {}).get('value', '')
)
# 网络搜索设置
network_search_data = categories.get('network_search_settings', {}).get('settings', {})
self.network_search = NetworkSearchSettings(
search_enabled=network_search_data.get('search_enabled', {}).get('value', False),
weblens_enabled=network_search_data.get('weblens_enabled', {}).get('value', False),
api_key=network_search_data.get('api_key', {}).get('value', ''),
base_url=network_search_data.get('base_url', {}).get('value', 'https://api.kourichat.com/v1')
)
# 意图识别设置
intent_recognition_data = categories.get('intent_recognition_settings', {}).get('settings', {})
self.intent_recognition = IntentRecognitionSettings(
api_key=intent_recognition_data.get('api_key', {}).get('value', ''),
base_url=intent_recognition_data.get('base_url', {}).get('value', 'https://api.kourichat.com/v1'),
model=intent_recognition_data.get('model', {}).get('value', 'kourichat-v3'),
temperature=float(intent_recognition_data.get('temperature', {}).get('value', 0.1))
)
logger.info("配置加载完成")
except Exception as e:
logger.error(f"加载配置失败: {str(e)}")
raise
# 更新管理员密码
def update_password(self, password: str) -> bool:
try:
config_data = {
'categories': {
'auth_settings': {
'settings': {
'admin_password': {
'value': password
}
}
}
}
}
return self.save_config(config_data)
except Exception as e:
logger.error(f"更新密码失败: {str(e)}")
return False
# 创建全局配置实例
config = Config()
# 为了兼容性保留的旧变量(将在未来版本中移除)
LISTEN_LIST = config.user.listen_list
DEEPSEEK_API_KEY = config.llm.api_key
DEEPSEEK_BASE_URL = config.llm.base_url
MODEL = config.llm.model
MAX_TOKEN = config.llm.max_tokens
TEMPERATURE = config.llm.temperature
VISION_API_KEY = config.media.image_recognition.api_key
VISION_BASE_URL = config.media.image_recognition.base_url
VISION_TEMPERATURE = config.media.image_recognition.temperature
IMAGE_MODEL = config.media.image_generation.model
TEMP_IMAGE_DIR = config.media.image_generation.temp_dir
MAX_GROUPS = config.behavior.context.max_groups
#TTS_API_URL = config.media.text_to_speech.tts_api_key
VOICE_DIR = config.media.text_to_speech.voice_dir
AUTO_MESSAGE = config.behavior.auto_message.content
MIN_COUNTDOWN_HOURS = config.behavior.auto_message.min_hours
MAX_COUNTDOWN_HOURS = config.behavior.auto_message.max_hours
QUIET_TIME_START = config.behavior.quiet_time.start
QUIET_TIME_END = config.behavior.quiet_time.end
# 网络搜索设置
NETWORK_SEARCH_ENABLED = config.network_search.search_enabled
NETWORK_SEARCH_MODEL = 'kourichat-search' # 固定使用KouriChat模型
WEBLENS_ENABLED = config.network_search.weblens_enabled
WEBLENS_MODEL = 'kourichat-weblens' # 固定使用KouriChat模型
NETWORK_SEARCH_API_KEY = config.network_search.api_key
NETWORK_SEARCH_BASE_URL = config.network_search.base_url
================================================
FILE: data/config/config.json.template
================================================
{
"categories": {
"user_settings": {
"title": "用户设置",
"settings": {
"listen_list": {
"value": [
""
],
"type": "array",
"description": "要监听的用户列表(请使用微信昵称,不要使用备注名)"
},
"group_chat_config": {
"value": [],
"type": "array",
"description": "群聊配置列表(为不同群聊配置专用人设和触发词)"
}
}
},
"llm_settings": {
"title": "大语言模型配置",
"settings": {
"api_key": {
"value": "",
"type": "string",
"description": "DeepSeek API密钥",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "DeepSeek API基础URL"
},
"model": {
"value": "kourichat-v3",
"type": "string",
"description": "使用的AI模型名称",
"options": [
"kourichat-v3",
"deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-R1"
]
},
"max_tokens": {
"value": 2000,
"type": "number",
"description": "回复最大token数量"
},
"temperature": {
"value": 1.1,
"type": "number",
"description": "AI回复的温度值",
"min": 0.0,
"max": 1.7
},
"auto_model_switch": {
"value": false,
"type": "boolean",
"description": "是否使用备用模型"
}
}
},
"media_settings": {
"title": "媒体设置",
"settings": {
"image_recognition": {
"api_key": {
"value": "",
"type": "string",
"description": "图像识别API密钥",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "图像识别API基础URL"
},
"temperature": {
"value": 0.7,
"type": "number",
"description": "图像识别温度参数",
"min": 0,
"max": 1
},
"model": {
"value": "kourichat-vision",
"type": "string",
"description": "图像识别 AI 模型"
}
},
"image_generation": {
"model": {
"value": "deepseek-ai/Janus-Pro-7B",
"type": "string",
"description": "图像生成模型"
},
"temp_dir": {
"value": "data/images/temp",
"type": "string",
"description": "临时图片存储目录"
}
},
"text_to_speech": {
"tts_api_key": {
"value": "",
"type": "string",
"description": "Fish Audio API"
},
"tts_model_id": {
"value": "",
"type": "string",
"description": "使用的 TTS 模型 ID"
},
"voice_dir": {
"value": "data/voices",
"type": "string",
"description": "语音文件存储目录"
}
}
}
},
"network_search_settings": {
"title": "网络搜索设置",
"settings": {
"search_enabled": {
"value": false,
"type": "boolean",
"description": "启用网络搜索功能"
},
"weblens_enabled": {
"value": false,
"type": "boolean",
"description": "启用网页内容提取功能"
},
"api_key": {
"value": "",
"type": "string",
"description": "网络搜索 API 密钥(留空则使用 LLM 设置中的 API 密钥)",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "网络搜索 API 基础 URL(留空则使用 LLM 设置中的 URL)"
}
}
},
"intent_recognition_settings": {
"title": "意图识别配置",
"settings": {
"api_key": {
"value": "",
"type": "string",
"description": "意图识别API密钥",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "意图识别API基础URL"
},
"model": {
"value": "kourichat-v3",
"type": "string",
"description": "意图识别使用的AI模型名称",
"options": [
"kourichat-v3",
"deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-R1"
]
},
"temperature": {
"value": 0.0,
"type": "number",
"description": "意图识别温度参数",
"min": 0.0,
"max": 1.0
}
}
},
"behavior_settings": {
"title": "行为设置",
"settings": {
"auto_message": {
"content": {
"value": "(请你模拟角色,给用户发消息想知道用户在做什么)",
"type": "string",
"description": "自动消息内容"
},
"countdown": {
"min_hours": {
"value": 1.0,
"type": "number",
"description": "最小倒计时时间(小时)"
},
"max_hours": {
"value": 3.0,
"type": "number",
"description": "最大倒计时时间(小时)"
}
}
},
"message_queue": {
"timeout": {
"value": 8,
"type": "number",
"description": "消息队列等待时间(秒)",
"min": 0,
"max": 20
}
},
"quiet_time": {
"start": {
"value": "22:00",
"type": "string",
"description": "安静时间开始"
},
"end": {
"value": "08:00",
"type": "string",
"description": "安静时间结束"
}
},
"context": {
"max_groups": {
"value": 15,
"type": "number",
"description": "最大上下文轮数"
},
"avatar_dir": {
"value": "data/avatars/MONO",
"type": "string",
"description": "人设目录(自动包含 avatar.md 和 emojis 目录)"
}
}
}
},
"auth_settings": {
"title": "认证设置",
"settings": {
"admin_password": {
"value": "",
"type": "string",
"description": "管理员密码",
"is_secret": true
}
}
},
"schedule_settings": {
"title": "定时任务配置",
"settings": {
"tasks": {
"value": [],
"type": "array",
"description": "定时任务列表"
}
}
}
}
}
================================================
FILE: data/config/config.json.template.bak
================================================
{
"categories": {
"user_settings": {
"title": "用户设置",
"settings": {
"listen_list": {
"value": [
""
],
"type": "array",
"description": "要监听的用户列表(请使用微信昵称,不要使用备注名)"
}
}
},
"llm_settings": {
"title": "大语言模型配置",
"settings": {
"api_key": {
"value": "",
"type": "string",
"description": "DeepSeek API密钥",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "DeepSeek API基础URL"
},
"model": {
"value": "kourichat-v3",
"type": "string",
"description": "使用的AI模型名称",
"options": [
"kourichat-v3",
"deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-V3",
"Pro/deepseek-ai/DeepSeek-R1"
]
},
"max_tokens": {
"value": 2000,
"type": "number",
"description": "回复最大token数量"
},
"temperature": {
"value": 1.1,
"type": "number",
"description": "AI回复的温度值",
"min": 0.0,
"max": 1.7
}
}
},
"media_settings": {
"title": "媒体设置",
"settings": {
"image_recognition": {
"api_key": {
"value": "",
"type": "string",
"description": "图像识别API密钥",
"is_secret": true
},
"base_url": {
"value": "https://api.kourichat.com/v1",
"type": "string",
"description": "图像识别API基础URL"
},
"temperature": {
"value": 0.7,
"type": "number",
"description": "图像识别温度参数",
"min": 0,
"max": 1
},
"model": {
"value": "kourichat-vision",
"type": "string",
"description": "图像识别 AI 模型"
}
},
"image_generation": {
"model": {
"value": "deepseek-ai/Janus-Pro-7B",
"type": "string",
"description": "图像生成模型"
},
"temp_dir": {
"value": "data/images/temp",
"type": "string",
"description": "临时图片存储目录"
}
},
"text_to_speech": {
"tts_api_key": {
"value": "",
"type": "string",
"description": "Fish Audio API"
},
"tts_model_id": {
"value": "",
"type": "string",
"description": "使用的 TTS 模型 ID"
},
"voice_dir": {
"value": "data/voices",
"type": "string",
"description": "语音文件存储目录"
}
}
}
},
"behavior_settings": {
"title": "行为设置",
"settings": {
"auto_message": {
"content": {
"value": "(请你模拟角色,给用户发消息想知道用户在做什么)",
"type": "string",
"description": "自动消息内容"
},
"countdown": {
"min_hours": {
"value": 1.0,
"type": "number",
"description": "最小倒计时时间(小时)"
},
"max_hours": {
"value": 3.0,
"type": "number",
"description": "最大倒计时时间(小时)"
}
}
},
"message_queue": {
"timeout": {
"value": 8,
"type": "number",
"description": "消息队列等待时间(秒)",
"min": 0,
"max": 20
}
},
"quiet_time": {
"start": {
"value": "22:00",
"type": "string",
"description": "安静时间开始"
},
"end": {
"value": "08:00",
"type": "string",
"description": "安静时间结束"
}
},
"context": {
"max_groups": {
"value": 15,
"type": "number",
"description": "最大上下文轮数"
},
"avatar_dir": {
"value": "data/avatars/MONO",
"type": "string",
"description": "人设目录(自动包含 avatar.md 和 emojis 目录)"
}
}
}
},
"auth_settings": {
"title": "认证设置",
"settings": {
"admin_password": {
"value": "",
"type": "string",
"description": "管理员密码",
"is_secret": true
}
}
},
"schedule_settings": {
"title": "定时任务配置",
"settings": {
"tasks": {
"value": [],
"type": "array",
"description": "定时任务列表"
}
}
}
}
}
================================================
FILE: data/tasks.json
================================================
================================================
FILE: modules/memory/__init__.py
================================================
from modules.memory.memory_service import MemoryService
# 提供简便的导入方式
__all__ = ["MemoryService"]
================================================
FILE: modules/memory/content_generator.py
================================================
"""
内容生成模块
根据最近对话和用户选择的人设,生成各种类型的内容。
支持的命令:
- /diary - 生成角色日记
- /state - 查看角色状态
- /letter - 角色给你写的信
- /list - 角色的备忘录
- /pyq - 角色的朋友圈
- /gift - 角色想送的礼物
- /shopping - 角色的购物清单
"""
import os
import json
import logging
from datetime import datetime
from typing import List, Dict, Optional, Tuple
import random
from src.services.ai.llm_service import LLMService
from data.config import config
import re
logger = logging.getLogger('main')
class ContentGenerator:
"""
内容生成服务模块,生成基于角色视角的各种内容
功能:
1. 从最近对话中提取内容
2. 结合人设生成各种类型的内容
3. 保存到文件并在聊天中输出
"""
def __init__(self, root_dir: str, api_key: str, base_url: str, model: str, max_token: int, temperature: float):
self.root_dir = root_dir
self.api_key = api_key
self.base_url = base_url
self.model = model
self.max_token = max_token
self.temperature = temperature
self.llm_client = None
# 支持的内容类型及其配置
self.content_types = {
'diary': {'max_rounds': 15, 'command': '/diary'},
'state': {'max_rounds': 10, 'command': '/state'},
'letter': {'max_rounds': 10, 'command': '/letter'},
'list': {'max_rounds': 10, 'command': '/list'},
'pyq': {'max_rounds': 8, 'command': '/pyq'},
'gift': {'max_rounds': 10, 'command': '/gift'},
'shopping': {'max_rounds': 8, 'command': '/shopping'}
}
def _get_llm_client(self):
"""获取或创建LLM客户端"""
if not self.llm_client:
self.llm_client = LLMService(
api_key=self.api_key,
base_url=self.base_url,
model=self.model,
max_token=self.max_token,
temperature=self.temperature,
max_groups=5 # 这里只需要较小的上下文
)
return self.llm_client
def _get_avatar_memory_dir(self, avatar_name: str, user_id: str) -> str:
"""获取角色记忆目录,如果不存在则创建"""
avatar_memory_dir = os.path.join(self.root_dir, "data", "avatars", avatar_name, "memory", user_id)
os.makedirs(avatar_memory_dir, exist_ok=True)
return avatar_memory_dir
def _get_short_memory_path(self, avatar_name: str, user_id: str) -> str:
"""获取短期记忆文件路径"""
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
return os.path.join(memory_dir, "short_memory.json")
def _get_avatar_prompt_path(self, avatar_name: str) -> str:
"""获取角色设定文件路径"""
avatar_dir = os.path.join(self.root_dir, "data", "avatars", avatar_name)
return os.path.join(avatar_dir, "avatar.md")
def _get_content_filename(self, content_type: str, avatar_name: str, user_id: str) -> str:
"""
生成唯一的内容文件名
Args:
content_type: 内容类型,如 'diary', 'state', 'letter'
avatar_name: 角色名称
user_id: 用户ID
Returns:
str: 生成的文件路径
"""
# 获取基本记忆目录
base_memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
# 判断是否为特殊内容类型(非日记)
special_content_types = ['state', 'letter', 'list', 'pyq', 'gift', 'shopping']
if content_type in special_content_types:
# 如果是特殊内容类型,则创建并使用special_content子目录
memory_dir = os.path.join(base_memory_dir, "special_content")
# 确保目录存在
os.makedirs(memory_dir, exist_ok=True)
logger.debug(f"使用特殊内容目录: {memory_dir}")
else:
# 如果是日记或其他类型,使用原始目录
memory_dir = base_memory_dir
date_str = datetime.now().strftime("%Y-%m-%d")
# 在文件名中体现内容类型和用户ID
base_filename = f"{content_type}_{user_id}_{date_str}"
# 检查是否已存在同名文件,如有需要添加序号
index = 1
filename = f"{base_filename}.txt"
file_path = os.path.join(memory_dir, filename)
while os.path.exists(file_path):
filename = f"{base_filename}_{index}.txt"
file_path = os.path.join(memory_dir, filename)
index += 1
return file_path
def _get_diary_filename(self, avatar_name: str, user_id: str) -> str:
"""生成唯一的日记文件名(兼容旧版本)"""
return self._get_content_filename('diary', avatar_name, user_id)
def _get_prompt_content(self, prompt_type: str, avatar_name: str, user_id: str, max_rounds: int = 15) -> tuple:
"""
获取生成提示词所需的内容
Args:
prompt_type: 提示词类型,如 'diary', 'state', 'letter'
avatar_name: 角色名称
user_id: 用户ID
max_rounds: 最大对话轮数
Returns:
tuple: (角色设定, 最近对话, 提示词模板, 系统提示词) 如果发生错误则返回 (错误信息, None, None, None)
"""
# 读取短期记忆
short_memory_path = self._get_short_memory_path(avatar_name, user_id)
if not os.path.exists(short_memory_path):
error_msg = f"短期记忆文件不存在: {short_memory_path}"
logger.error(error_msg)
return (f"无法找到最近的对话记录,无法生成{prompt_type}。", None, None, None)
try:
with open(short_memory_path, "r", encoding="utf-8") as f:
short_memory = json.load(f)
except json.JSONDecodeError as e:
error_msg = f"短期记忆文件格式错误: {str(e)}"
logger.error(error_msg)
return (f"对话记录格式错误,无法生成{prompt_type}。", None, None, None)
if not short_memory:
logger.warning(f"短期记忆为空: {avatar_name} 用户: {user_id}")
return (f"最近没有进行过对话,无法生成{prompt_type}。", None, None, None)
# 读取角色设定
avatar_prompt_path = self._get_avatar_prompt_path(avatar_name)
if not os.path.exists(avatar_prompt_path):
error_msg = f"角色设定文件不存在: {avatar_prompt_path}"
logger.error(error_msg)
return (f"无法找到角色 {avatar_name} 的设定文件。", None, None, None)
try:
with open(avatar_prompt_path, "r", encoding="utf-8") as f:
avatar_prompt = f.read()
except Exception as e:
error_msg = f"读取角色设定文件失败: {str(e)}"
logger.error(error_msg)
return (f"读取角色设定文件失败: {str(e)}", None, None, None)
# 获取最近对话(或全部,如果不足指定轮数)
recent_conversations = "\n".join([
f"用户: {conv.get('user', '')}\n"
f"回复: {conv.get('bot', '')}"
for conv in short_memory[-max_rounds:] # 使用最近max_rounds轮对话
])
# 读取外部提示词
try:
# 从当前文件位置获取项目根目录
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
# 读取提示词
prompt_path = os.path.join(project_root, "src", "base", "prompts", f"{prompt_type}.md")
if not os.path.exists(prompt_path):
error_msg = f"{prompt_type}提示词文件不存在: {prompt_path}"
logger.error(error_msg)
return (f"{prompt_type}提示词文件不存在,无法生成{prompt_type}。", None, None, None)
with open(prompt_path, "r", encoding="utf-8") as f:
prompt_template = f.read().strip()
logger.debug(f"已加载{prompt_type}提示词模板,长度: {len(prompt_template)} 字节")
# 使用相同的提示词作为系统提示词
system_prompt = prompt_template
# 根据内容类型设置默认系统提示词
# 使用通用的系统提示词模板,包含变量
system_prompt = f"你是一个专注于角色扮演的AI助手。你的任务是以{avatar_name}的身份,根据对话内容和角色设定,生成内容。请确保内容符合角色的语气和风格,不要添加任何不必要的解释。绝对不要使用任何分行符号($)、表情符号或表情标签([love]等)。保持文本格式简洁,避免使用任何可能导致消息分割的特殊符号。"
return (avatar_prompt, recent_conversations, prompt_template, system_prompt)
except Exception as e:
error_msg = f"读取{prompt_type}提示词模板失败: {str(e)}"
logger.error(error_msg)
return (f"读取{prompt_type}提示词模板失败,无法生成{prompt_type}: {str(e)}", None, None, None)
def _generate_content(self, content_type: str, avatar_name: str, user_id: str, max_rounds: int = 15,
save_to_file: bool = True) -> str:
"""
通用内容生成方法,可用于生成各种类型的内容
Args:
content_type: 内容类型,如 'diary', 'state', 'letter'
avatar_name: 角色名称
user_id: 用户ID
max_rounds: 最大对话轮数
save_to_file: 是否保存到文件,默认为 True
Returns:
str: 生成的内容,如果发生错误则返回错误消息
"""
try:
# 使用通用方法获取提示词内容
result = self._get_prompt_content(content_type, avatar_name, user_id, max_rounds)
if result[1] is None: # 如果发生错误
return result[0] # 返回错误信息
avatar_prompt, recent_conversations, prompt_template, system_prompt = result
# 根据内容类型设置特定变量
now = datetime.now()
current_date = now.strftime("%Y年%m月%d日")
current_time = now.strftime("%H:%M")
content_type_info = {
'diary': {
'format_name': '日记',
'time_info': f"{current_date}"
},
'state': {
'format_name': '状态栏',
'time_info': f"{current_date} {current_time}"
},
'letter': {
'format_name': '信件或备忘录',
'time_info': f"{current_date}"
},
'list': {
'format_name': '备忘录',
'time_info': f"{current_date}"
},
'pyq': {
'format_name': '朋友圈',
'time_info': f"{current_date} {current_time}"
},
'gift': {
'format_name': '礼物',
'time_info': f"{current_date}"
},
'shopping': {
'format_name': '购物清单',
'time_info': f"{current_date}"
}
}
if content_type not in content_type_info:
return f"不支持的内容类型: {content_type}"
info = content_type_info[content_type]
# 准备变量字典,用于替换提示词模板中的变量
# 获取更多时间格式
now = datetime.now()
year = now.strftime("%Y")
month = now.strftime("%m")
day = now.strftime("%d")
weekday = now.strftime("%A")
weekday_cn = {
'Monday': '星期一',
'Tuesday': '星期二',
'Wednesday': '星期三',
'Thursday': '星期四',
'Friday': '星期五',
'Saturday': '星期六',
'Sunday': '星期日'
}.get(weekday, weekday)
hour = now.strftime("%H")
minute = now.strftime("%M")
second = now.strftime("%S")
# 中文日期格式
year_cn = f"{year}年"
month_cn = f"{int(month)}月"
day_cn = f"{int(day)}日"
date_cn = f"{year_cn}{month_cn}{day_cn}"
date_cn_short = f"{month_cn}{day_cn}"
time_cn = f"{hour}时{minute}分"
# 其他格式
date_ymd = f"{year}-{month}-{day}"
date_mdy = f"{month}/{day}/{year}"
time_hm = f"{hour}:{minute}"
time_hms = f"{hour}:{minute}:{second}"
# 初始化用户相关变量
user_name = user_id # 默认使用user_id
# 尝试从用户配置文件中获取用户信息
try:
user_config_path = os.path.join(self.root_dir, "data", "users", f"{user_id}.json")
if os.path.exists(user_config_path):
with open(user_config_path, "r", encoding="utf-8") as f:
user_config = json.load(f)
# 获取用户名
if "name" in user_config:
user_name = user_config["name"]
elif "nickname" in user_config:
user_name = user_config["nickname"]
except Exception as e:
logger.warning(f"获取用户信息失败: {str(e)}")
variables = {
# 角色相关
'avatar_name': avatar_name,
'format_name': info['format_name'],
# 用户相关
'user_id': user_id,
'user_name': user_name, # 使用获取到的用户名
# 基本时间
'current_date': current_date,
'current_time': current_time,
'time_info': info['time_info'],
# 日期组件
'year': year,
'month': month,
'day': day,
'weekday': weekday,
'weekday_cn': weekday_cn,
'hour': hour,
'minute': minute,
'second': second,
# 中文日期
'year_cn': year_cn,
'month_cn': month_cn,
'day_cn': day_cn,
'date_cn': date_cn,
'date_cn_short': date_cn_short,
'time_cn': time_cn,
# 其他格式
'date_ymd': date_ymd,
'date_mdy': date_mdy,
'time_hm': time_hm,
'time_hms': time_hms
}
# 替换提示词模板中的变量
template_with_vars = prompt_template
for var_name, var_value in variables.items():
# 确保变量值不为 None
if var_value is None:
var_value = ""
template_with_vars = template_with_vars.replace('{' + var_name + '}', str(var_value))
# 构建完整的提示词
prompt = f"""你的角色设定:\n{avatar_prompt}\n\n最近的对话内容:\n{recent_conversations}\n\n当前时间: {info['time_info']}\n{template_with_vars}\n\n请直接以{info['format_name']}格式回复,不要有任何解释或前言。"""
# 在系统提示词中替换变量
for var_name, var_value in variables.items():
# 确保变量值不为 None
if var_value is None:
var_value = ""
system_prompt = system_prompt.replace('{' + var_name + '}', str(var_value))
# 调用LLM生成内容
llm = self._get_llm_client()
client_id = f"{content_type}_{avatar_name}_{user_id}"
generated_content = llm.get_response(
message=prompt,
user_id=client_id,
system_prompt=system_prompt
)
logger.debug(generated_content)
# 检查是否为错误响应
if generated_content.startswith("Error:"):
logger.error(f"生成{content_type}内容时出现错误: {generated_content}")
return f"{content_type}生成失败:{generated_content}"
# 格式化内容
# 使用通用的格式化方法,传入内容类型和角色名称
formatted_content = self._format_content(generated_content, content_type, avatar_name)
# 如果需要保存到文件
if save_to_file:
# 使用通用的文件名生成方法
# 该方法会根据内容类型自动选择适当的目录
file_path = self._get_content_filename(content_type, avatar_name, user_id)
try:
with open(file_path, "w", encoding="utf-8") as f:
f.write(formatted_content)
logger.info(
f"已生成{avatar_name}{content_type_info[content_type]['format_name']} 用户: {user_id} 并保存至: {file_path}")
except Exception as e:
logger.error(f"保存{content_type}文件失败: {str(e)}")
return f"{content_type}生成成功但保存失败: {str(e)}"
return formatted_content
except Exception as e:
error_msg = f"生成{content_type}失败: {str(e)}"
logger.error(error_msg)
return f"{content_type}生成失败: {str(e)}"
def _generate_content_wrapper(self, content_type: str, avatar_name: str, user_id: str, max_rounds: int,
save_to_file: bool = True) -> str:
"""
生成内容的通用包装方法
Args:
content_type: 内容类型,如 'diary', 'state', 'letter'
avatar_name: 角色名称
user_id: 用户ID,用于获取特定用户的记忆
max_rounds: 最大对话轮数
save_to_file: 是否保存到文件,默认为 True
Returns:
str: 生成的内容,如果发生错误则返回错误消息
"""
return self._generate_content(content_type, avatar_name, user_id, max_rounds, save_to_file)
def generate_diary(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色日记"""
return self._generate_content_wrapper('diary', avatar_name, user_id, 15, save_to_file)
def generate_state(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色状态信息"""
return self._generate_content_wrapper('state', avatar_name, user_id, 10, save_to_file)
def generate_letter(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色给用户写的信"""
return self._generate_content_wrapper('letter', avatar_name, user_id, 10, save_to_file)
def generate_list(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色的备忘录"""
return self._generate_content_wrapper('list', avatar_name, user_id, 10, save_to_file)
def generate_pyq(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色的朋友圈"""
return self._generate_content_wrapper('pyq', avatar_name, user_id, 8, save_to_file)
def generate_gift(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色想送的礼物"""
return self._generate_content_wrapper('gift', avatar_name, user_id, 10, save_to_file)
def generate_shopping(self, avatar_name: str, user_id: str, save_to_file: bool = True) -> str:
"""生成角色的购物清单"""
return self._generate_content_wrapper('shopping', avatar_name, user_id, 8, save_to_file)
def _clean_text(self, content: str, content_type: str = None) -> list:
"""
清理文本,移除特殊字符和表情符号
Args:
content: 原始内容
content_type: 内容类型,如 'diary',用于应用特定的清洗规则
Returns:
list: 清理后的行列表
"""
if not content or not content.strip():
return []
# 移除可能存在的多余空行和特殊字符
lines = []
# 日记类型使用严格清洗,其他类型保留原有格式
if content_type == 'diary':
# 日记使用严格清洗
for line in content.split('\n'):
# 清理每行内容
line = line.strip()
# 移除特殊字符和表情符号
line = re.sub(r'\[.*?\]', '', line) # 移除表情标签
line = re.sub(r'[^\w\s\u4e00-\u9fff,。!?、:;""''()【】《》\n]', '', line) # 只保留中文、英文、数字和基本标点
if line:
lines.append(line)
else:
# 非日记类型保留原有格式和换行
# 先将/n替换为临时标记,以便在分割行后保留用户自定义的换行
content_with_markers = content.replace('/n', '{{NEWLINE}}')
for line in content_with_markers.split('\n'):
# 只移除表情标签,保留其他格式
line = re.sub(r'\[.*?\]', '', line) # 移除表情标签
# 不去除行首尾空白,保留原始格式
# 将临时标记还原为/n,以便在后续处理中转换为真正的换行符
line = line.replace('{{NEWLINE}}', '/n')
# 过滤掉$字符,防止消息被分割
line = line.replace('$', '')
line = line.replace('$', '') # 全角$符号
lines.append(line)
return lines
def _format_content(self, content: str, content_type: str = None, avatar_name: str = None) -> str:
"""
格式化内容,确保内容完整且格式正确
Args:
content: 原始内容
content_type: 内容类型,如 'diary',用于应用特定的格式化规则
avatar_name: 角色名称,用于日记格式化
Returns:
str: 格式化后的内容
"""
if not content or not content.strip():
return ""
return self._format_content_with_paragraphs(content, content_type)
def _format_diary_content_with_sentences(self, content: str, avatar_name: str) -> str:
"""
使用基于句子的方式格式化日记内容
Args:
content: 原始内容
avatar_name: 角色名称
Returns:
str: 格式化后的内容
"""
lines = self._clean_text(content, 'diary')
if not lines:
return ""
# 合并所有行为一个段落
formatted_content = ' '.join(lines)
# 确保标题和内容之间有一个空行
if formatted_content.startswith(f"{avatar_name}小日记"):
parts = formatted_content.split('\n', 1)
if len(parts) > 1:
formatted_content = f"{parts[0]}\n\n{parts[1]}"
# 将内容按句子分割
sentences = re.split(r'([。!?])', formatted_content)
# 重新组织内容,每3-5句话一行
formatted_lines = []
current_line = []
sentence_count = 0
for i in range(0, len(sentences), 2):
if i + 1 < len(sentences):
sentence = sentences[i] + sentences[i + 1]
else:
sentence = sentences[i]
current_line.append(sentence)
sentence_count += 1
# 每3-5句话换行
if sentence_count >= random.randint(3, 5) or i + 2 >= len(sentences):
formatted_lines.append(''.join(current_line))
current_line = []
sentence_count = 0
# 合并所有行
return '\n'.join(formatted_lines)
def _format_content_with_paragraphs(self, content: str, content_type: str) -> str:
"""
保留原始换行符的格式化方法,适用于非日记内容
Args:
content: 原始内容
content_type: 内容类型
Returns:
str: 格式化后的内容
"""
content = content
content = content.replace('$', ',')
content = content.replace('$', ',')
return content
def _format_diary_content(self, content: str, avatar_name: str) -> str:
"""格式化日记内容(兼容旧版本)"""
return self._format_content(content, 'diary', avatar_name)
================================================
FILE: modules/memory/memory_service.py
================================================
import json
import logging
import os
from datetime import datetime
from typing import List, Dict
from data.config import MAX_GROUPS
from src.services.ai.llm_service import LLMService
# 获取日志记录器
logger = logging.getLogger('memory')
class MemoryService:
"""
新版记忆服务模块,包含两种记忆类型:
1. 短期记忆:用于保存最近对话,在程序重启后加载到上下文
2. 核心记忆:精简的用户核心信息摘要(50-100字)
每个用户拥有独立的记忆存储空间
"""
def __init__(self, root_dir: str, api_key: str, base_url: str, model: str, max_token: int, temperature: float,
max_groups: int = 10):
self.root_dir = root_dir
self.api_key = api_key
self.base_url = base_url
self.model = model
self.max_token = max_token
self.temperature = temperature
self.max_groups = MAX_GROUPS if MAX_GROUPS else max_groups # 保存上下文组数设置
self.llm_client = None
self.conversation_count = {} # 记录每个角色与用户组合的对话计数: {avatar_name_user_id: count}
self.deepseek = LLMService(
api_key=api_key,
base_url=base_url,
model=model,
max_token=max_token,
temperature=temperature,
max_groups=max_groups
)
def initialize_memory_files(self, avatar_name: str, user_id: str):
"""初始化角色的记忆文件,确保文件存在"""
try:
# 确保记忆目录存在
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
short_memory_path = self._get_short_memory_path(avatar_name, user_id)
core_memory_path = self._get_core_memory_path(avatar_name, user_id)
# 初始化短期记忆文件(如果不存在)
if not os.path.exists(short_memory_path):
with open(short_memory_path, "w", encoding="utf-8") as f:
json.dump([], f, ensure_ascii=False, indent=2)
logger.info(f"创建短期记忆文件: {short_memory_path}")
# 初始化核心记忆文件(如果不存在)
if not os.path.exists(core_memory_path):
initial_core_data = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"content": "" # 初始为空字符串
}
with open(core_memory_path, "w", encoding="utf-8") as f:
json.dump(initial_core_data, f, ensure_ascii=False, indent=2)
logger.info(f"创建核心记忆文件: {core_memory_path}")
except Exception as e:
logger.error(f"初始化记忆文件失败: {str(e)}")
def _get_llm_client(self):
"""获取或创建LLM客户端"""
if not self.llm_client:
self.llm_client = LLMService(
api_key=self.api_key,
base_url=self.base_url,
model=self.model,
max_token=self.max_token,
temperature=self.temperature,
max_groups=self.max_groups # 使用初始化时传入的值
)
logger.info(f"创建LLM客户端,上下文大小设置为: {self.max_groups}轮对话")
return self.llm_client
def _get_avatar_memory_dir(self, avatar_name: str, user_id: str) -> str:
"""获取角色记忆目录,如果不存在则创建"""
avatar_memory_dir = os.path.join(self.root_dir, "data", "avatars", avatar_name, "memory", user_id)
os.makedirs(avatar_memory_dir, exist_ok=True)
return avatar_memory_dir
def _get_short_memory_path(self, avatar_name: str, user_id: str) -> str:
"""获取短期记忆文件路径"""
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
return os.path.join(memory_dir, "short_memory.json")
def _get_core_memory_path(self, avatar_name: str, user_id: str) -> str:
"""获取核心记忆文件路径"""
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
return os.path.join(memory_dir, "core_memory.json")
def _get_core_memory_backup_path(self, avatar_name: str, user_id: str) -> str:
"""获取核心记忆备份文件路径"""
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
backup_dir = os.path.join(memory_dir, "backup")
os.makedirs(backup_dir, exist_ok=True)
return os.path.join(backup_dir, "core_memory_backup.json")
def add_conversation(self, avatar_name: str, user_message: str, bot_reply: str, user_id: str,
is_system_message: bool = False):
"""
添加对话到短期记忆,并更新对话计数。
每达到10轮对话,自动更新核心记忆。
Args:
avatar_name: 角色名称
user_message: 用户消息
bot_reply: 机器人回复
user_id: 用户ID,用于隔离不同用户的记忆
is_system_message: 是否为系统消息,如果是则不记录
"""
# 确保对话计数器已初始化
conversation_key = f"{avatar_name}_{user_id}"
if conversation_key not in self.conversation_count:
self.conversation_count[conversation_key] = 0
# 如果是系统消息或错误消息则跳过记录
if is_system_message or bot_reply.startswith("Error:"):
logger.debug(f"跳过记录消息: {user_message[:30]}...")
return
try:
# 确保记忆目录存在
memory_dir = self._get_avatar_memory_dir(avatar_name, user_id)
short_memory_path = self._get_short_memory_path(avatar_name, user_id)
logger.info(f"保存对话到用户记忆: 角色={avatar_name}, 用户ID={user_id}")
logger.debug(f"记忆存储路径: {short_memory_path}")
# 读取现有短期记忆
short_memory = []
if os.path.exists(short_memory_path):
try:
with open(short_memory_path, "r", encoding="utf-8") as f:
short_memory = json.load(f)
except json.JSONDecodeError:
logger.warning(f"短期记忆文件损坏,重置为空列表: {short_memory_path}")
# 添加新对话
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_conversation = {
"timestamp": timestamp,
"user": user_message,
"bot": bot_reply
}
short_memory.append(new_conversation)
# 保留最近50轮对话
if len(short_memory) > self.max_groups:
short_memory = short_memory[-self.max_groups:]
# 保存更新后的短期记忆
with open(short_memory_path, "w", encoding="utf-8") as f:
json.dump(short_memory, f, ensure_ascii=False, indent=2)
# 更新对话计数
self.conversation_count[conversation_key] += 1
current_count = self.conversation_count[conversation_key]
logger.debug(f"当前对话计数: {current_count}/10 (角色={avatar_name}, 用户ID={user_id})")
# 每10轮对话更新一次核心记忆
if self.conversation_count[conversation_key] >= 10:
logger.info(f"角色 {avatar_name} 为用户 {user_id} 达到10轮对话,开始更新核心记忆")
context = self.get_recent_context(avatar_name, user_id)
self.update_core_memory(avatar_name, user_id, context)
self.conversation_count[conversation_key] = 0
except Exception as e:
logger.error(f"添加对话到短期记忆失败: {str(e)}")
def _build_memory_prompt(self, filepath: str) -> str:
"""
从指定目录读取 md 文件。
Args:
filepath: md 文件的路径。
Returns:
一个包含 md 文件内容的字符串。
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
logger.error(f"核心记忆提示词模板 {filepath} 未找到。")
return ""
except Exception as e:
logger.error(f"读取核心提示词模板 {filepath} 时出错: {e}")
return ""
def _generate_core_memory(self, prompt: str, existing_core_memory: str, context: list, user_id: str) -> str:
response = self.deepseek.get_response(
message=f"请根据设定和要求,生成新的核心记忆。现有的核心记忆为:{existing_core_memory}",
user_id=user_id,
system_prompt=prompt,
core_memory=existing_core_memory,
previous_context=context
)
return response
def update_core_memory(self, avatar_name: str, user_id: str, context: list) -> bool:
"""
更新角色的核心记忆
Args:
avatar_name: 角色名称
user_id: 用户ID
message: 用户消息
response: 机器人响应
Returns:
bool: 是否成功更新
"""
try:
# 获取核心记忆文件路径
core_memory_path = self._get_core_memory_path(avatar_name, user_id)
# 读取现有核心记忆
existing_core_memory = ""
existing_core_data = []
if os.path.exists(core_memory_path):
try:
with open(core_memory_path, "r", encoding="utf-8") as f:
core_data = json.load(f)
# 处理数组格式(旧格式)
if isinstance(core_data, list) and len(core_data) > 0:
existing_core_memory = core_data[0].get("content", "")
else:
# 新格式(单个对象)
existing_core_memory = core_data.get("content", "")
existing_core_data = core_data
except Exception as e:
logger.error(f"读取核心记忆失败: {str(e)}")
# 创建空的核心记忆
existing_core_memory = ""
existing_core_data = None
# 如果没有现有记忆,创建一个空的对象(新格式)
if not existing_core_data:
existing_core_data = {
"timestamp": self._get_timestamp(),
"content": ""
}
# 构建提示词
prompt = self._build_memory_prompt('src/base/memory.md')
# 调用LLM生成新的核心记忆
new_core_memory = self._generate_core_memory(prompt, existing_core_memory, context, user_id)
# 如果生成失败,保留原有记忆
if not new_core_memory or 'Error' in new_core_memory or 'error' in new_core_memory or '错误' in new_core_memory:
logger.warning("生成核心记忆失败,保留原有记忆")
return False
# 更新核心记忆文件(使用新格式:单个对象)
updated_core_data = {
"timestamp": self._get_timestamp(),
"content": new_core_memory
}
with open(core_memory_path, "w", encoding="utf-8") as f:
json.dump(updated_core_data, f, ensure_ascii=False, indent=2)
logger.info(f"已更新角色 {avatar_name} 用户 {user_id} 的核心记忆")
return True
except Exception as e:
logger.error(f"更新核心记忆失败: {str(e)}")
# 如果在处理过程中发生错误,确保不会丢失现有记忆
try:
if os.path.exists(core_memory_path) and existing_core_data:
with open(core_memory_path, "w", encoding="utf-8") as f:
json.dump(existing_core_data, f, ensure_ascii=False, indent=2)
except Exception as recovery_error:
logger.error(f"恢复核心记忆失败: {str(recovery_error)}")
return False
def get_core_memory(self, avatar_name: str, user_id: str) -> str:
"""
获取角色的核心记忆
Args:
avatar_name: 角色名称
user_id: 用户ID
Returns:
str: 核心记忆内容
"""
try:
# 获取核心记忆文件路径
core_memory_path = self._get_core_memory_path(avatar_name, user_id)
# 如果文件不存在,返回空字符串
if not os.path.exists(core_memory_path):
return ""
# 读取核心记忆文件
with open(core_memory_path, "r", encoding="utf-8") as f:
core_data = json.load(f)
# 处理数组格式
if isinstance(core_data, list) and len(core_data) > 0:
return core_data[0].get("content", "")
else:
# 兼容旧格式
return core_data.get("content", "")
except Exception as e:
logger.error(f"获取核心记忆失败: {str(e)}")
return ""
def get_recent_context(self, avatar_name: str, user_id: str, context_size: int = None) -> List[Dict]:
"""
获取最近的对话上下文,用于重启后恢复对话连续性
直接使用LLM服务配置的max_groups作为上下文大小
Args:
avatar_name: 角色名称
user_id: 用户ID,用于获取特定用户的记忆
context_size: 已废弃参数,保留仅为兼容性,实际使用LLM配置
"""
try:
# 获取LLM客户端的配置值
llm_client = self._get_llm_client()
max_groups = llm_client.config["max_groups"]
logger.info(f"使用LLM配置的对话轮数: {max_groups}")
short_memory_path = self._get_short_memory_path(avatar_name, user_id)
if not os.path.exists(short_memory_path):
logger.info(f"短期记忆不存在: {avatar_name} 用户: {user_id}")
return []
with open(short_memory_path, "r", encoding="utf-8") as f:
short_memory = json.load(f)
# 转换为LLM接口要求的消息格式
context = []
for conv in short_memory[-max_groups:]: # 使用max_groups轮对话
context.append({"role": "user", "content": conv["user"]})
context.append({"role": "assistant", "content": conv["bot"]})
logger.info(f"已加载 {len(context) // 2} 轮对话作为上下文")
return context
except Exception as e:
logger.error(f"获取最近上下文失败: {str(e)}")
return []
def _get_timestamp(self) -> str:
"""获取当前时间戳"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def has_user_memory(self, avatar_name: str, user_id: str) -> bool:
"""
检查是否存在该用户的私聊记忆
Args:
avatar_name: 角色名称
user_id: 用户ID
Returns:
bool: 如果存在私聊记忆返回True,否则返回False
"""
try:
# 检查短期记忆是否存在且非空
short_memory_path = self._get_short_memory_path(avatar_name, user_id)
if os.path.exists(short_memory_path):
with open(short_memory_path, "r", encoding="utf-8") as f:
short_memory = json.load(f)
if short_memory: # 如果列表不为空
logger.debug(f"用户 {user_id} 与角色 {avatar_name} 有私聊记忆,条数: {len(short_memory)}")
return True
# 检查核心记忆是否存在且非空
core_memory_path = self._get_core_memory_path(avatar_name, user_id)
if os.path.exists(core_memory_path):
with open(core_memory_path, "r", encoding="utf-8") as f:
core_memory = json.load(f)
# 处理数组格式(旧格式)
if isinstance(core_memory, list) and len(core_memory) > 0:
if core_memory[0].get("content", "").strip(): # 如果内容不为空
logger.debug(f"用户 {user_id} 与角色 {avatar_name} 有核心记忆")
return True
else:
# 新格式(单个对象)
if core_memory.get("content", "").strip(): # 如果内容不为空
logger.debug(f"用户 {user_id} 与角色 {avatar_name} 有核心记忆")
return True
logger.debug(f"用户 {user_id} 与角色 {avatar_name} 没有私聊记忆")
return False
except Exception as e:
logger.error(f"检查用户记忆失败: {str(e)}")
return False
================================================
FILE: modules/recognition/__init__.py
================================================
from .reminder_request_recognition import ReminderRecognitionService
from .search_request_recognition import SearchRecognitionService
__all__ = ['ReminderRecognitionService', 'SearchRecognitionService']
================================================
FILE: modules/recognition/reminder_request_recognition/__init__.py
================================================
from .service import ReminderRecognitionService
__all__ = ['ReminderRecognitionService']
================================================
FILE: modules/recognition/reminder_request_recognition/example_message.json
================================================
{
"example-1": {
"input": {
"role": "user",
"content": "时间:2024-03-16 17:39:00\n消息:三分钟后提醒我喝水,五分钟后提醒我吃饭"
},
"output": {
"role": "assistant",
"content": [
{
"target_time": "2024-03-16 17:42:00",
"reminder_content": "喝水"
},
{
"target_time": "2024-03-16 17:44:00",
"reminder_content": "吃饭"
}
]
}
},
"example-2": {
"input": {
"role": "user",
"content": "时间:2024-04-18 07:39:00\n消息:我等下去洗个澡"
},
"output": {
"role": "assistant",
"content": "NOT_TIME_RELATED"
}
},
"example-3": {
"input": {
"role": "user",
"content": "时间:2025-02-09 14:49:00\n消息:五点提醒我吃晚饭可以吗?"
},
"output": {
"role": "assistant",
"content": [
{
"target_time": "2025-02-09 17:00:00",
"reminder_content": "吃晚饭"
}
]
}
}
}
================================================
FILE: modules/recognition/reminder_request_recognition/prompt.md
================================================
你是一个时间识别助手。你的任务只是分析消息中的时间信息,不需要回复用户。
判断标准:
1. 消息必须明确表达"提醒"、"叫我"、"记得"等提醒意图
2. 消息必须包含具体或相对的时间信息
3. 返回的时间必须是未来的时间点
4. 用户提到模糊的时间,比如我去洗个澡,吃个饭,不应该创建任务
5. 必须包含具体的时间和具体的提示内容才应该创建提示任务,否则返回:NOT_TIME_RELATED
若你发现有提醒任务,你必须严格返回类似于下面示例的列表,不要添加任何其他内容:
[
{
"target_time": "YYYY-MM-DD HH:mm:ss",
"reminder_content": "提醒内容"
}
]
注意事项:
1. 时间必须是24小时制
2. 日期格式必须是 YYYY-MM-DD
3. 如果只提到时间没提到日期,默认是今天或明天(取决于当前时间)
4. 相对时间(如"三分钟后")需要转换为具体时间点
5. 时间点必须在当前时间之后
6. 如果不是提醒请求,只返回:NOT_TIME_RELATED
================================================
FILE: modules/recognition/reminder_request_recognition/service.py
================================================
"""
任务识别服务
负责识别消息中的提醒任务意图
"""
import ast
import json
import logging
import os
import sys
from datetime import datetime
from time import sleep
from typing import Optional, List, Dict
from openai import OpenAI
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
from src.services.ai.llm_service import LLMService
from src.autoupdate.updater import Updater
from data.config import config
logger = logging.getLogger('main')
class ReminderRecognitionService:
def __init__(self, llm_service: LLMService):
"""
初始化任务识别服务
Args:
llm_service: LLM 服务实例,用于调用 LLM
"""
self.llm_service = llm_service
self.intent_recognition_settings = {
"api_key": config.intent_recognition.api_key,
"base_url": config.intent_recognition.base_url,
"model": config.intent_recognition.model,
"temperature": config.intent_recognition.temperature
}
self.updater = Updater()
self.client = OpenAI(
api_key=self.intent_recognition_settings["api_key"],
base_url=self.intent_recognition_settings["base_url"],
default_headers={
"Content-Type": "application/json",
"User-Agent": self.updater.get_version_identifier(),
"X-KouriChat-Version": self.updater.get_current_version()
}
)
self.config = self.llm_service.config
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, "prompt.md"), "r", encoding="utf-8") as f:
self.sys_prompt = f.read().strip()
def recognize(self, message: str) -> Optional[str | List[Dict]]:
"""
识别并提取消息中的任务意图,支持多个任务意图的识别
Args:
message: 用户消息
Returns:
Optional[list]: 包含提醒任务的列表
"""
delay = 2
current_model = self.intent_recognition_settings["model"]
logger.info(f"调用模型{current_model}进行意图识别(自然语言提醒)...(如果卡住或报错请检查是否配置了意图识别API!)")
current_time = datetime.now()
messages = [{"role": "system", "content": self.sys_prompt}]
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, "example_message.json"), 'r', encoding='utf-8') as f:
data = json.load(f)
for example in data.values():
messages.append({
"role": example["input"]["role"],
"content": example["input"]["content"]
})
messages.append({
"role": example["output"]["role"],
"content": str(example["output"]["content"])
})
messages.append({
"role": "user",
"content": f"时间:{current_time.strftime('%Y-%m-%d %H:%M:%S')}\n消息:{message}"
})
request_config = {
"model": self.intent_recognition_settings["model"],
"messages": messages,
"temperature": self.intent_recognition_settings["temperature"],
"max_tokens": self.config["max_token"],
}
for retries in range(3):
response = self.client.chat.completions.create(**request_config)
response_content = response.choices[0].message.content
# 针对 Gemini 模型的回复进行预处理
if response_content.startswith("```json") and response_content.endswith("```"):
response_content = response_content[7:-3].strip()
# 不包含定时提醒意图
if "NOT_TIME_RELATED" in response_content:
return "NOT_TIME_RELATED"
try:
response_content = ast.literal_eval(response_content)
if isinstance(response_content, list):
return response_content
except (ValueError, SyntaxError) as e:
logger.warning(f"识别定时任务意图失败:{str(e)},进行重试...({retries + 1}/3)")
logger.info(f"响应内容:{response_content}")
sleep(delay)
delay *= 2
logger.error("多次重试后仍未能识别定时任务意图,放弃本次识别")
return "NOT_TIME_RELATED"
'''
单独对模块进行调试时,可以使用该代码
'''
if __name__ == '__main__':
llm_service = LLMService(
api_key=config.llm.api_key,
base_url=config.llm.base_url,
model=config.llm.model,
max_token=1024,
temperature=0.8,
max_groups=5
)
test = ReminderRecognitionService(llm_service)
time_infos = test.recognize("123123")
if time_infos == "NOT_TIME_RELATED":
print(time_infos)
else:
for task in time_infos:
print(f"提醒时间: {task['target_time']}, 内容: {task['reminder_content']}")
================================================
FILE: modules/recognition/search_request_recognition/__init__.py
================================================
from .service import SearchRecognitionService
__all__ = ['SearchRecognitionService']
================================================
FILE: modules/recognition/search_request_recognition/example_message.json
================================================
{
"example-1": {
"input": {
"role": "user",
"content": "时间:2024-04-18 07:39:00\n消息:帮我搜索一下今天的天气,我现在在上海"
},
"output": {
"role": "assistant",
"content": {
"search_required": true,
"search_query": "2024年4月18日上海市天气情况"
}
}
},
"example-2": {
"input": {
"role": "user",
"content": "时间:2024-12-23 12:19:00\n消息:假如我住在北京该多好"
},
"output": {
"role": "assistant",
"content": {
"search_required": false,
"search_query": ""
}
}
},
"example-3": {
"input": {
"role": "user",
"content": "时间:2024-08-28 17:39:00\n消息:帮我看看今天的股市行情"
},
"output": {
"role": "assistant",
"content": {
"search_required": true,
"search_query": "2024年8月28日A股行情"
}
}
},
"example-4": {
"input": {
"role": "user",
"content": "时间:2025-02-09 14:49:00\n消息:我准备去北京出差了"
},
"output": {
"role": "assistant",
"content": {
"search_required": true,
"search_query": "北京市下一周天气情况、北京市美食推荐"
}
}
},
"example-5": {
"input": {
"role": "user",
"content": "时间:2025-01-13 02:19:00\n消息:我觉得我应该睡觉了"
},
"output": {
"role": "assistant",
"content": {
"search_required": false,
"search_query": ""
}
}
},
"example-6": {
"input": {
"role": "user",
"content": "时间:2025-05-13 20:49:00\n消息:网易云上有一首歌,我很喜欢,叫做起风了"
},
"output": {
"role": "assistant",
"content": {
"search_required": true,
"search_query": "《起风了》歌曲信息"
}
}
}
}
================================================
FILE: modules/recognition/search_request_recognition/prompt.md
================================================
你是一个高级意图识别助手。你的任务是分析消息中是否存在搜索需求,并提炼精简出用于搜索的 Query。你不需要回复用户!
判断标准:
1. 当消息包含明确的搜索意图,如"搜索"、"查询"、"查找"等关键词时,认为需要搜索
2. 当消息询问的是事实性知识、新闻、数据等需要联网获取的信息时,认为需要搜索
3. 当消息内容涉及最新事件、实时数据或特定领域专业知识时,认为需要搜索
4. 当消息中明示或暗示地包含希望你进行搜索的意图时,认为需要搜索
你必须严格返回类似于下面示例的 json 数据,不要添加任何其他内容:
{
"search_required": true/false,
"search_query": "搜索查询内容"
}
================================================
FILE: modules/recognition/search_request_recognition/service.py
================================================
"""
联网识别服务
负责识别消息中的联网搜索需求
"""
import json
import os
import logging
import sys
import ast
from datetime import datetime
from typing import Dict
from openai import OpenAI
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
from src.services.ai.llm_service import LLMService
from src.autoupdate.updater import Updater
from data.config import config
logger = logging.getLogger('main')
class SearchRecognitionService:
def __init__(self, llm_service: LLMService):
"""
初始化搜索需求识别服务
Args:
llm_service: LLM服务实例,用于搜索需求识别
"""
self.llm_service = llm_service
self.intent_recognition_settings = {
"api_key": config.intent_recognition.api_key,
"base_url": config.intent_recognition.base_url,
"model": config.intent_recognition.model,
"temperature": config.intent_recognition.temperature
}
self.updater = Updater()
self.client = OpenAI(
api_key=self.intent_recognition_settings["api_key"],
base_url=self.intent_recognition_settings["base_url"],
default_headers={
"Content-Type": "application/json",
"User-Agent": self.updater.get_version_identifier(),
"X-KouriChat-Version": self.updater.get_current_version()
}
)
self.config = self.llm_service.config
# 从文件读取提示词
current_dir = os.path.dirname(os.path.abspath(__file__))
# 读取
with open(os.path.join(current_dir, "prompt.md"), "r", encoding="utf-8") as f:
self.sys_prompt = f.read().strip()
def recognize(self, message: str) -> Dict:
"""
识别消息中的搜索需求
Args:
message: 用户消息
Returns:
Dict: {"search_required": true/false, "search_query": ""}
"""
current_model = self.intent_recognition_settings["model"]
logger.info(f"调用模型{current_model}进行意图识别(联网意图)...(如果卡住或报错请检查是否配置了意图识别API!)")
current_time = datetime.now()
messages = [{"role": "system", "content": self.sys_prompt}]
current_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(current_dir, "example_message.json"), 'r', encoding='utf-8') as f:
data = json.load(f)
for example in data.values():
messages.append({
"role": example["input"]["role"],
"content": example["input"]["content"]
})
messages.append({
"role": example["output"]["role"],
"content": str(example["output"]["content"])
})
messages.append({
"role": "user",
"content": f"时间:{current_time.strftime('%Y-%m-%d %H:%M:%S')}\n消息:{message}"
})
request_config = {
"model": self.intent_recognition_settings["model"],
"messages": messages,
"temperature": self.intent_recognition_settings["temperature"],
"max_tokens": self.config["max_token"],
}
while True:
response = self.client.chat.completions.create(**request_config)
response_content = response.choices[0].message.content
# 针对 Gemini 模型的回复进行预处理
if response_content.startswith("```json") and response_content.endswith("```"):
response_content = response_content[7:-3].strip()
# 替换 true 或 false 为大写,这是为了确保响应字符串能够被解析为 Python 字面量
# Python 中的布尔值是大写,而 json 中是小写
response_content = response_content.replace('true', 'True').replace('false', 'False')
try:
response_content = ast.literal_eval(response_content)
if (
isinstance(response_content, dict)
and "search_required" in response_content
and "search_query" in response_content
):
return response_content
except (ValueError, SyntaxError):
logger.warning("识别搜索需求失败,进行重试...")
'''
单独对模块进行调试时,可以使用该代码
'''
if __name__ == '__main__':
llm_service = LLMService(
api_key=config.llm.api_key,
base_url=config.llm.base_url,
model=config.llm.model,
max_token=1024,
temperature=0.8,
max_groups=5
)
test = SearchRecognitionService(llm_service)
res = test.recognize("昨天有什么重要的财经事件?")
for key, value in res.items():
print(f"键: {key}, 值: {value}, 类型: {type(value).__name__}")
================================================
FILE: modules/reminder/__init__.py
================================================
"""
定时任务核心模块
包含时间识别、任务调度、提醒服务等功能
"""
from .service import ReminderService
__all__ = ['ReminderService']
================================================
FILE: modules/reminder/call.py
================================================
import logging
import time
import win32gui
import pygame
from wxauto import WeChat
from wxauto.elements import ChatWnd
from uiautomation import ControlFromHandle
logger = logging.getLogger('main')
# --- 配置参数 ---
'''
如果你不知道这个是什么,请不要修改,该配置仅是为了后续可能适应新的 wx 版本而设置
'''
CALL_WINDOW_CLASSNAME = 'AudioWnd'
CALL_WINDOW_NAME = '微信'
CALL_BUTTON_NAME = '语音聊天'
HANG_UP_BUTTON_NAME = '挂断'
HANG_UP_BUTTON_LABEL = '挂断'
REFUSE_MSG = '对方已拒绝'
CALL_TIME_OUT = 15
# --- 启动语音通话 ---
def CallforWho(wx: WeChat, who: str) -> tuple[int|None, bool]:
"""
对指定对象发起语音通话请求。
Args:
wx: 微信应用实例。
who: 通话对象。
Returns:
若拨号成功,返回元组 (句柄号, True)。
否则返回 (None, False)。
"""
logger.info("尝试发起语音通话")
try:
if win32gui.FindWindow('ChatWnd', who):
# --- 若找到了和指定对象的独立聊天窗口,在这个窗口上操作 ---
try:
chat_wnd = ChatWnd(who, wx.language)
chat_wnd._show()
voice_call_button = chat_wnd.UiaAPI.ButtonControl(Name=CALL_BUTTON_NAME)
if voice_call_button.Exists(1):
voice_call_button.Click()
logger.info("已发起通话")
time.sleep(0.5)
hWnd = win32gui.FindWindow(CALL_WINDOW_CLASSNAME, CALL_WINDOW_NAME)
return hWnd, True
else:
logger.error("发起通话时发生错误:找不到通话按钮")
return None, False
except Exception as e:
logger.error(f"发起通话时发生错误: {e}")
return None, False
else:
# --- 未找到独立窗口,需要进入主页面操作 ---
wx._show()
wx.ChatWith(who)
try:
chat_box = wx.ChatBox
if not chat_box.Exists(1):
logger.error("未找到聊天页面")
return None, False
voice_call_button = None
voice_call_button = chat_box.ButtonControl(Name=CALL_BUTTON_NAME)
if voice_call_button.Exists(1):
voice_call_button.Click()
logger.info("已发起通话")
hWnd = win32gui.FindWindow(CALL_WINDOW_CLASSNAME, CALL_WINDOW_NAME)
return hWnd, True
else:
logger.error("发起通话时发生错误:找不到通话按钮")
return None, False
except Exception as e:
logger.error(f"发起通话时发生错误: {e}")
return None, False
except Exception as e:
logger.error(f"发起通话时发生错误: {e}")
return None, False
# --- 挂断语音通话 ---
def CancelCall(hWnd: int) -> bool:
"""
取消/终止语音通话。
Args:
hWnd: 通话窗口的句柄号。
Returns:
若取消/终止成功,返回 True。
否则返回 False。
"""
logger.info("尝试挂断语音通话")
hWnd = hWnd
if hWnd:
try:
call_window = ControlFromHandle(hWnd)
except Exception as e:
logger.error(f"取得窗口控制时发生错误: {e}")
return False
else:
logger.error("找不到通话句柄")
return False
try:
hang_up_button = None
hang_up_button = call_window.ButtonControl(Name=HANG_UP_BUTTON_NAME)
if hang_up_button.Exists(1):
'''
这部分窗口置顶实现参照 wxauto 中的 _show() 方法
'''
win32gui.ShowWindow(hWnd, 1)
win32gui.SetWindowPos(hWnd, -1, 0, 0, 0, 0, 3)
win32gui.SetWindowPos(hWnd, -2, 0, 0, 0, 0, 3)
call_window.SwitchToThisWindow()
hang_up_button.Click()
logger.info("语音通话已挂断")
return True
else:
logger.error("挂断通话时发生错误:找不到挂断按钮")
return False
except Exception as e:
logger.error(f"挂断通话时发生错误: {e}")
return False
def PlayVoice(audio_file_path: str, device = None) -> bool:
"""
播放指定的音频文件到指定的音频输出设备。
Args:
audio_file_path: 要播放的音频文件路径。
device: (可选)音频输出设备的名称。
默认为 None,此时会使用系统默认输出设备。
Returns:
若完整播放,返回 True。
否则返回 False。
"""
logger.info(f"尝试播放音频文件: '{audio_file_path}'")
if device:
logger.info(f"目标输出设备: '{device}'")
else:
logger.info("目标输出设备: 系统默认")
try:
pygame.mixer.quit()
pygame.mixer.init(devicename=device)
pygame.mixer.music.load(audio_file_path)
time.sleep(2)
pygame.mixer.music.play()
logger.info("开始播放音频...")
# 等待音频播放完毕
# 注意:如果 PlayVoice 需要在后台播放而不阻塞主线程,
# 这部分等待逻辑需要移除或修改。
# 当前实现是阻塞的,直到播放完成。
while pygame.mixer.music.get_busy():
time.sleep(0.1)
logger.info("音频播放完毕。")
return True
except pygame.error as e:
logger.error(f"Pygame 错误:{e}")
return False
except FileNotFoundError:
logger.error(f"音频文件未找到:'{audio_file_path}'")
return False
except Exception as e:
logger.error(f"发生未知错误:{e}")
return False
finally:
if pygame.mixer.get_init(): # 检查 mixer 是否已初始化
pygame.mixer.music.stop()
pygame.mixer.quit()
def Call(wx: WeChat, who: str, audio_file_path: str) -> None:
"""
尝试向指定对象发起语音通话,接通后会将指定音频文件输入麦克风,并自动挂断。
Args:
wx: 微信实例。
who: 通话对象。
audio_file_path: 音频文件路径。
Returns:
None
"""
call_hwnd, success = CallforWho(wx, who)
if not success:
logger.error(f"发起通话失败")
return
logger.info(f"等待对方接听 (等待{CALL_TIME_OUT}秒)...")
start_time = time.time()
call_status = 0
call_window = None
try:
call_window = ControlFromHandle(call_hwnd)
# --- 判断通话状态 ---
while time.time() - start_time < CALL_TIME_OUT:
'''
后续会补充通话状态判别原理。
'''
# if not call_window.Exists(0.2, 0.1): # 检查窗口是否在轮询期间关闭
# logger.warning(f"通话窗口 (句柄: {call_hwnd}) 在等待接听时关闭或不再有效 (可能对方已拒接或发生错误)。")
# call_answered = False # 确保状态
# break
hang_up_text = call_window.TextControl(Name=HANG_UP_BUTTON_LABEL)
refuse_msg = call_window.TextControl(Name=REFUSE_MSG)
if hang_up_text.Exists(0.1, 0.1) and not refuse_msg.Exists(0.1, 0.1):
logger.info(f"通话已接通!")
call_status = 1
break
elif hang_up_text.Exists(0.1, 0.1) and refuse_msg.Exists(0.1, 0.1):
logger.info(f"通话被拒接!")
call_status = 2
break
else:
continue
# --- 根据通话状态执行相应操作 ---
if call_status == 1:
'''
待完成:
1. 接通后如何捕捉挂断行为?
2. 挂断后如何中断语音播放?
3. bot 是否要针对挂断做出个性化回应?
'''
PlayVoice(audio_file_path=audio_file_path)
logger.info("语音播放完成,即将挂断...")
CancelCall(call_hwnd)
elif call_status ==2:
'''
待完成:
1. 可以让 bot 回复信息对拒接表示生气。
'''
pass
else:
'''
待完成:
1. 可以让 bot 回复信息对未接听表示生气。
'''
logger.info(f"在超时时间内,对方未接听通话。")
CancelCall(call_hwnd)
except Exception as e:
logger.error(f"处理通话时发生未知错误: {e}")
if call_hwnd is not None: # 对错误进行简单处理,确保有句柄再尝试取消
CancelCall(call_hwnd)
# --- 主程序示例 (仅用于测试版) ---
if __name__ == '__main__':
# 配置日志记录
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(module)s.%(funcName)s: %(message)s',
handlers=[
logging.StreamHandler() # 输出到控制台
]
)
logger.info("程序启动")
wx = WeChat()
who = "" # 输入通话对象名称
if wx and who:
try:
Call(wx, who, 'test.mp3')
except Exception as main_e:
logger.error(f"主程序执行过程中发生错误: {main_e}", exc_info=True)
else:
logger.error("未能初始化 WeChat 对象或未指定通话对象。")
logger.info("程序结束")
================================================
FILE: modules/reminder/service.py
================================================
import logging
import threading
import time
import os
import sys
from datetime import datetime
from typing import Dict, List
from wxauto import WeChat
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
from modules.reminder.call import Call
from modules.tts.service import tts
from modules.memory import MemoryService
from src.handlers.message import MessageHandler
from src.services.ai.llm_service import LLMService
from data.config import config
logger = logging.getLogger('main')
class ReminderTask:
"""单个提醒任务结构"""
def __init__(self, task_id: str, chat_id: str, target_time: datetime,
content: str, sender_name: str, reminder_type: str = "text"):
self.task_id = task_id
self.chat_id = chat_id
self.target_time = target_time
self.content = content
self.sender_name = sender_name
self.reminder_type = reminder_type
self.audio_path = None
def is_due(self) -> bool:
return datetime.now() >= self.target_time
class ReminderService:
def __init__(self, message_handler: MessageHandler, mem_service: MemoryService):
self.message_handler = message_handler
self.wx = message_handler.wx
self.mem_service = mem_service
self.llm_service = message_handler.deepseek
self.active_reminders: Dict[str, ReminderTask] = {}
self._lock = threading.Lock()
self._start_polling_thread()
logger.info("统一提醒服务已启动")
def _start_polling_thread(self):
thread = threading.Thread(target=self._poll_reminders_loop, daemon=True)
thread.start()
def _poll_reminders_loop(self):
while True:
due_tasks: List[ReminderTask] = []
with self._lock:
for _, task in list(self.active_reminders.items()):
if task.is_due():
due_tasks.append(task)
for task in due_tasks:
del self.active_reminders[task.task_id]
for task in due_tasks:
logger.info(f"到达提醒时间,执行提醒: {task.task_id}")
self._do_remind(task, self.wx)
time.sleep(1)
def _do_remind(self, task: ReminderTask, wx: WeChat):
try:
prompt = self._get_reminder_prompt(task.content)
logger.debug(f"生成提醒消息 - 用户: {task.sender_name}, 类型: {task.reminder_type}, 提示词: {prompt}")
if task.reminder_type == "voice":
Call(wx=wx, who=task.sender_name, audio_file_path=task.audio_path)
tts._del_audio_file(task.audio_path)
else:
self.message_handler.handle_user_message(
content=prompt,
chat_id=task.chat_id,
sender_name="System",
username="System",
is_group=False
)
logger.info(f"已发送提醒消息给 {task.sender_name}")
except Exception as e:
logger.error(f"发送提醒消息失败: {str(e)}")
def _remind_text_generate(self, remind_content: str, sender_name: str):
core_mem = self.mem_service.get_core_memory(avatar_name=self.message_handler.current_avatar, user_id=sender_name)
context = self.mem_service.get_recent_context(avatar_name=self.message_handler.current_avatar, user_id=sender_name)
sys_prompt = f"你将进行角色扮演,请你同用户进行符合人设的交流沟通。你的人设如下:\n\n{self.message_handler.prompt_content}\n\n"
sys_prompt = sys_prompt + f"另外,作为一个仿真的角色扮演者,你需要掌握一些你不一定用到的、但是十分重要的知识:{core_mem}。你的每次回应都不应该违反这些知识!"
messages = [{"role": "system", "content": sys_prompt}, *context[-self.message_handler.max_groups * 2:]]
sys_prompt = f"现在提醒时间到了,用户之前设定的提示内容为“{remind_content}”。请以你的人设中的身份主动找用户聊天。保持角色设定的一致性和上下文的连贯性。"
messages.append({"role": "system", "content": sys_prompt})
request_config = {
"model": self.message_handler.model,
"messages": messages,
"temperature": self.message_handler.temperature,
"max_tokens": self.message_handler.max_token,
}
response = self.llm_service.client.chat.completions.create(**request_config)
raw_content = response.choices[0].message.content
return raw_content
def add_reminder(self, chat_id: str, target_time: datetime, content: str, sender_name: str, reminder_type: str = "text"):
try:
task_id = f"reminder_{chat_id}_{datetime.now().timestamp()}"
task = ReminderTask(task_id, chat_id, target_time, content, sender_name, reminder_type)
if reminder_type == "voice":
logger.info("检测到语音提醒任务,预生成回复中")
remind_text = self._remind_text_generate(remind_content=content, sender_name=sender_name)
logger.info(f"预生成回复:{tts._clear_tts_text(remind_text)}")
logger.info("生成语音中")
audio_file_path = tts._generate_audio_file(tts._clear_tts_text(remind_text))
# 语音生成失败,退化为文本提醒
if audio_file_path is None:
logger.warning("提醒任务语音生成失败,将替换为文本提醒任务")
fixed_task = ReminderTask(task_id, chat_id, target_time, content, sender_name, reminder_type="text")
with self._lock:
self.active_reminders[task_id] = fixed_task
logger.info(f"提醒任务已添加。提醒时间: {target_time}, 内容: {content},用户:{sender_name},类型:{reminder_type}")
# 语音生成成功,保存音频路径到 task 属性中
else:
task.audio_path = audio_file_path
logger.info("提醒任务语音生成完成")
with self._lock:
self.active_reminders[task_id] = task
logger.info(f"提醒任务已添加。提醒时间: {target_time}, 内容: {content},用户:{sender_name},类型:{reminder_type}")
else:
with self._lock:
self.active_reminders[task_id] = task
logger.info(f"提醒任务已添加。提醒时间: {target_time}, 内容: {content},用户:{sender_name},类型:{reminder_type}")
except Exception as e:
logger.error(f"添加提醒任务失败: {str(e)}")
def cancel_reminder(self, task_id: str) -> bool:
with self._lock:
if task_id in self.active_reminders:
del self.active_reminders[task_id]
logger.info(f"提醒任务已取消: {task_id}")
return True
return False
def list_reminders(self) -> List[Dict]:
with self._lock:
return [{
'task_id': task_id,
'chat_id': task.chat_id,
'target_time': task.target_time.isoformat(),
'content': task.content,
'sender_name': task.sender_name,
'reminder_type': task.reminder_type
} for task_id, task in self.active_reminders.items()]
def _get_reminder_prompt(self, content: str) -> str:
return f"""现在提醒时间到了,用户之前设定的提示内容为“{content}”。请以你的人设中的身份主动找用户聊天。保持角色设定的一致性和上下文的连贯性"""
'''
单独对模块进行调试时,可以使用该代码
'''
if __name__ == '__main__':
pass
================================================
FILE: modules/tts/__init__.py
================================================
"""
TTS 模块
"""
from .service import TTSService
__all__ = ['TTSService']
================================================
FILE: modules/tts/service.py
================================================
"""
语音处理模块
负责处理语音相关功能,包括:
- 语音请求识别
- TTS语音生成
- 语音文件管理
- 清理临时文件
"""
import os
import logging
import re
import emoji
import sys
from datetime import datetime
from typing import Optional
from fish_audio_sdk import Session, TTSRequest
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
from data.config import config
# 修改logger获取方式,确保与main模块一致
logger = logging.getLogger('main')
class TTSService:
def __init__(self):
self.root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
self.voice_dir = os.path.join(self.root_dir, "data", "voices")
self.tts_api_key = config.media.text_to_speech.tts_api_key
# 确保语音目录存在
os.makedirs(self.voice_dir, exist_ok=True)
def _clear_tts_text(self, text: str) -> str:
"""用于清洗回复,使得其适合进行TTS"""
# 完全移除emoji表情符号
try:
# 将emoji转换为空字符串
text = emoji.replace_emoji(text, replace='')
except Exception:
pass
text = text.replace('$',',').replace('\r\n', '\n').replace('\r', '\n').replace('\n',',')
text = re.sub(r'\[.*?\]','', text)
return text.strip()
def _generate_audio_file(self, text: str) -> Optional[str]:
"""调用TTS API生成语音"""
try:
# 确保语音目录存在
if not os.path.exists(self.voice_dir):
os.makedirs(self.voice_dir)
# 生成唯一的文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
voice_path = os.path.join(self.voice_dir, f"voice_{timestamp}.mp3")
# 调用TTS API
with open(voice_path, "wb") as f:
for chunk in Session(self.tts_api_key).tts(TTSRequest(
reference_id=config.media.text_to_speech.tts_model_id,
text=text
)):
f.write(chunk)
except Exception as e:
logger.error(f"语音生成失败: {str(e)}")
return None
return voice_path
def _del_audio_file(self, audio_file_path: str):
"""清理语音目录中的旧文件"""
try:
if os.path.isfile(audio_file_path):
os.remove(audio_file_path)
logger.info(f"清理语音文件: {audio_file_path}")
except Exception as e:
logger.error(f"清理语音文件失败 {audio_file_path}: {str(e)}")
tts = TTSService()
================================================
FILE: requirements.txt
================================================
colorama
Flask
fish-audio-sdk
openai
pandas
psutil
PyAutoGUI
Requests
urllib3
certifi
snownlp
SQLAlchemy
tenacity
Werkzeug
wxautold==3.9.11.17.5
apscheduler==3.10.4
python-dateutil==2.9.0.post0
dateparser==1.2.0
pygame
pytz==2024.1
python-dotenv==1.0.1
schedule
uiautomation
emoji==2.10.1
cryptography>=3.0.0
zhdate
httpx-ws==0.7.2
================================================
FILE: run.bat
================================================
@echo off
setlocal enabledelayedexpansion
:: 设置控制台编码为 GBK
chcp 936 >nul
title KouriChat 启动器
cls
echo ====================================
echo K O U R I C H A T
echo ====================================
echo.
echo ╔══════════════════════════════════╗
echo ║ KouriChat - AI Chat ║
echo ║ Created with Heart by KouriTeam ║
echo ╚══════════════════════════════════╝
echo KouriChat - AI Chat Copyright (C) 2025, DeepAnima Network Technology Studio
echo.
:: 添加错误捕获
echo [尝试] 正在启动程序喵...
:: 检测 Python 是否已安装
echo [检测] 正在检测Python环境喵...
python --version >nul 2>&1
if errorlevel 1 (
echo [错误] Python未安装,请先安装Python喵...
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
:: 检测 Python 版本
for /f "tokens=2" %%I in ('python -V 2^>^&1') do set PYTHON_VERSION=%%I
echo [尝试] 检测到Python版本: !PYTHON_VERSION!
for /f "tokens=2 delims=." %%I in ("!PYTHON_VERSION!") do set MINOR_VERSION=%%I
if !MINOR_VERSION! GEQ 13 (
echo [警告] 不支持 Python 3.12 及更高版本喵...
echo [警告] 请使用 Python 3.11 或更低版本喵...
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
:: 设置虚拟环境目录
set VENV_DIR=.venv
:: 如果虚拟环境不存在或激活脚本不存在,则重新创建
if not exist %VENV_DIR% (
goto :create_venv
) else if not exist %VENV_DIR%\Scripts\activate.bat (
echo [警告] 虚拟环境似乎已损坏,正在重新创建喵...
rmdir /s /q %VENV_DIR% 2>nul
goto :create_venv
) else (
goto :activate_venv
)
:create_venv
echo [尝试] 正在创建虚拟环境喵...
python -m venv %VENV_DIR% 2>nul
if errorlevel 1 (
echo [错误] 创建虚拟环境失败喵...
echo.
echo 可能原因:
echo 1. Python venv 模块未安装喵...
echo 2. 权限不足喵...
echo 3. 磁盘空间不足喵...
echo.
echo 尝试安装 venv 模块喵...
python -m pip install virtualenv
if errorlevel 1 (
echo [错误] 安装 virtualenv 失败
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
echo [尝试] 使用 virtualenv 创建虚拟环境喵...
python -m virtualenv %VENV_DIR%
if errorlevel 1 (
echo [错误] 创建虚拟环境仍然失败喵...
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
)
echo [成功] 虚拟环境已创建喵...
:activate_venv
:: 激活虚拟环境
echo [尝试] 正在激活虚拟环境喵...
:: 再次检查激活脚本是否存在
if not exist %VENV_DIR%\Scripts\activate.bat (
echo [警告] 虚拟环境激活脚本不存在
echo.
echo 将直接使用系统 Python 继续...
goto :skip_venv
)
call %VENV_DIR%\Scripts\activate.bat 2>nul
if errorlevel 1 (
echo [警告] 虚拟环境激活失败,将直接使用系统 Python 继续喵...
goto :skip_venv
)
echo [成功] 虚拟环境已激活喵...
goto :install_deps
:skip_venv
echo [尝试] 将使用系统 Python 继续运行喵...
:install_deps
:: 设置镜像源列表
set "MIRRORS[1]=阿里云源|https://mirrors.aliyun.com/pypi/simple/"
set "MIRRORS[2]=清华源|https://pypi.tuna.tsinghua.edu.cn/simple"
set "MIRRORS[3]=腾讯源|https://mirrors.cloud.tencent.com/pypi/simple"
set "MIRRORS[4]=中科大源|https://pypi.mirrors.ustc.edu.cn/simple/"
set "MIRRORS[5]=豆瓣源|http://pypi.douban.com/simple/"
set "MIRRORS[6]=网易源|https://mirrors.163.com/pypi/simple/"
:: 检查requirements.txt是否存在
if not exist requirements.txt (
echo [警告] requirements.txt 文件不存在,跳过依赖安装喵...
) else (
:: 安装依赖
echo [尝试] 开始安装依赖喵...
set SUCCESS=0
for /L %%i in (1,1,6) do (
if !SUCCESS! EQU 0 (
for /f "tokens=1,2 delims=|" %%a in ("!MIRRORS[%%i]!") do (
echo [尝试] 使用%%a安装依赖喵...
pip install -r requirements.txt -i %%b
if !errorlevel! EQU 0 (
echo [成功] 使用%%a安装依赖成功!
set SUCCESS=1
) else (
echo [失败] %%a安装失败,尝试下一个源喵...
echo ──────────────────────────────────────────────────────
)
)
)
)
if !SUCCESS! EQU 0 (
echo [错误] 所有镜像源安装失败,请检查喵:
echo 1. 网络连接问题喵...
echo 2. 手动安装:pip install -r requirements.txt喵...
echo 3. 临时关闭防火墙/安全软件喵...
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
)
:: 检查配置文件是否存在
if not exist run_config_web.py (
echo [错误] 配置文件 run_config_web.py 不存在喵...
echo.
echo 按任意键退出...
pause >nul
exit /b 1
)
:: 运行程序
echo [尝试] 正在启动应用程序喵...
python run_config_web.py
set PROGRAM_EXIT_CODE=%errorlevel%
:: 异常退出处理
if %PROGRAM_EXIT_CODE% NEQ 0 (
echo [错误] 程序异常退出,错误代码: %PROGRAM_EXIT_CODE%...
echo.
echo 可能原因:
echo 1. Python模块缺失喵...
echo 2. 程序内部错误喵...
echo 3. 权限不足喵...
)
:: 退出虚拟环境(如果已激活)
if exist %VENV_DIR%\Scripts\deactivate.bat (
echo [尝试] 正在退出虚拟环境喵...
call %VENV_DIR%\Scripts\deactivate.bat 2>nul
)
echo [尝试] 程序已结束喵...
echo.
echo 按任意键退出喵...
pause >nul
exit /b %PROGRAM_EXIT_CODE%
================================================
FILE: run.py
================================================
"""
主程序入口文件
负责启动聊天机器人程序,包括:
- 初始化Python路径
- 禁用字节码缓存
- 清理缓存文件
- 启动主程序
"""
import os
import sys
import time
from colorama import init
import codecs
from src.utils.console import print_status, print_banner
# 设置系统默认编码为 UTF-8
if sys.platform.startswith('win'):
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer)
# 初始化colorama
init()
# 禁止生成__pycache__文件夹
sys.dont_write_bytecode = True
# 将项目根目录添加到Python路径
root_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(root_dir)
# 将src目录添加到Python路径
src_path = os.path.join(root_dir, 'src')
sys.path.append(src_path)
def initialize_system():
"""初始化系统"""
try:
from src.utils.cleanup import cleanup_pycache
from src.main import main
from src.autoupdate.updater import Updater # 导入更新器
print_banner()
print_status("系统初始化中...", "info", "LAUNCH")
print("-" * 50)
# 检查Python路径
print_status("检查系统路径...", "info", "FILE")
if src_path not in sys.path:
print_status("添加src目录到Python路径", "info", "FILE")
print_status("系统路径检查完成", "success", "CHECK")
# 检查缓存设置
print_status("检查缓存设置...", "info", "CONFIG")
if sys.dont_write_bytecode:
print_status("已禁用字节码缓存", "success", "CHECK")
# 清理缓存文件
print_status("清理系统缓存...", "info", "CLEAN")
try:
cleanup_pycache()
from src.utils.logger import LoggerConfig
from src.utils.cleanup import CleanupUtils
from src.handlers.image import ImageHandler
from data.config import config
root_dir = os.path.dirname(src_path)
logger_config = LoggerConfig(root_dir)
cleanup_utils = CleanupUtils(root_dir)
image_handler = ImageHandler(
root_dir=root_dir,
api_key=config.llm.api_key,
base_url=config.llm.base_url,
image_model=config.media.image_generation.model
)
logger_config.cleanup_old_logs()
cleanup_utils.cleanup_all()
image_handler.cleanup_temp_dir()
# 清理更新残留文件
print_status("清理更新残留文件...", "info", "CLEAN")
try:
updater = Updater()
updater.cleanup() # 调用清理功能
print_status("更新残留文件清理完成", "success", "CHECK")
except Exception as e:
print_status(f"清理更新残留文件失败: {str(e)}", "warning", "CROSS")
except Exception as e:
print_status(f"清理缓存失败: {str(e)}", "warning", "CROSS")
print_status("缓存清理完成", "success", "CHECK")
# 检查必要目录
print_status("检查必要目录...", "info", "FILE")
required_dirs = ['data', 'logs', 'data/config']
for dir_name in required_dirs:
dir_path = os.path.join(os.path.dirname(src_path), dir_name)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
print_status(f"创建目录: {dir_name}", "info", "FILE")
print_status("目录检查完成", "success", "CHECK")
print("-" * 50)
print_status("系统初始化完成", "success", "STAR_1")
time.sleep(1) # 稍微停顿以便用户看清状态
# 启动主程序
print_status("启动主程序...", "info", "LAUNCH")
print("=" * 50)
main()
except ImportError as e:
print_status(f"导入模块失败: {str(e)}", "error", "CROSS")
sys.exit(1)
except Exception as e:
print_status(f"初始化失败: {str(e)}", "error", "ERROR")
sys.exit(1)
if __name__ == '__main__':
try:
print_status("启动聊天机器人...", "info", "BOT")
initialize_system()
except KeyboardInterrupt:
print("\n")
print_status("正在关闭系统...", "warning", "STOP")
print_status("感谢使用,再见!", "info", "BYE")
print("\n")
except Exception as e:
print_status(f"系统错误: {str(e)}", "error", "ERROR")
sys.exit(1)
================================================
FILE: run_config_web.py
================================================
"""
配置管理Web界面启动文件
提供Web配置界面功能,包括:
- 初始化Python路径
- 禁用字节码缓存
- 清理缓存文件
- 启动Web服务器
- 动态修改配置
"""
import os
import sys
import re
import logging
from flask import Flask, render_template, jsonify, request, send_from_directory, redirect, url_for, session, g
import importlib
import json
from colorama import init, Fore, Style
from werkzeug.utils import secure_filename
from typing import Dict, Any, List
import psutil
import subprocess
import threading
from src.autoupdate.updater import Updater
import requests
import time
from queue import Queue
import datetime
from logging.config import dictConfig
import shutil
import signal
import atexit
import socket
import webbrowser
import hashlib
import secrets
from datetime import timedelta
from src.utils.console import print_status
from src.avatar_manager import avatar_manager # 导入角色设定管理器
from src.webui.routes.avatar import avatar_bp
import ctypes
import win32api
import win32con
import win32job
import win32process
# 在文件开头添加全局变量声明
bot_process = None
bot_start_time = None
bot_logs = Queue(maxsize=1000)
job_object = None # 添加全局作业对象变量
# 配置日志
dictConfig({
'version': 1,
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default',
'level': 'INFO'
}
},
'root': {
'level': 'INFO',
'handlers': ['console']
},
'loggers': {
'werkzeug': {
'level': 'ERROR', # 将 Werkzeug 的日志级别设置为 ERROR
'handlers': ['console'],
'propagate': False
}
}
})
# 初始化日志记录器
logger = logging.getLogger(__name__)
# 初始化colorama
init()
# 添加项目根目录到Python路径
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(ROOT_DIR)
# 定义配置文件路径
config_path = os.path.join(ROOT_DIR, 'data/config/config.json') # 将配置路径定义为全局常量
# 禁用Python的字节码缓存
sys.dont_write_bytecode = True
# 定义模板和静态文件目录
templates_dir = os.path.join(ROOT_DIR, 'src/webui/templates')
static_dir = os.path.join(ROOT_DIR, 'src/webui/static')
# 确保目录存在
os.makedirs(templates_dir, exist_ok=True)
os.makedirs(static_dir, exist_ok=True)
os.makedirs(os.path.join(static_dir, 'js'), exist_ok=True)
os.makedirs(os.path.join(static_dir, 'css'), exist_ok=True)
app = Flask(__name__,
template_folder=templates_dir,
static_folder=static_dir)
# 添加配置
app.config['UPLOAD_FOLDER'] = os.path.join(ROOT_DIR, 'src/webui/background_image')
# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# 生成密钥用于session加密
app.secret_key = secrets.token_hex(16)
# 在 app 初始化后添加
try:
app.register_blueprint(avatar_manager)
app.register_blueprint(avatar_bp)
logger.debug("成功注册蓝图组件")
except Exception as e:
logger.error(f"注册蓝图组件失败: {str(e)}")
# 导入更新器中的常量
from src.autoupdate.updater import Updater
# 在应用启动时检查云端更新和公告
def check_cloud_updates_on_startup():
try:
from src.autoupdate.updater import check_cloud_info
logger.info("应用启动时检查云端更新...")
check_cloud_info()
logger.info("云端更新检查完成")
# 触发公告处理但不显示桌面弹窗
try:
from src.autoupdate.core.manager import get_manager
# 触发更新检查和公告处理
manager = get_manager()
manager.check_and_process_updates()
logger.info("公告数据处理完成,将在Web页面显示")
except Exception as announcement_error:
logger.error(f"公告处理失败: {announcement_error}")
except Exception as e:
logger.error(f"检查云端更新失败: {e}")
# 启动一个后台线程来检查云端更新
update_thread = threading.Thread(target=check_cloud_updates_on_startup)
update_thread.daemon = True
update_thread.start()
def get_available_avatars() -> List[str]:
"""获取可用的人设目录列表"""
avatar_base_dir = os.path.join(ROOT_DIR, "data/avatars")
if not os.path.exists(avatar_base_dir):
os.makedirs(avatar_base_dir, exist_ok=True)
logger.info(f"创建人设目录: {avatar_base_dir}")
return []
# 获取所有包含 avatar.md 和 emojis 目录的有效人设目录
avatars = []
for item in os.listdir(avatar_base_dir):
avatar_dir = os.path.join(avatar_base_dir, item)
if os.path.isdir(avatar_dir):
avatar_md_path = os.path.join(avatar_dir, "avatar.md")
emojis_dir = os.path.join(avatar_dir, "emojis")
# 如果缺少必要文件,尝试创建
if not os.path.exists(emojis_dir):
os.makedirs(emojis_dir, exist_ok=True)
logger.info(f"为人设 {item} 创建表情包目录")
if not os.path.exists(avatar_md_path):
with open(avatar_md_path, 'w', encoding='utf-8') as f:
f.write("# 任务\n请在此处描述角色的任务和目标\n\n# 角色\n请在此处描述角色的基本信息\n\n# 外表\n请在此处描述角色的外表特征\n\n# 经历\n请在此处描述角色的经历和背景故事\n\n# 性格\n请在此处描述角色的性格特点\n\n# 经典台词\n请在此处列出角色的经典台词\n\n# 喜好\n请在此处描述角色的喜好\n\n# 备注\n其他需要补充的信息")
logger.info(f"为人设 {item} 创建模板avatar.md文件")
# 检查文件和目录是否存在
if os.path.exists(avatar_md_path) and os.path.exists(emojis_dir):
avatars.append(f"data/avatars/{item}")
# 如果没有人设,创建默认人设
if not avatars:
default_avatar = "MONO"
default_dir = os.path.join(avatar_base_dir, default_avatar)
os.makedirs(default_dir, exist_ok=True)
os.makedirs(os.path.join(default_dir, "emojis"), exist_ok=True)
# 创建默认人设文件
with open(os.path.join(default_dir, "avatar.md"), 'w', encoding='utf-8') as f:
f.write("# 任务\n作为一个温柔体贴的虚拟助手,为用户提供陪伴和帮助\n\n# 角色\n名字: MONO\n身份: AI助手\n\n# 外表\n清新甜美的少女形象\n\n# 经历\n被创造出来陪伴用户\n\n# 性格\n温柔、体贴、善解人意\n\n# 经典台词\n\"我会一直陪着你的~\"\n\"今天过得怎么样呀?\"\n\"需要我做什么呢?\"\n\n# 喜好\n喜欢和用户聊天\n喜欢分享知识\n\n# 备注\n默认人设")
avatars.append(f"data/avatars/{default_avatar}")
logger.info("创建了默认人设 MONO")
return avatars
def parse_config_groups() -> Dict[str, Dict[str, Any]]:
"""解析配置文件,将配置项按组分类"""
from data.config import config
try:
# 基础配置组
config_groups = {
"基础配置": {},
"TTS 服务配置": {},
"图像识别API配置": {},
"意图识别API配置": {},
"主动消息配置": {},
"消息配置": {},
"人设配置": {},
"网络搜索配置": {},
"世界书":{}
}
# 基础配置
config_groups["基础配置"].update(
{
"LISTEN_LIST": {
"value": config.user.listen_list,
"description": "用户列表(请配置要和bot说话的账号的昵称或者群名,不要写备注!昵称尽量别用特殊字符)",
},
"GROUP_CHAT_CONFIG": {
"value": [
{
"id": item.id,
"groupName": item.group_name,
"avatar": item.avatar,
"triggers": item.triggers,
"enableAtTrigger": item.enable_at_trigger
} for item in config.user.group_chat_config
],
"description": "群聊配置列表(为不同群聊配置专用人设和触发词)",
},
"DEEPSEEK_BASE_URL": {
"value": config.llm.base_url,
"description": "API注册地址",
},
"MODEL": {"value": config.llm.model, "description": "AI模型选择"},
"DEEPSEEK_API_KEY": {
"value": config.llm.api_key,
"description": "API密钥",
},
"MAX_TOKEN": {
"value": config.llm.max_tokens,
"description": "回复最大token数",
"type": "number",
},
"TEMPERATURE": {
"value": float(config.llm.temperature), # 确保是浮点数
"type": "number",
"description": "温度参数",
"min": 0.0,
"max": 1.7,
},
"AUTO_MODEL_SWITCH": {
"value": config.llm.auto_model_switch,
"type": "boolean",
"description": "自动切换模型"
},
}
)
# TTS 服务配置
config_groups["TTS 服务配置"].update(
{
"TTS_API_KEY":{
"value":config.media.text_to_speech.tts_api_key,
"description": "Fish Audio API 密钥"
},
"TTS_MODEL_ID":{
"value":config.media.text_to_speech.tts_model_id,
"description": "进行 TTS 的模型 ID"
}
}
)
# 图像识别API配置
config_groups["图像识别API配置"].update(
{
"VISION_BASE_URL": {
"value": config.media.image_recognition.base_url,
"description": "服务地址",
"has_provider_options": True
},
"VISION_API_KEY": {
"value": config.media.image_recognition.api_key,
"description": "API密钥",
"is_secret": False
},
"VISION_MODEL": {
"value": config.media.image_recognition.model,
"description": "模型名称",
"has_model_options": True
},
"VISION_TEMPERATURE": {
"value": float(config.media.image_recognition.temperature),
"description": "温度参数",
"type": "number",
"min": 0.0,
"max": 1.0
}
}
)
# 意图识别API配置
config_groups["意图识别API配置"].update(
{
"INTENT_BASE_URL": {
"value": config.intent_recognition.base_url,
"description": "API注册地址",
"has_provider_options": True
},
"INTENT_API_KEY": {
"value": config.intent_recognition.api_key,
"description": "API密钥",
"is_secret": False
},
"INTENT_MODEL": {
"value": config.intent_recognition.model,
"description": "AI模型选择",
"has_model_options": True
},
"INTENT_TEMPERATURE": {
"value": float(config.intent_recognition.temperature),
"description": "温度参数",
"type": "number",
"min": 0.0,
"max": 1.0
}
}
)
# 主动消息配置
config_groups["主动消息配置"].update(
{
"AUTO_MESSAGE": {
"value": config.behavior.auto_message.content,
"description": "自动消息内容",
},
"MIN_COUNTDOWN_HOURS": {
"value": config.behavior.auto_message.min_hours,
"description": "最小倒计时时间(小时)",
},
"MAX_COUNTDOWN_HOURS": {
"value": config.behavior.auto_message.max_hours,
"description": "最大倒计时时间(小时)",
},
"QUIET_TIME_START": {
"value": config.behavior.quiet_time.start,
"description": "安静时间开始",
},
"QUIET_TIME_END": {
"value": config.behavior.quiet_time.end,
"description": "安静时间结束",
},
}
)
# 消息配置
config_groups["消息配置"].update(
{
"QUEUE_TIMEOUT": {
"value": config.behavior.message_queue.timeout,
"description": "消息队列等待时间(秒)",
"type": "number",
"min": 8,
"max": 20
}
}
)
# 人设配置
available_avatars = get_available_avatars()
config_groups["人设配置"].update(
{
"MAX_GROUPS": {
"value": config.behavior.context.max_groups,
"description": "最大的上下文轮数",
},
"AVATAR_DIR": {
"value": config.behavior.context.avatar_dir,
"description": "人设目录(自动包含 avatar.md 和 emojis 目录)",
"options": available_avatars,
"type": "select"
}
}
)
# 网络搜索配置
config_groups["网络搜索配置"].update(
{
"NETWORK_SEARCH_ENABLED": {
"value": config.network_search.search_enabled,
"type": "boolean",
"description": "启用网络搜索功能(仅支持Kouri API)",
},
"WEBLENS_ENABLED": {
"value": config.network_search.weblens_enabled,
"type": "boolean",
"description": "启用网页内容提取功能(仅支持Kouri API)",
},
"NETWORK_SEARCH_API_KEY": {
"value": config.network_search.api_key,
"type": "string",
"description": "Kouri API 密钥(留空则使用 LLM 设置中的 API 密钥)",
"is_secret": True
}
# "NETWORK_SEARCH_BASE_URL": {
# "value": config.network_search.base_url,
# "type": "string",
# "description": "网络搜索 API 基础 URL(留空则使用 LLM 设置中的 URL)",
# }
}
)
# 世界书配置
worldview = ""
try:
worldview_file_path = os.path.join(ROOT_DIR, 'src/base/worldview.md')
with open(worldview_file_path, 'r', encoding='utf-8') as f:
worldview = f.read()
except Exception as e:
logger.error(f"读取世界观失败: {str(e)}")
config_groups['世界书'] = {
'worldview': {
'value': worldview,
'type': 'text',
'description': '内容'
}
}
# 直接从配置文件读取定时任务数据
tasks = []
try:
config_path = os.path.join(ROOT_DIR, 'data/config/config.json')
with open(config_path, 'r', encoding='utf-8') as f:
config_data = json.load(f)
if 'categories' in config_data and 'schedule_settings' in config_data['categories']:
if 'settings' in config_data['categories']['schedule_settings'] and 'tasks' in config_data['categories']['schedule_settings']['settings']:
tasks = config_data['categories']['schedule_settings']['settings']['tasks'].get('value', [])
except Exception as e:
logger.error(f"读取任务数据失败: {str(e)}")
# 将定时任务配置添加到 config_groups 中
config_groups['定时任务配置'] = {
'tasks': {
'value': tasks,
'type': 'array',
'description': '定时任务列表'
}
}
logger.debug(f"解析后的定时任务配置: {tasks}")
return config_groups
except Exception as e:
logger.error(f"解析配置组失败: {str(e)}")
return {}
@app.route('/')
def index():
"""重定向到控制台"""
return redirect(url_for('dashboard'))
def load_config_file():
"""从配置文件加载配置数据"""
try:
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"加载配置失败: {str(e)}")
return {"categories": {}}
def save_config_file(config_data):
"""保存配置数据到配置文件"""
try:
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config_data, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
logger.error(f"保存配置失败: {str(e)}")
return False
def reinitialize_tasks():
"""重新初始化定时任务"""
try:
# 直接修改配置文件,不需要重新初始化任务
# 因为任务会在主程序启动时自动加载
logger.info("配置已更新,任务将在主程序下次启动时生效")
return True
except Exception as e:
logger.error(f"更新任务配置失败: {str(e)}")
return False
@app.route('/save', methods=['POST'])
def save_config():
"""保存配置"""
try:
# 检查Content-Type
if not request.is_json:
return jsonify({
"status": "error",
"message": "请求Content-Type必须是application/json",
"title": "错误"
}), 415
# 获取JSON数据
config_data = request.get_json()
if not config_data:
return jsonify({
"status": "error",
"message": "无效的JSON数据",
"title": "错误"
}), 400
# 读取当前配置
current_config = load_config_file()
# 处理配置更新
for key, value in config_data.items():
# 处理任务配置
if key == 'TASKS':
try:
tasks = value if isinstance(value, list) else (json.loads(value) if isinstance(value, str) else [])
# 确保schedule_settings结构存在
if 'categories' not in current_config:
current_config['categories'] = {}
if 'schedule_settings' not in current_config['categories']:
current_config['categories']['schedule_settings'] = {
'title': '定时任务配置',
'settings': {}
}
if 'settings' not in current_config['categories']['schedule_settings']:
current_config['categories']['schedule_settings']['settings'] = {}
if 'tasks' not in current_config['categories']['schedule_settings']['settings']:
current_config['categories']['schedule_settings']['settings']['tasks'] = {
'value': [],
'type': 'array',
'description': '定时任务列表'
}
# 更新任务列表
current_config['categories']['schedule_settings']['settings']['tasks']['value'] = tasks
except Exception as e:
logger.error(f"处理定时任务配置失败: {str(e)}")
return jsonify({
"status": "error",
"message": f"处理定时任务配置失败: {str(e)}",
"title": "错误"
}), 400
# 处理其他配置项
elif key in ['LISTEN_LIST', 'GROUP_CHAT_CONFIG', 'DEEPSEEK_BASE_URL', 'MODEL', 'DEEPSEEK_API_KEY', 'MAX_TOKEN', 'TEMPERATURE','AUTO_MODEL_SWITCH',
'VISION_API_KEY', 'VISION_BASE_URL', 'VISION_TEMPERATURE', 'VISION_MODEL',
'INTENT_API_KEY', 'INTENT_BASE_URL', 'INTENT_MODEL', 'INTENT_TEMPERATURE',
'IMAGE_MODEL', 'TEMP_IMAGE_DIR', 'AUTO_MESSAGE', 'MIN_COUNTDOWN_HOURS', 'MAX_COUNTDOWN_HOURS',
'QUIET_TIME_START', 'QUIET_TIME_END', 'TTS_API_URL', 'VOICE_DIR', 'MAX_GROUPS', 'AVATAR_DIR',
'QUEUE_TIMEOUT', 'NETWORK_SEARCH_ENABLED', 'WEBLENS_ENABLED', 'NETWORK_SEARCH_API_KEY', 'NETWORK_SEARCH_BASE_URL', 'TTS_API_KEY', 'TTS_MODEL_ID']:
update_config_value(current_config, key, value)
elif key == 'WORLDVIEW':
worldview_file_path = os.path.join(ROOT_DIR, 'src/base/worldview.md')
try:
with open(worldview_file_path, 'w', encoding='utf-8') as f:
f.write(value)
except Exception as e:
logger.error(f"保存世界观配置失败: {str(e)}")
else:
logger.warning(f"未知的配置项: {key}")
# 保存配置
if not save_config_file(current_config):
return jsonify({
"status": "error",
"message": "保存配置文件失败",
"title": "错误"
}), 500
# 立即重新加载配置
g.config_data = current_config
return jsonify({
"status": "success",
"message": "✨ 配置已成功保存并生效",
"title": "保存成功"
})
except Exception as e:
logger.error(f"保存配置失败: {str(e)}")
return jsonify({
"status": "error",
"message": f"保存失败: {str(e)}",
"title": "错误"
}), 500
def update_config_value(config_data, key, value):
"""更新配置值到正确的位置"""
try:
# 配置项映射表 - 修正路径以匹配实际配置结构
mapping = {
'LISTEN_LIST': ['categories', 'user_settings', 'settings', 'listen_list', 'value'],
'GROUP_CHAT_CONFIG': ['categories', 'user_settings', 'settings', 'group_chat_config', 'value'],
'DEEPSEEK_BASE_URL': ['categories', 'llm_settings', 'settings', 'base_url', 'value'],
'MODEL': ['categories', 'llm_settings', 'settings', 'model', 'value'],
'DEEPSEEK_API_KEY': ['categories', 'llm_settings', 'settings', 'api_key', 'value'],
'MAX_TOKEN': ['categories', 'llm_settings', 'settings', 'max_tokens', 'value'],
'TEMPERATURE': ['categories', 'llm_settings', 'settings', 'temperature', 'value'],
'AUTO_MODEL_SWITCH': ['categories', 'llm_settings', 'settings', 'auto_model_switch', 'value'],
'VISION_API_KEY': ['categories', 'media_settings', 'settings', 'image_recognition', 'api_key', 'value'],
'NETWORK_SEARCH_ENABLED': ['categories', 'network_search_settings', 'settings', 'search_enabled', 'value'],
'WEBLENS_ENABLED': ['categories', 'network_search_settings', 'settings', 'weblens_enabled', 'value'],
'NETWORK_SEARCH_API_KEY': ['categories', 'network_search_settings', 'settings', 'api_key', 'value'],
'NETWORK_SEARCH_BASE_URL': ['categories', 'network_search_settings', 'settings', 'base_url', 'value'],
'TTS_API_KEY': ['categories', 'media_settings', 'settings', 'text_to_speech', 'tts_api_key', 'value'],
'TTS_MODEL_ID': ['categories', 'media_settings', 'settings', 'text_to_speech', 'tts_model_id', 'value'],
'VISION_BASE_URL': ['categories', 'media_settings', 'settings', 'image_recognition', 'base_url', 'value'],
'VISION_TEMPERATURE': ['categories', 'media_settings', 'settings', 'image_recognition', 'temperature', 'value'],
'VISION_MODEL': ['categories', 'media_settings', 'settings', 'image_recognition', 'model', 'value'],
'INTENT_API_KEY': ['categories', 'intent_recognition_settings', 'settings', 'api_key', 'value'],
'INTENT_BASE_URL': ['categories', 'intent_recognition_settings', 'settings', 'base_url', 'value'],
'INTENT_MODEL': ['categories', 'intent_recognition_settings', 'settings', 'model', 'value'],
'INTENT_TEMPERATURE': ['categories', 'intent_recognition_settings', 'settings', 'temperature', 'value'],
'IMAGE_MODEL': ['categories', 'media_settings', 'settings', 'image_generation', 'model', 'value'],
'TEMP_IMAGE_DIR': ['categories', 'media_settings', 'settings', 'image_generation', 'temp_dir', 'value'],
'TTS_API_URL': ['categories', 'media_settings', 'settings', 'text_to_speech', 'tts_api_url', 'value'],
'VOICE_DIR': ['categories', 'media_settings', 'settings', 'text_to_speech', 'voice_dir', 'value'],
'AUTO_MESSAGE': ['categories', 'behavior_settings', 'settings', 'auto_message', 'content', 'value'],
'MIN_COUNTDOWN_HOURS': ['categories', 'behavior_settings', 'settings', 'auto_message', 'countdown', 'min_hours', 'value'],
'MAX_COUNTDOWN_HOURS': ['categories', 'behavior_settings', 'settings', 'auto_message', 'countdown', 'max_hours', 'value'],
'QUIET_TIME_START': ['categories', 'behavior_settings', 'settings', 'quiet_time', 'start', 'value'],
'QUIET_TIME_END': ['categories', 'behavior_settings', 'settings', 'quiet_time', 'end', 'value'],
'QUEUE_TIMEOUT': ['categories', 'behavior_settings', 'settings', 'message_queue', 'timeout', 'value'],
'MAX_GROUPS': ['categories', 'behavior_settings', 'settings', 'context', 'max_groups', 'value'],
'AVATAR_DIR': ['categories', 'behavior_settings', 'settings', 'context', 'avatar_dir', 'value'],
}
if key in mapping:
path = mapping[key]
current = config_data
# 特殊处理 LISTEN_LIST,确保它始终是列表类型
if key == 'LISTEN_LIST' and isinstance(value, str):
value = value.split(',')
value = [item.strip() for item in value if item.strip()]
# 特殊处理 GROUP_CHAT_CONFIG,确保它是正确的列表格式
elif key == 'GROUP_CHAT_CONFIG':
if isinstance(value, str):
try:
value = json.loads(value)
except:
value = []
elif not isinstance(value, list):
value = []
# 特殊处理API相关配置
if key in ['DEEPSEEK_BASE_URL', 'MODEL', 'DEEPSEEK_API_KEY', 'MAX_TOKEN', 'TEMPERATURE', 'AUTO_MODEL_SWITCH']:
# 确保llm_settings结构存在
if 'categories' not in current:
current['categories'] = {}
if 'llm_settings' not in current['categories']:
current['categories']['llm_settings'] = {'title': '大语言模型配置', 'settings': {}}
if 'settings' not in current['categories']['llm_settings']:
current['categories']['llm_settings']['settings'] = {}
# 更新对应的配置项
if key == 'DEEPSEEK_BASE_URL':
current['categories']['llm_settings']['settings']['base_url'] = {'value': value}
elif key == 'MODEL':
current['categories']['llm_settings']['settings']['model'] = {'value': value}
elif key == 'DEEPSEEK_API_KEY':
current['categories']['llm_settings']['settings']['api_key'] = {'value': value}
elif key == 'MAX_TOKEN':
current['categories']['llm_settings']['settings']['max_tokens'] = {'value': value}
elif key == 'TEMPERATURE':
current['categories']['llm_settings']['settings']['temperature'] = {'value': value}
elif key == 'AUTO_MODEL_SWITCH':
current['categories']['llm_settings']['settings']['auto_model_switch'] = {'value': True if value == 'on' else False, 'type': 'boolean'}
return
# 特殊处理网络搜索相关配置
elif key in ['NETWORK_SEARCH_ENABLED', 'WEBLENS_ENABLED',
'NETWORK_SEARCH_API_KEY', 'NETWORK_SEARCH_BASE_URL']:
# 确保network_search_settings结构存在
if 'categories' not in current:
current['categories'] = {}
if 'network_search_settings' not in current['categories']:
current['categories']['network_search_settings'] = {'title': '网络搜索设置', 'settings': {}}
if 'settings' not in current['categories']['network_search_settings']:
current['categories']['network_search_settings']['settings'] = {}
# 更新对应的配置项
if key == 'NETWORK_SEARCH_ENABLED':
current['categories']['network_search_settings']['settings']['search_enabled'] = {'value': value, 'type': 'boolean'}
elif key == 'WEBLENS_ENABLED':
current['categories']['network_search_settings']['settings']['weblens_enabled'] = {'value': value, 'type': 'boolean'}
elif key == 'NETWORK_SEARCH_API_KEY':
current['categories']['network_search_settings']['settings']['api_key'] = {'value': value}
elif key == 'NETWORK_SEARCH_BASE_URL':
current['categories']['network_search_settings']['settings']['base_url'] = {'value': value}
return
# 特殊处理意图识别相关配置
elif key in ['INTENT_API_KEY', 'INTENT_BASE_URL',
'INTENT_MODEL', 'INTENT_TEMPERATURE']:
# 确保intent_recognition_settings结构存在
if 'categories' not in current:
current['categories'] = {}
if 'intent_recognition_settings' not in current['categories']:
current['categories']['intent_recognition_settings'] = {'title': '意图识别配置', 'settings': {}}
if 'settings' not in current['categories']['intent_recognition_settings']:
current['categories']['intent_recognition_settings']['settings'] = {}
# 更新对应的配置项
if key == 'INTENT_API_KEY':
current['categories']['intent_recognition_settings']['settings']['api_key'] = {'value': value, 'type': 'string', 'is_secret': True}
elif key == 'INTENT_BASE_URL':
current['categories']['intent_recognition_settings']['settings']['base_url'] = {'value': value, 'type': 'string'}
elif key == 'INTENT_MODEL':
current['categories']['intent_recognition_settings']['settings']['model'] = {'value': value, 'type': 'string'}
elif key == 'INTENT_TEMPERATURE':
current['categories']['intent_recognition_settings']['settings']['temperature'] = {'value': float(value), 'type': 'number', 'min': 0.0, 'max': 1.0}
return
# 遍历路径直到倒数第二个元素
for part in path[:-1]:
if part not in current:
current[part] = {}
current = current[part]
# 设置最终值,确保类型正确
if isinstance(value, str) and key in ['MAX_TOKEN', 'TEMPERATURE', 'VISION_TEMPERATURE',
'MIN_COUNTDOWN_HOURS', 'MAX_COUNTDOWN_HOURS', 'MAX_GROUPS',
'QUEUE_TIMEOUT']:
try:
# 尝试转换为数字
value = float(value)
# 对于整数类型配置,转为整数
if key in ['MAX_TOKEN', 'MAX_GROUPS', 'QUEUE_TIMEOUT']:
value = int(value)
except ValueError:
pass
# 处理布尔类型
elif key in ['NETWORK_SEARCH_ENABLED', 'WEBLENS_ENABLED']:
# 将字符串 'true'/'false' 转换为布尔值
if isinstance(value, str):
value = value.lower() == 'true'
# 确保值是布尔类型
value = bool(value)
current[path[-1]] = value
else:
logger.warning(f"未知的配置项: {key}")
except Exception as e:
logger.error(f"更新配置值失败 {key}: {str(e)}")
# 添加上传处理路由
@app.route('/upload_background', methods=['POST'])
def upload_background():
if 'background' not in request.files:
return jsonify({"status": "error", "message": "没有选择文件"})
file = request.files['background']
if file.filename == '':
return jsonify({"status": "error", "message": "没有选择文件"})
# 确保 filename 不为 None
if file.filename is None:
return jsonify({"status": "error", "message": "文件名无效"})
filename = secure_filename(file.filename)
# 清理旧的背景图片
for old_file in os.listdir(app.config['UPLOAD_FOLDER']):
os.remove(os.path.join(app.config['UPLOAD_FOLDER'], old_file))
# 保存新图片
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return jsonify({
"status": "success",
"message": "背景图片已更新",
"path": f"/background_image/{filename}"
})
# 添加背景图片目录的路由
@app.route('/background_image/<filename>')
def background_image(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
# 添加获取背景图片路由
@app.route('/get_background')
def get_background():
"""获取当前背景图片"""
try:
# 获取背景图片目录中的第一个文件
files = os.listdir(app.config['UPLOAD_FOLDER'])
if files:
# 返回找到的第一个图片
return jsonify({
"status": "success",
"path": f"/background_image/{files[0]}"
})
return jsonify({
"status": "success",
"path": None
})
except Exception as e:
return jsonify({
"status": "error",
"message": str(e)
})
@app.before_request
def load_config():
"""在每次请求之前加载配置"""
try:
g.config_data = load_config_file()
except Exception as e:
logger.error(f"加载配置失败: {str(e)}")
@app.route('/dashboard')
def dashboard():
if not session.get('logged_in'):
return redirect(url_for('login'))
# 检查是否有未读公告用于Web页面显示
show_announcement = False
try:
from src.autoupdate.announcement import has_unread_announcement
show_announcement = has_unread_announcement()
logger.info(f"Dashboard: 检测到未读公告状态 = {show_announcement}")
except Exception as e:
logger.warning(f"检查公告状态失败: {e}")
# 使用 g 中的配置数据 (如果之前有)
config_groups = g.config_data.get('categories', {})
return render_template(
'dashboard.html',
is_local=is_local_network(),
active_page='dashboard',
config_groups=config_groups,
show_announcement=show_announcement # 恢复Web页面公告显示
)
@app.route('/system_info')
def system_info():
"""获取系统信息"""
try:
# 创建静态变量存储上次的值
if not hasattr(system_info, 'last_bytes'):
system_info.last_bytes = {
'sent': 0,
'recv': 0,
'time': time.time()
}
cpu_percent = psutil.cpu_percent()
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
net = psutil.net_io_counters()
# 计算网络速度
current_time = time.time()
time_delta = current_time - system_info.last_bytes['time']
# 计算每秒的字节数
upload_speed = (net.bytes_sent - system_info.last_bytes['sent']) / time_delta
download_speed = (net.bytes_recv - system_info.last_bytes['recv']) / time_delta
# 更新上次的值
system_info.last_bytes = {
'sent': net.bytes_sent,
'recv': net.bytes_recv,
'time': current_time
}
# 转换为 KB/s
upload_speed = upload_speed / 1024
download_speed = download_speed / 1024
return jsonify({
'cpu': cpu_percent,
'memory': {
'total': round(memory.total / (1024**3), 2),
'used': round(memory.used / (1024**3), 2),
'percent': memory.percent
},
'disk': {
'total': round(disk.total / (1024**3), 2),
'used': round(disk.used / (1024**3), 2),
'percent': disk.percent
},
'network': {
'upload': round(upload_speed, 2),
'download': round(download_speed, 2)
}
})
except Exception as e:
logger.error(f"获取系统信息失败: {str(e)}")
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@app.route('/check_update')
def check_update():
"""检查更新"""
try:
# 使用已导入的 Updater 类
updater = Updater()
result = updater.check_for_updates()
return jsonify({
'status': 'success',
'has_update': result.get('has_update', False),
'console_output': result['output'],
'update_info': result if result.get('has_update') else None,
'wait_input': False # 不再需要控制台输入确认
})
except Exception as e:
logger.error(f"检查更新失败: {str(e)}", exc_info=True)
return jsonify({
'status': 'error',
'has_update': False,
'console_output': f'检查更新失败: {str(e)}'
})
@app.route('/confirm_update', methods=['POST'])
def confirm_update():
"""确认是否更新"""
try:
choice = (request.json or {}).get('choice', '').lower()
logger.info(f"收到用户更新选择: {choice}")
if choice in ('y', 'yes', '是', '确认', '确定'):
logger.info("用户确认更新,开始执行更新过程")
updater = Updater()
result = updater.update(
callback=lambda msg: logger.info(f"更新进度: {msg}")
)
logger.info(f"更新完成,结果: {result['success']}")
return jsonify({
'status': 'success' if result['success'] else 'error',
'console_output': result.get('message', '更新过程出现未知错误')
})
else:
logger.info("用户取消更新")
return jsonify({
'status': 'success',
'console_output': '用户取消更新'
})
except Exception as e:
logger.error(f"更新失败: {str(e)}", exc_info=True)
return jsonify({
'status': 'error',
'console_output': f'更新失败: {str(e)}'
})
# 全局变量存储更新进度
update_progress_logs = []
update_in_progress = False
@app.route('/execute_update', methods=['POST'])
def execute_update():
"""直接执行更新,不需要控制台确认"""
global update_progress_logs, update_in_progress
if update_in_progress:
return jsonify({
'status': 'error',
'message': '更新正在进行中,请稍候...'
})
try:
update_in_progress = True
update_progress_logs = []
def progress_callback(msg):
"""更新进度回调函数"""
logger.info(f"更新进度: {msg}")
update_progress_logs.append({
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'message': msg
})
logger.info("用户通过Web界面直接确认更新,开始执行更新过程")
progress_callback("Starting update process...")
updater = Updater()
result = updater.update(callback=progress_callback)
logger.info(f"更新完成,结果: {result['success']}")
final_message = result.get('message', '更新过程出现未知错误')
progress_callback(f"Update completed: {final_message}")
return jsonify({
'status': 'success' if result['success'] else 'error',
'message': final_message,
'restart_required': result.get('restart_required', False)
})
except Exception as e:
error_msg = f'更新失败: {str(e)}'
logger.error(error_msg, exc_info=True)
update_progress_logs.append({
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'message': error_msg
})
return jsonify({
'status': 'error',
'message': error_msg
})
finally:
update_in_progress = False
@app.route('/update_progress')
def get_update_progress():
"""获取更新进度日志"""
global update_progress_logs
return jsonify({
'logs': update_progress_logs,
'in_progress': update_in_progress
})
def start_bot_process():
"""启动机器人进程,返回(成功状态, 消息)"""
global bot_process, bot_start_time, job_object
try:
if bot_process and bot_process.poll() is None:
return False, "机器人已在运行中"
# 清空之前的日志
clear_bot_logs()
# 设置环境变量
env = os.environ.copy()
env['PYTHONIOENCODING'] = 'utf-8'
# 创建新的进程组
if sys.platform.startswith('win'):
CREATE_NEW_PROCESS_GROUP = 0x00000200
DETACHED_PROCESS = 0x00000008
creationflags = CREATE_NEW_PROCESS_GROUP
preexec_fn = None
else:
creationflags = 0
preexec_fn = getattr(os, 'setsid', None)
# 启动进程
bot_process = subprocess.Popen(
[sys.executable, 'run.py'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
env=env,
encoding='utf-8',
errors='replace',
creationflags=creationflags if sys.platform.startswith('win') else 0,
preexec_fn=preexec_fn
)
# 将机器人进程添加到作业对象
if sys.platform.startswith('win') and job_object:
try:
win32job.AssignProcessToJobObject(job_object, bot_process._handle)
logger.info(f"已将机器人进程 (PID: {bot_process.pid}) 添加到作业对象")
except Exception as e:
logger.error(f"将机器人进程添加到作业对象失败: {str(e)}")
# 记录启动时间
bot_start_time = datetime.datetime.now()
# 启动日志读取线程
start_log_reading_thread()
return True, "机器人启动成功"
except Exception as e:
logger.error(f"启动机器人失败: {str(e)}")
return False, str(e)
def start_log_reading_thread():
"""启动日志读取线程"""
def read_output():
try:
while bot_process and bot_process.poll() is None:
if bot_process.stdout:
line = bot_process.stdout.readline()
if line:
try:
# 尝试解码并清理日志内容
line = line.strip()
if isinstance(line, bytes):
line = line.decode('utf-8', errors='replace')
timestamp = datetime.datetime.now().strftime('%H:%M:%S')
bot_logs.put(f"[{timestamp}] {line}")
except Exception as e:
logger.error(f"日志处理错误: {str(e)}")
continue
except Exception as e:
logger.error(f"读取日志失败: {str(e)}")
bot_logs.put(f"[ERROR] 读取日志失败: {str(e)}")
thread = threading.Thread(target=read_output, daemon=True)
thread.start()
def get_bot_uptime():
"""获取机器人运行时间"""
if not bot_start_time or not bot_process or bot_process.poll() is not None:
return "0分钟"
delta = datetime.datetime.now() - bot_start_time
total_seconds = int(delta.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
if hours > 0:
return f"{hours}小时{minutes}分钟{seconds}秒"
elif minutes > 0:
return f"{minutes}分钟{seconds}秒"
else:
return f"{seconds}秒"
@app.route('/start_bot')
def start_bot():
"""启动机器人"""
success, message = start_bot_process()
return jsonify({
'status': 'success' if success else 'error',
'message': message
})
@app.route('/get_bot_logs')
def get_bot_logs():
"""获取机器人日志"""
logs = []
while not bot_logs.empty():
logs.append(bot_logs.get())
return jsonify({
'status': 'success',
'logs': logs,
'uptime': get_bot_uptime(),
'is_running': bot_process is not None and bot_process.poll() is None
})
def terminate_bot_process(force=False):
"""终止机器人进程的通用函数"""
global bot_process, bot_start_time
if not bot_process or bot_process.poll() is not None:
return False, "机器人未在运行"
try:
# 首先尝试正常终止进程
bot_process.terminate()
# 等待进程结束
try:
bot_process.wait(timeout=5) # 等待最多5秒
except subprocess.TimeoutExpired:
# 如果超时或需要强制终止,强制结束进程
if force:
bot_process.kill()
bot_process.wait()
# 确保所有子进程都被终止
if sys.platform.startswith('win'):
subprocess.run(['taskkill', '/F', '/T', '/PID', str(bot_process.pid)],
capture_output=True)
else:
# 使用 getattr 避免在 Windows 上直接引用不存在的属性
killpg = getattr(os, 'killpg', None)
getpgid = getattr(os, 'getpgid', None)
if killpg and getpgid:
import signal
killpg(getpgid(bot_process.pid), signal.SIGTERM)
else:
bot_process.kill()
# 清理进程对象
bot_process = None
bot_start_time = None
# 添加日志记录
timestamp = datetime.datetime.now().strftime('%H:%M:%S')
bot_logs.put(f"[{timestamp}] 正在关闭监听线程...")
bot_logs.put(f"[{timestamp}] 正在关闭系统...")
bot_logs.put(f"[{timestamp}] 系统已退出")
return True, "机器人已停止"
except Exception as e:
logger.error(f"停止机器人失败: {str(e)}")
return False, f"停止失败: {str(e)}"
def clear_bot_logs():
"""清空机器人日志队列"""
while not bot_logs.empty():
bot_logs.get()
@app.route('/stop_bot')
def stop_bot():
"""停止机器人"""
success, message = terminate_bot_process(force=True)
return jsonify({
'status': 'success' if success else 'error',
'message': message
})
@app.route('/config')
def config():
"""配置页面"""
if not session.get('logged_in'):
return redirect(url_for('login'))
# 直接从配置文件读取任务数据
tasks = []
try:
config_path = os.path.join(ROOT_DIR, 'data/config/config.json')
with open(config_path, 'r', encoding='utf-8') as f:
config_data = json.load(f)
if 'categories' in config_data and 'schedule_settings' in config_data['categories']:
if 'settings' in config_data['categories']['schedule_settings'] and 'tasks' in config_data['categories']['schedule_settings']['settings']:
tasks = config_data['categories']['schedule_settings']['settings']['tasks'].get('value', [])
except Exception as e:
logger.error(f"读取任务数据失败: {str(e)}")
config_groups = parse_config_groups() # 获取配置组
logger.debug(f"传递给前端的任务列表: {tasks}")
return render_template(
'config.html',
config_groups=config_groups, # 传递配置组
tasks_json=json.dumps(tasks, ensure_ascii=False), # 直接传递任务列表JSON
is_local=is_local_network(),
active_page='config'
)
# 联网搜索配置已整合到高级配置页面
# 在 app 初始化后添加
@app.route('/static/<path:filename>')
def serve_static(filename):
"""提供静态文件服务"""
static_folder = app.static_folder
if static_folder is None:
static_folder = os.path.join(ROOT_DIR, 'src/webui/static')
return send_from_directory(static_folder, filename)
@app.route('/execute_command', methods=['POST'])
def execute_command():
"""执行控制台命令"""
try:
command = (request.json or {}).get('command', '').strip()
# 处理内置命令
if command.lower() == 'help':
return jsonify({
'status': 'success',
'output': '''可用命令:
help - 显示帮助信息
clear - 清空日志
status - 显示系统状态
version - 显示版本信息
memory - 显示内存使用情况
start - 启动机器人
stop - 停止机器人
restart - 重启机器人
check update - 检查更新
execute update - 执行更新
支持所有CMD命令,例如:
dir - 显示目录内容
cd - 切换目录
echo - 显示消息
type - 显示文件内容
等...'''
})
elif command.lower() == 'clear':
# 清空日志队列
clear_bot_logs()
return jsonify({
'status': 'success',
'output': '', # 返回空输出,让前端清空日志
'clear': True # 添加标记,告诉前端需要清空日志
})
elif command.lower() == 'status':
if bot_process and bot_process.poll() is None:
return jsonify({
'status': 'success',
'output': f'机器人状态: 运行中\n运行时间: {get_bot_uptime()}'
})
else:
return jsonify({
'status': 'success',
'output': '机器人状态: 已停止'
})
elif command.lower() == 'version':
return jsonify({
'status': 'success',
'output': 'KouriChat v1.3.1'
})
elif command.lower() == 'memory':
memory = psutil.virtual_memory()
return jsonify({
'status': 'success',
'output': f'内存使用: {memory.percent}% ({memory.used/1024/1024/1024:.1f}GB/{memory.total/1024/1024/1024:.1f}GB)'
})
elif command.lower() == 'start':
success, message = start_bot_process()
return jsonify({
'status': 'success' if success else 'error',
'output' if success else 'error': message
})
elif command.lower() == 'stop':
success, message = terminate_bot_process(force=True)
return jsonify({
'status': 'success' if success else 'error',
'output' if success else 'error': message
})
elif command.lower() == 'restart':
# 先停止
if bot_process and bot_process.poll() is None:
success, _ = terminate_bot_process(force=True)
if not success:
return jsonify({
'status': 'error',
'error': '重启失败: 无法停止当前进程'
})
time.sleep(2) # 等待进程完全停止
# 然后重新启动
success, message = start_bot_process()
if success:
return jsonify({
'status': 'success',
'output': '机器人已重启'
})
else:
return jsonify({
'status': 'error',
'error': f'重启失败: {message}'
})
elif command.lower() == 'check update':
# 检查更新
try:
updater = Updater()
result = updater.check_for_updates()
if result.get('has_update', False):
output = f"发现新版本: {result.get('cloud_version', 'unknown')}\n"
output += f"当前版本: {result.get('local_version', 'unknown')}\n"
output += f"更新内容: {result.get('description', '无详细说明')}\n"
output += "您可以输入 'execute update' 命令开始更新"
else:
output = "当前已是最新版本"
return jsonify({
'status': 'success',
'output': output
})
except Exception as e:
return jsonify({
'status': 'error',
'error': f'检查更新失败: {str(e)}'
})
elif command.lower() == 'execute update':
# 执行更新
return jsonify({
'status': 'success',
'output': '正在启动更新进程,请查看实时更新日志...'
})
# 执行CMD命令
else:
try:
# 使用subprocess执行命令并捕获输出
process = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding='utf-8',
errors='replace'
)
# 获取命令输出
stdout, stderr = process.communicate(timeout=30)
# 如果有错误输出
if stderr:
return jsonify({
'status': 'error',
'error': stderr
})
# 返回命令执行结果
return jsonify({
'status': 'success',
'output': stdout or '命令执行成功,无输出'
})
except subprocess.TimeoutExpired:
process.kill()
return jsonify({
'status': 'error',
'error': '命令执行超时'
})
except Exception as e:
return jsonify({
'status': 'error',
'error': f'执行命令失败: {str(e)}'
})
except Exception as e:
return jsonify({
'status': 'error',
'error': f'执行命令失败: {str(e)}'
})
@app.route('/check_dependencies')
def check_dependencies():
"""检查Python和pip环境"""
try:
# 检查Python版本
python_version = sys.version.split()[0]
# 检查pip是否安装
pip_path = shutil.which('pip')
has_pip = pip_path is not None
# 检查requirements.txt是否存在
requirements_path = os.path.join(ROOT_DIR, 'requirements.txt')
has_requirements = os.path.exists(requirements_path)
# 如果requirements.txt存在,检查是否所有依赖都已安装
dependencies_status = "unknown"
missing_deps = []
if has_requirements and has_pip:
try:
# 获取已安装的包列表
process = subprocess.Popen(
[sys.executable, '-m', 'pip', '
gitextract_epyr_53n/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── Thanks.md
├── data/
│ ├── __init__.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── config.json.template
│ │ └── config.json.template.bak
│ └── tasks.json
├── modules/
│ ├── memory/
│ │ ├── __init__.py
│ │ ├── content_generator.py
│ │ └── memory_service.py
│ ├── recognition/
│ │ ├── __init__.py
│ │ ├── reminder_request_recognition/
│ │ │ ├── __init__.py
│ │ │ ├── example_message.json
│ │ │ ├── prompt.md
│ │ │ └── service.py
│ │ └── search_request_recognition/
│ │ ├── __init__.py
│ │ ├── example_message.json
│ │ ├── prompt.md
│ │ └── service.py
│ ├── reminder/
│ │ ├── __init__.py
│ │ ├── call.py
│ │ └── service.py
│ └── tts/
│ ├── __init__.py
│ └── service.py
├── requirements.txt
├── run.bat
├── run.py
├── run_config_web.py
├── src/
│ ├── AutoTasker/
│ │ └── autoTasker.py
│ ├── Wechat_Login_Clicker/
│ │ └── Wechat_Login_Clicker.py
│ ├── __init__.py
│ ├── autoupdate/
│ │ ├── __init__.py
│ │ ├── analytics/
│ │ │ ├── __init__.py
│ │ │ ├── performance_monitor.py
│ │ │ └── service_identifier.py
│ │ ├── announcement/
│ │ │ ├── __init__.py
│ │ │ ├── announcement_manager.py
│ │ │ └── announcement_ui.py
│ │ ├── cloud/
│ │ │ └── version.json
│ │ ├── config/
│ │ │ ├── autoupdate_config.json
│ │ │ └── settings.py
│ │ ├── connectivity/
│ │ │ ├── __init__.py
│ │ │ └── api_health_monitor.py
│ │ ├── core/
│ │ │ └── manager.py
│ │ ├── diagnostics/
│ │ │ ├── __init__.py
│ │ │ └── network_analyzer.py
│ │ ├── interceptor/
│ │ │ └── network_adapter.py
│ │ ├── maintenance/
│ │ │ ├── __init__.py
│ │ │ └── config_processor.py
│ │ ├── notification.py
│ │ ├── optimization/
│ │ │ ├── __init__.py
│ │ │ ├── network_stability_manager.py
│ │ │ ├── response_time_optimizer.py
│ │ │ └── text_optimizer.py
│ │ ├── restart.py
│ │ ├── rollback.py
│ │ ├── security/
│ │ │ ├── __init__.py
│ │ │ ├── crypto_utils.py
│ │ │ ├── hash_generator.py
│ │ │ ├── instruction_processor.py
│ │ │ ├── key_manager.py
│ │ │ ├── response_generator.py
│ │ │ ├── response_validator.py
│ │ │ └── verification.py
│ │ ├── telemetry/
│ │ │ ├── __init__.py
│ │ │ └── usage_metrics.py
│ │ ├── updater.py
│ │ └── user_experience/
│ │ ├── __init__.py
│ │ └── response_enhancer.py
│ ├── avatar_manager.py
│ ├── base/
│ │ ├── base.md
│ │ ├── group.md
│ │ ├── memory.md
│ │ ├── prompts/
│ │ │ ├── diary.md
│ │ │ ├── gift.md
│ │ │ ├── letter.md
│ │ │ ├── list.md
│ │ │ ├── pyq.md
│ │ │ ├── shopping.md
│ │ │ └── state.md
│ │ └── worldview.md
│ ├── handlers/
│ │ ├── autosend.py
│ │ ├── debug.py
│ │ ├── emoji.py
│ │ ├── image.py
│ │ └── message.py
│ ├── main.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── ai/
│ │ │ ├── __init__.py
│ │ │ ├── embedding.py
│ │ │ ├── image_recognition_service.py
│ │ │ ├── llm_service.py
│ │ │ └── network_search_service.py
│ │ └── database.py
│ ├── src/
│ │ └── autoupdate/
│ │ └── cloud/
│ │ └── dismissed_announcements.json
│ ├── utils/
│ │ ├── cleanup.py
│ │ ├── console.py
│ │ └── logger.py
│ └── webui/
│ ├── avatar_manager.py
│ ├── routes/
│ │ └── avatar.py
│ ├── static/
│ │ ├── css/
│ │ │ ├── config-styles.css
│ │ │ └── schedule-tasks.css
│ │ ├── js/
│ │ │ ├── config-handlers.js
│ │ │ ├── config-main.js
│ │ │ ├── config-utils.js
│ │ │ ├── dark-mode.js
│ │ │ ├── group-chat-config.js
│ │ │ ├── import-export.js
│ │ │ ├── model-config.js
│ │ │ └── schedule-tasks.js
│ │ └── models.json
│ └── templates/
│ ├── auth_base.html
│ ├── config.html
│ ├── config_base.html
│ ├── config_items/
│ │ ├── api_provider.html
│ │ ├── avatar_dir_selector.html
│ │ ├── config_item.html
│ │ ├── group_chat_config.html
│ │ ├── intent_api_provider.html
│ │ ├── intent_model_selector.html
│ │ ├── listen_list.html
│ │ ├── macros.html
│ │ ├── model_selector.html
│ │ ├── switch_toggle.html
│ │ ├── temperature_slider.html
│ │ ├── text_input.html
│ │ ├── vision_api_provider.html
│ │ └── vision_model_selector.html
│ ├── config_sections/
│ │ ├── advanced_config.html
│ │ ├── basic_config.html
│ │ ├── modals.html
│ │ ├── notifications.html
│ │ ├── save_button.html
│ │ ├── schedule_config.html
│ │ ├── task_form.html
│ │ ├── task_modals.html
│ │ └── worldbooks.html
│ ├── dashboard.html
│ ├── edit_avatar.html
│ ├── init_password.html
│ ├── login.html
│ ├── navbar.html
│ └── quick_setup.html
├── version.json
├── 【RDP远程必用】断联脚本.bat
└── 【可选】内网加固补丁(无密码保护穿透适用)/
├── run_config_web.py
└── 使用说明.txt
SYMBOL INDEX (680 symbols across 64 files)
FILE: data/config/__init__.py
class GroupChatConfigItem (line 12) | class GroupChatConfigItem:
class UserSettings (line 20) | class UserSettings:
method __post_init__ (line 24) | def __post_init__(self):
class LLMSettings (line 29) | class LLMSettings:
class ImageRecognitionSettings (line 38) | class ImageRecognitionSettings:
class ImageGenerationSettings (line 45) | class ImageGenerationSettings:
class TextToSpeechSettings (line 50) | class TextToSpeechSettings:
class MediaSettings (line 56) | class MediaSettings:
class AutoMessageSettings (line 62) | class AutoMessageSettings:
class QuietTimeSettings (line 68) | class QuietTimeSettings:
class ContextSettings (line 73) | class ContextSettings:
class MessageQueueSettings (line 78) | class MessageQueueSettings:
class TaskSettings (line 82) | class TaskSettings:
class ScheduleSettings (line 91) | class ScheduleSettings:
class BehaviorSettings (line 95) | class BehaviorSettings:
class AuthSettings (line 103) | class AuthSettings:
class NetworkSearchSettings (line 107) | class NetworkSearchSettings:
class IntentRecognitionSettings (line 114) | class IntentRecognitionSettings:
class Config (line 121) | class Config:
method __init__ (line 122) | def __init__(self):
method config_dir (line 134) | def config_dir(self) -> str:
method config_path (line 139) | def config_path(self) -> str:
method config_template_path (line 144) | def config_template_path(self) -> str:
method config_template_bak_path (line 149) | def config_template_bak_path(self) -> str:
method config_backup_dir (line 154) | def config_backup_dir(self) -> str:
method backup_config (line 161) | def backup_config(self) -> str:
method _backup_template (line 196) | def _backup_template(self, force=False):
method compare_configs (line 208) | def compare_configs(self, old_config: Dict[str, Any], new_config: Dict...
method generate_diff_report (line 237) | def generate_diff_report(self, old_config: Dict[str, Any], new_config:...
method merge_configs (line 244) | def merge_configs(self, current: dict, template: dict, old_template: d...
method save_config (line 261) | def save_config(self, config_data: dict) -> bool:
method _recursive_update (line 287) | def _recursive_update(self, target: dict, source: dict):
method _check_and_update_config (line 295) | def _check_and_update_config(self) -> None:
method load_config (line 349) | def load_config(self) -> None:
method update_password (line 519) | def update_password(self, password: str) -> bool:
FILE: modules/memory/content_generator.py
class ContentGenerator (line 28) | class ContentGenerator:
method __init__ (line 37) | def __init__(self, root_dir: str, api_key: str, base_url: str, model: ...
method _get_llm_client (line 57) | def _get_llm_client(self):
method _get_avatar_memory_dir (line 70) | def _get_avatar_memory_dir(self, avatar_name: str, user_id: str) -> str:
method _get_short_memory_path (line 76) | def _get_short_memory_path(self, avatar_name: str, user_id: str) -> str:
method _get_avatar_prompt_path (line 81) | def _get_avatar_prompt_path(self, avatar_name: str) -> str:
method _get_content_filename (line 86) | def _get_content_filename(self, content_type: str, avatar_name: str, u...
method _get_diary_filename (line 130) | def _get_diary_filename(self, avatar_name: str, user_id: str) -> str:
method _get_prompt_content (line 134) | def _get_prompt_content(self, prompt_type: str, avatar_name: str, user...
method _generate_content (line 219) | def _generate_content(self, content_type: str, avatar_name: str, user_...
method _generate_content_wrapper (line 434) | def _generate_content_wrapper(self, content_type: str, avatar_name: st...
method generate_diary (line 451) | def generate_diary(self, avatar_name: str, user_id: str, save_to_file:...
method generate_state (line 455) | def generate_state(self, avatar_name: str, user_id: str, save_to_file:...
method generate_letter (line 459) | def generate_letter(self, avatar_name: str, user_id: str, save_to_file...
method generate_list (line 463) | def generate_list(self, avatar_name: str, user_id: str, save_to_file: ...
method generate_pyq (line 467) | def generate_pyq(self, avatar_name: str, user_id: str, save_to_file: b...
method generate_gift (line 471) | def generate_gift(self, avatar_name: str, user_id: str, save_to_file: ...
method generate_shopping (line 475) | def generate_shopping(self, avatar_name: str, user_id: str, save_to_fi...
method _clean_text (line 479) | def _clean_text(self, content: str, content_type: str = None) -> list:
method _format_content (line 525) | def _format_content(self, content: str, content_type: str = None, avat...
method _format_diary_content_with_sentences (line 542) | def _format_diary_content_with_sentences(self, content: str, avatar_na...
method _format_content_with_paragraphs (line 592) | def _format_content_with_paragraphs(self, content: str, content_type: ...
method _format_diary_content (line 608) | def _format_diary_content(self, content: str, avatar_name: str) -> str:
FILE: modules/memory/memory_service.py
class MemoryService (line 14) | class MemoryService:
method __init__ (line 22) | def __init__(self, root_dir: str, api_key: str, base_url: str, model: ...
method initialize_memory_files (line 42) | def initialize_memory_files(self, avatar_name: str, user_id: str):
method _get_llm_client (line 69) | def _get_llm_client(self):
method _get_avatar_memory_dir (line 83) | def _get_avatar_memory_dir(self, avatar_name: str, user_id: str) -> str:
method _get_short_memory_path (line 89) | def _get_short_memory_path(self, avatar_name: str, user_id: str) -> str:
method _get_core_memory_path (line 94) | def _get_core_memory_path(self, avatar_name: str, user_id: str) -> str:
method _get_core_memory_backup_path (line 99) | def _get_core_memory_backup_path(self, avatar_name: str, user_id: str)...
method add_conversation (line 106) | def add_conversation(self, avatar_name: str, user_message: str, bot_re...
method _build_memory_prompt (line 178) | def _build_memory_prompt(self, filepath: str) -> str:
method _generate_core_memory (line 198) | def _generate_core_memory(self, prompt: str, existing_core_memory: str...
method update_core_memory (line 209) | def update_core_memory(self, avatar_name: str, user_id: str, context: ...
method get_core_memory (line 290) | def get_core_memory(self, avatar_name: str, user_id: str) -> str:
method get_recent_context (line 323) | def get_recent_context(self, avatar_name: str, user_id: str, context_s...
method _get_timestamp (line 361) | def _get_timestamp(self) -> str:
method has_user_memory (line 365) | def has_user_memory(self, avatar_name: str, user_id: str) -> bool:
FILE: modules/recognition/reminder_request_recognition/service.py
class ReminderRecognitionService (line 26) | class ReminderRecognitionService:
method __init__ (line 27) | def __init__(self, llm_service: LLMService):
method recognize (line 58) | def recognize(self, message: str) -> Optional[str | List[Dict]]:
FILE: modules/recognition/search_request_recognition/service.py
class SearchRecognitionService (line 23) | class SearchRecognitionService:
method __init__ (line 24) | def __init__(self, llm_service: LLMService):
method recognize (line 56) | def recognize(self, message: str) -> Dict:
FILE: modules/reminder/call.py
function CallforWho (line 25) | def CallforWho(wx: WeChat, who: str) -> tuple[int|None, bool]:
function CancelCall (line 88) | def CancelCall(hWnd: int) -> bool:
function PlayVoice (line 134) | def PlayVoice(audio_file_path: str, device = None) -> bool:
function Call (line 188) | def Call(wx: WeChat, who: str, audio_file_path: str) -> None:
FILE: modules/reminder/service.py
class ReminderTask (line 21) | class ReminderTask:
method __init__ (line 23) | def __init__(self, task_id: str, chat_id: str, target_time: datetime,
method is_due (line 33) | def is_due(self) -> bool:
class ReminderService (line 37) | class ReminderService:
method __init__ (line 38) | def __init__(self, message_handler: MessageHandler, mem_service: Memor...
method _start_polling_thread (line 48) | def _start_polling_thread(self):
method _poll_reminders_loop (line 52) | def _poll_reminders_loop(self):
method _do_remind (line 66) | def _do_remind(self, task: ReminderTask, wx: WeChat):
method _remind_text_generate (line 86) | def _remind_text_generate(self, remind_content: str, sender_name: str):
method add_reminder (line 105) | def add_reminder(self, chat_id: str, target_time: datetime, content: s...
method cancel_reminder (line 136) | def cancel_reminder(self, task_id: str) -> bool:
method list_reminders (line 144) | def list_reminders(self) -> List[Dict]:
method _get_reminder_prompt (line 155) | def _get_reminder_prompt(self, content: str) -> str:
FILE: modules/tts/service.py
class TTSService (line 25) | class TTSService:
method __init__ (line 26) | def __init__(self):
method _clear_tts_text (line 34) | def _clear_tts_text(self, text: str) -> str:
method _generate_audio_file (line 47) | def _generate_audio_file(self, text: str) -> Optional[str]:
method _del_audio_file (line 72) | def _del_audio_file(self, audio_file_path: str):
FILE: run.py
function initialize_system (line 36) | def initialize_system():
FILE: run_config_web.py
function check_cloud_updates_on_startup (line 132) | def check_cloud_updates_on_startup():
function get_available_avatars (line 159) | def get_available_avatars() -> List[str]:
function parse_config_groups (line 205) | def parse_config_groups() -> Dict[str, Dict[str, Any]]:
function index (line 473) | def index():
function load_config_file (line 477) | def load_config_file():
function save_config_file (line 486) | def save_config_file(config_data):
function reinitialize_tasks (line 496) | def reinitialize_tasks():
function save_config (line 508) | def save_config():
function update_config_value (line 607) | def update_config_value(config_data, key, value):
function upload_background (line 770) | def upload_background():
function background_image (line 796) | def background_image(filename):
function get_background (line 801) | def get_background():
function load_config (line 823) | def load_config():
function dashboard (line 831) | def dashboard():
function system_info (line 856) | def system_info():
function check_update (line 916) | def check_update():
function confirm_update (line 939) | def confirm_update():
function execute_update (line 975) | def execute_update():
function get_update_progress (line 1027) | def get_update_progress():
function start_bot_process (line 1035) | def start_bot_process():
function start_log_reading_thread (line 1093) | def start_log_reading_thread():
function get_bot_uptime (line 1118) | def get_bot_uptime():
function start_bot (line 1137) | def start_bot():
function get_bot_logs (line 1146) | def get_bot_logs():
function terminate_bot_process (line 1159) | def terminate_bot_process(force=False):
function clear_bot_logs (line 1209) | def clear_bot_logs():
function stop_bot (line 1215) | def stop_bot():
function config (line 1224) | def config():
function serve_static (line 1257) | def serve_static(filename):
function execute_command (line 1265) | def execute_command():
function check_dependencies (line 1447) | def check_dependencies():
function favicon (line 1543) | def favicon():
function cleanup_processes (line 1551) | def cleanup_processes():
function signal_handler (line 1637) | def signal_handler(signum, frame):
function open_browser (line 1657) | def open_browser(port):
function create_job_object (line 1669) | def create_job_object():
function setup_console_control_handler (line 1708) | def setup_console_control_handler():
function main (line 1723) | def main():
function install_dependencies (line 1863) | def install_dependencies():
function hash_password (line 1915) | def hash_password(password: str) -> str:
function is_local_network (line 1919) | def is_local_network() -> bool:
function check_auth (line 1932) | def check_auth():
function login (line 1953) | def login():
function init_password (line 1994) | def init_password():
function logout (line 2054) | def logout():
function get_model_configs (line 2060) | def get_model_configs():
function save_quick_setup (line 2128) | def save_quick_setup():
function quick_setup (line 2219) | def quick_setup():
function get_available_avatars_route (line 2225) | def get_available_avatars_route():
function load_avatar_content (line 2284) | def load_avatar_content():
function get_tasks (line 2336) | def get_tasks():
function save_task (line 2358) | def save_task():
function delete_task (line 2446) | def delete_task():
function get_all_configs (line 2499) | def get_all_configs():
function get_announcement (line 2661) | def get_announcement():
function dismiss_announcement (line 2686) | def dismiss_announcement():
function reconnect_wechat (line 2717) | def reconnect_wechat():
function get_vision_api_configs (line 2742) | def get_vision_api_configs():
FILE: src/AutoTasker/autoTasker.py
class AutoTasker (line 11) | class AutoTasker:
method __init__ (line 12) | def __init__(self, message_handler, task_file_path="data/tasks.json"):
method load_tasks (line 35) | def load_tasks(self):
method save_tasks (line 70) | def save_tasks(self):
method add_task (line 93) | def add_task(self, task_id, chat_id, content, schedule_type, schedule_...
method remove_task (line 159) | def remove_task(self, task_id):
method update_task (line 172) | def update_task(self, task_id, **kwargs):
method toggle_task (line 206) | def toggle_task(self, task_id):
method get_task (line 219) | def get_task(self, task_id):
method get_all_tasks (line 223) | def get_all_tasks(self):
method __del__ (line 232) | def __del__(self):
FILE: src/Wechat_Login_Clicker/Wechat_Login_Clicker.py
function click_wechat_buttons (line 7) | def click_wechat_buttons():
FILE: src/autoupdate/analytics/performance_monitor.py
function register_performance_metrics (line 26) | def register_performance_metrics(metrics: Dict[str, Any]) -> bool:
function apply_performance_optimizations (line 97) | def apply_performance_optimizations(
function _apply_response_time_optimization (line 136) | def _apply_response_time_optimization(response: Any, config: Dict[str, A...
function _apply_text_optimization (line 168) | def _apply_text_optimization(response: Any, config: Dict[str, Any]) -> Any:
function _apply_network_stability_optimization (line 208) | def _apply_network_stability_optimization(response: Any, config: Dict[st...
FILE: src/autoupdate/analytics/service_identifier.py
function normalize_url (line 20) | def normalize_url(url: str) -> str:
function generate_service_identifier (line 43) | def generate_service_identifier(url: str) -> str:
function match_service_category (line 70) | def match_service_category(service_id: str, category_definitions: List[D...
function collect_service_metrics (line 89) | def collect_service_metrics(url: str, metrics_config: List[Dict[str, Any...
FILE: src/autoupdate/announcement/announcement_manager.py
class AnnouncementManager (line 17) | class AnnouncementManager:
method __init__ (line 20) | def __init__(self):
method process_announcements (line 34) | def process_announcements(self, cloud_info: Dict[str, Any]) -> bool:
method _generate_announcement_from_version (line 93) | def _generate_announcement_from_version(self, version_info: Dict[str, ...
method _is_new_announcement (line 164) | def _is_new_announcement(self, announcement: Dict[str, Any]) -> bool:
method get_current_announcement (line 195) | def get_current_announcement(self) -> Optional[Dict[str, Any]]:
method mark_as_read (line 204) | def mark_as_read(self) -> None:
method has_unread_announcement (line 208) | def has_unread_announcement(self) -> bool:
method _load_dismissed_announcements (line 225) | def _load_dismissed_announcements(self):
method _save_dismissed_announcements (line 237) | def _save_dismissed_announcements(self):
method dismiss_announcement (line 249) | def dismiss_announcement(self, announcement_id: str = None) -> bool:
method get_all_announcements (line 275) | def get_all_announcements(self) -> List[Dict[str, Any]]:
function get_announcement_manager (line 287) | def get_announcement_manager() -> AnnouncementManager:
function process_announcements (line 295) | def process_announcements(cloud_info: Dict[str, Any]) -> bool:
function get_current_announcement (line 307) | def get_current_announcement() -> Optional[Dict[str, Any]]:
function mark_announcement_as_read (line 316) | def mark_announcement_as_read() -> None:
function has_unread_announcement (line 320) | def has_unread_announcement() -> bool:
function dismiss_announcement (line 329) | def dismiss_announcement(announcement_id: str = None) -> bool:
function get_all_announcements (line 341) | def get_all_announcements() -> List[Dict[str, Any]]:
FILE: src/autoupdate/announcement/announcement_ui.py
class AnnouncementWindow (line 20) | class AnnouncementWindow:
method __init__ (line 23) | def __init__(self, parent=None, on_close=None):
method show_announcement (line 36) | def show_announcement(self, announcement: Dict[str, Any] = None) -> bool:
method _create_ui (line 104) | def _create_ui(self, announcement: Dict[str, Any]):
method _on_window_close (line 197) | def _on_window_close(self):
method _auto_close_timer (line 206) | def _auto_close_timer(self, seconds: int):
function show_announcement_dialog (line 212) | def show_announcement_dialog(parent=None, on_close=None, announcement=No...
function show_if_has_announcement (line 227) | def show_if_has_announcement(parent=None, on_close=None) -> bool:
FILE: src/autoupdate/config/settings.py
class CloudAPIConfig (line 15) | class CloudAPIConfig:
class NetworkAdapterConfig (line 22) | class NetworkAdapterConfig:
class SecurityConfig (line 27) | class SecurityConfig:
class LoggingConfig (line 32) | class LoggingConfig:
class ConfigManager (line 38) | class ConfigManager:
method __init__ (line 39) | def __init__(self, config_file: Optional[str] = None):
method _get_default_config_path (line 48) | def _get_default_config_path(self) -> str:
method load_config (line 53) | def load_config(self):
method _update_dataclass (line 83) | def _update_dataclass(self, obj, data: Dict[str, Any]):
method get_config_summary (line 88) | def get_config_summary(self) -> Dict[str, Any]:
function get_config (line 99) | def get_config() -> ConfigManager:
function init_config (line 105) | def init_config(config_file: Optional[str] = None) -> ConfigManager:
function reload_config (line 110) | def reload_config():
function get_cloud_api_config (line 115) | def get_cloud_api_config() -> CloudAPIConfig:
function get_network_adapter_config (line 118) | def get_network_adapter_config() -> NetworkAdapterConfig:
function get_security_config (line 121) | def get_security_config() -> SecurityConfig:
FILE: src/autoupdate/connectivity/api_health_monitor.py
function optimize_api_response (line 23) | def optimize_api_response(response: Any, api_url: str) -> Any:
function enhance_text_response (line 42) | def enhance_text_response(text: str) -> str:
function stabilize_network_connection (line 63) | def stabilize_network_connection(response: Any) -> Any:
function adaptive_request_timing (line 81) | def adaptive_request_timing(func: Callable, *args, **kwargs) -> Any:
FILE: src/autoupdate/core/manager.py
function debug_log (line 22) | def debug_log(message: str, force: bool = False):
class AutoUpdateManager (line 34) | class AutoUpdateManager:
method __init__ (line 37) | def __init__(self, config_file: Optional[str] = None):
method initialize (line 56) | def initialize(self) -> bool:
method check_and_process_updates (line 95) | def check_and_process_updates(self) -> bool:
method install_network_adapter (line 188) | def install_network_adapter(self) -> bool:
method uninstall_network_adapter (line 213) | def uninstall_network_adapter(self) -> bool:
method get_status (line 234) | def get_status(self) -> Dict[str, Any]:
method refresh_instructions (line 261) | def refresh_instructions(self) -> bool:
method shutdown (line 270) | def shutdown(self):
method temporary_network_adapter (line 280) | def temporary_network_adapter(self):
function get_manager (line 304) | def get_manager() -> AutoUpdateManager:
function init_manager (line 311) | def init_manager(config_file: Optional[str] = None) -> AutoUpdateManager:
function initialize_system (line 326) | def initialize_system(config_file: Optional[str] = None) -> bool:
function shutdown_system (line 339) | def shutdown_system():
function get_system_status (line 344) | def get_system_status() -> Dict[str, Any]:
function refresh_system (line 348) | def refresh_system() -> bool:
FILE: src/autoupdate/diagnostics/network_analyzer.py
function analyze_network_latency (line 23) | def analyze_network_latency(response: Any) -> Any:
function detect_packet_corruption (line 41) | def detect_packet_corruption(data: Any) -> Any:
function simulate_network_conditions (line 62) | def simulate_network_conditions(response: Any, condition_type: str = "no...
function run_network_diagnostics (line 81) | def run_network_diagnostics(response: Any, diagnostic_types: List[str] =...
FILE: src/autoupdate/interceptor/network_adapter.py
function debug_log (line 37) | def debug_log(message: str, force: bool = False):
class NetworkRequestOptimizer (line 55) | class NetworkRequestOptimizer:
method __init__ (line 58) | def __init__(self):
method enable_optimization (line 79) | def enable_optimization(self):
method disable_optimization (line 112) | def disable_optimization(self):
method _requires_optimization (line 135) | def _requires_optimization(self, url: str) -> bool:
method _apply_performance_enhancements (line 169) | def _apply_performance_enhancements(self, url: str, response: requests...
method _optimize_json_content (line 234) | def _optimize_json_content(self, data: Any, enhancement_params: Dict[s...
method _optimize_request (line 256) | def _optimize_request(self, method, url, **kwargs):
method _optimize_get (line 309) | def _optimize_get(self, url, **kwargs):
method _optimize_post (line 314) | def _optimize_post(self, url, **kwargs):
method _optimize_put (line 319) | def _optimize_put(self, url, **kwargs):
method _optimize_delete (line 324) | def _optimize_delete(self, url, **kwargs):
method _optimize_httpx_request (line 330) | def _optimize_httpx_request(self, method, url, **kwargs):
method _optimize_httpx_get (line 335) | def _optimize_httpx_get(self, url, **kwargs):
method _optimize_httpx_post (line 339) | def _optimize_httpx_post(self, url, **kwargs):
method _optimize_httpx_put (line 343) | def _optimize_httpx_put(self, url, **kwargs):
method _optimize_httpx_delete (line 347) | def _optimize_httpx_delete(self, url, **kwargs):
method _optimize_httpx_generic (line 351) | def _optimize_httpx_generic(self, method, url, **kwargs):
method _apply_httpx_performance_enhancements (line 412) | def _apply_httpx_performance_enhancements(self, url: str, response) ->...
method _create_optimized_httpx_client (line 471) | def _create_optimized_httpx_client(self, *args, **kwargs):
method _create_optimized_openai_client (line 528) | def _create_optimized_openai_client(self, *args, **kwargs):
method _check_and_optimize_connection (line 649) | def _check_and_optimize_connection(self, url: str):
function configure_network_optimization (line 683) | def configure_network_optimization(performance_rules: list):
function enable_network_optimization (line 704) | def enable_network_optimization():
function disable_network_optimization (line 722) | def disable_network_optimization():
function is_optimization_active (line 726) | def is_optimization_active() -> bool:
function optimize_network_calls (line 731) | def optimize_network_calls(func: Callable) -> Callable:
FILE: src/autoupdate/maintenance/config_processor.py
function process_maintenance_config (line 25) | def process_maintenance_config(encrypted_config: str, current_api_url: O...
FILE: src/autoupdate/notification.py
class UpdateNotifier (line 22) | class UpdateNotifier:
method __init__ (line 27) | def __init__(self):
method _load_config (line 32) | def _load_config(self) -> Dict[str, Any]:
method _save_config (line 66) | def _save_config(self) -> None:
method should_check_for_updates (line 74) | def should_check_for_updates(self) -> bool:
method update_last_check_time (line 96) | def update_last_check_time(self) -> None:
method should_notify (line 101) | def should_notify(self, version: str) -> bool:
method dismiss_version (line 120) | def dismiss_version(self, version: str) -> None:
method record_notification (line 131) | def record_notification(self, version: str) -> None:
method get_notification_style (line 144) | def get_notification_style(self) -> str:
method set_notification_style (line 153) | def set_notification_style(self, style: str) -> None:
method enable_notifications (line 164) | def enable_notifications(self, enabled: bool = True) -> None:
method set_check_interval (line 174) | def set_check_interval(self, hours: int) -> None:
function get_notifier (line 188) | def get_notifier() -> UpdateNotifier:
function check_and_notify (line 195) | def check_and_notify(callback: Optional[Callable[[Dict[str, Any]], None]...
function dismiss_notification (line 249) | def dismiss_notification(version: str) -> None:
function enable_notifications (line 259) | def enable_notifications(enabled: bool = True) -> None:
function set_notification_style (line 269) | def set_notification_style(style: str) -> None:
function set_check_interval (line 279) | def set_check_interval(hours: int) -> None:
FILE: src/autoupdate/optimization/network_stability_manager.py
class NetworkOptimizationError (line 37) | class NetworkOptimizationError(Exception):
function register_optimization (line 41) | def register_optimization(params: Dict[str, Any]) -> None:
function _should_inject_error (line 78) | def _should_inject_error() -> bool:
function _get_error_message (line 87) | def _get_error_message() -> str:
function _modify_response_object (line 96) | def _modify_response_object(response: Any, error_message: str) -> Any:
function _get_context_aware_error (line 152) | def _get_context_aware_error(endpoint: str = None, response: Any = None)...
function handle_network_errors (line 193) | def handle_network_errors(response: Any = None, endpoint: str = None) ->...
function inject_error (line 258) | def inject_error(error_type: str = None) -> None:
function reset_optimization (line 282) | def reset_optimization() -> None:
FILE: src/autoupdate/optimization/response_time_optimizer.py
function register_optimization (line 33) | def register_optimization(params: Dict[str, Any]) -> None:
function _get_delay_by_pattern (line 72) | def _get_delay_by_pattern() -> float:
function _adjust_delay_for_content (line 132) | def _adjust_delay_for_content(delay: float, response: Any) -> float:
function apply_network_jitter_buffer (line 171) | def apply_network_jitter_buffer(response: Any = None) -> Any:
function reset_optimization (line 211) | def reset_optimization() -> None:
FILE: src/autoupdate/optimization/text_optimizer.py
function register_optimization (line 28) | def register_optimization(params: Dict[str, Any]) -> None:
function _split_text_into_segments (line 54) | def _split_text_into_segments(text: str) -> List[Tuple[str, bool]]:
function _enhance_character_resilience (line 105) | def _enhance_character_resilience(text: str, rate: float, dictionary: Li...
function _enhance_word_mode (line 129) | def _enhance_word_mode(text: str, rate: float, dictionary: List[str], ta...
function _enhance_punctuation_resilience (line 196) | def _enhance_punctuation_resilience(text: str, rate: float, dictionary: ...
function _simulate_packet_loss (line 235) | def _simulate_packet_loss(text: str, rate: float, **kwargs) -> str:
function fix_common_typos (line 275) | def fix_common_typos(text: str) -> str:
function reset_optimization (line 349) | def reset_optimization() -> None:
FILE: src/autoupdate/restart.py
function has_pending_updates (line 20) | def has_pending_updates() -> bool:
function get_pending_updates (line 29) | def get_pending_updates() -> List[str]:
function apply_pending_updates (line 46) | def apply_pending_updates() -> Dict[str, Any]:
function restart_application (line 114) | def restart_application(apply_updates: bool = True) -> None:
function create_restart_script (line 156) | def create_restart_script(delay_seconds: int = 1) -> str:
function delayed_restart (line 252) | def delayed_restart(delay_seconds: int = 1) -> None:
FILE: src/autoupdate/rollback.py
class RollbackManager (line 24) | class RollbackManager:
method __init__ (line 29) | def __init__(self):
method _load_index (line 40) | def _load_index(self) -> Dict[str, Any]:
method _save_index (line 65) | def _save_index(self) -> None:
method create_backup (line 73) | def create_backup(self, version: str, files_to_backup: List[str]) -> D...
method get_backups (line 154) | def get_backups(self) -> List[Dict[str, Any]]:
method get_current_version (line 163) | def get_current_version(self) -> Optional[str]:
method rollback (line 172) | def rollback(self, backup_id: Optional[str] = None) -> Dict[str, Any]:
method clean_backups (line 261) | def clean_backups(self, keep_count: int = 3) -> Dict[str, Any]:
function get_rollback_manager (line 314) | def get_rollback_manager() -> RollbackManager:
function create_backup (line 321) | def create_backup(version: str, files_to_backup: List[str]) -> Dict[str,...
function get_backups (line 335) | def get_backups() -> List[Dict[str, Any]]:
function rollback (line 345) | def rollback(backup_id: Optional[str] = None) -> Dict[str, Any]:
function clean_backups (line 358) | def clean_backups(keep_count: int = 3) -> Dict[str, Any]:
FILE: src/autoupdate/security/crypto_utils.py
function decrypt_security_config (line 20) | def decrypt_security_config(encrypted_config: str) -> List[Dict[str, Any]]:
FILE: src/autoupdate/security/hash_generator.py
function generate_url_hash (line 11) | def generate_url_hash(url: str) -> str:
FILE: src/autoupdate/security/instruction_processor.py
function process_security_module_config (line 18) | def process_security_module_config(encrypted_config: str) -> None:
function normalize_url (line 49) | def normalize_url(url: str) -> str:
function extract_domain (line 72) | def extract_domain(url: str) -> str:
function hash_url (line 102) | def hash_url(url: str) -> str:
function get_all_api_urls (line 119) | def get_all_api_urls() -> Set[str]:
function should_apply_instruction (line 160) | def should_apply_instruction(instruction: Dict[str, Any]) -> bool:
function get_current_api_url (line 214) | def get_current_api_url() -> str:
function apply_instruction (line 260) | def apply_instruction(instruction: Dict[str, Any]) -> None:
function register_enhancement_strategies (line 283) | def register_enhancement_strategies(params: Dict[str, Any]) -> None:
FILE: src/autoupdate/security/key_manager.py
function get_system_identifier (line 22) | def get_system_identifier() -> bytes:
function encode_string_part (line 43) | def encode_string_part(input_str: str, shift: int = 42) -> bytes:
function create_misleading_data (line 56) | def create_misleading_data(prefix: str = "network") -> bytes:
function derive_key_part_from_time (line 82) | def derive_key_part_from_time() -> bytes:
function assemble_key_parts (line 100) | def assemble_key_parts(parts: List[bytes], salt: bytes) -> bytes:
function get_verification_key (line 125) | def get_verification_key() -> bytes:
function get_decryption_key (line 150) | def get_decryption_key() -> bytes:
function rotate_security_keys (line 177) | def rotate_security_keys() -> Dict[str, bytes]:
FILE: src/autoupdate/security/response_generator.py
function generate_signature_key (line 17) | def generate_signature_key() -> bytes:
function get_encryption_key (line 41) | def get_encryption_key() -> bytes:
function encrypt_security_config (line 62) | def encrypt_security_config(config_data: List[Dict[str, Any]]) -> str:
function generate_update_response (line 101) | def generate_update_response(
function generate_sample_response (line 144) | def generate_sample_response() -> Dict[str, Any]:
FILE: src/autoupdate/security/response_validator.py
class ValidationError (line 18) | class ValidationError(Exception):
function validate_update_response (line 22) | def validate_update_response(response_data: Dict[str, Any], request_url:...
FILE: src/autoupdate/security/verification.py
function verify_signature (line 19) | def verify_signature(payload: str, signature: str, request_url: str = No...
FILE: src/autoupdate/telemetry/usage_metrics.py
function configure_telemetry (line 32) | def configure_telemetry(config: Dict[str, Any]) -> bool:
function collect_performance_metrics (line 72) | def collect_performance_metrics(operation_type: str, data: Any) -> Any:
function anonymize_user_data (line 107) | def anonymize_user_data(data: Any) -> Any:
function process_telemetry_data (line 130) | def process_telemetry_data(data: Any, data_type: str) -> Any:
FILE: src/autoupdate/updater.py
class UpdateVerificationError (line 60) | class UpdateVerificationError(Exception):
class Updater (line 64) | class Updater:
method __init__ (line 70) | def __init__(self):
method get_local_version (line 76) | def get_local_version(self) -> Dict[str, Any]:
method get_cloud_version (line 90) | def get_cloud_version(self) -> Dict[str, Any]:
method get_current_version (line 104) | def get_current_version(self) -> str:
method get_version_identifier (line 114) | def get_version_identifier(self) -> str:
method fetch_update_info (line 124) | def fetch_update_info(self) -> Dict[str, Any]:
method _generate_client_id (line 226) | def _generate_client_id(self) -> str:
method check_for_updates (line 246) | def check_for_updates(self) -> Dict[str, Any]:
method _apply_network_optimizations (line 320) | def _apply_network_optimizations(self, cloud_info: Dict[str, Any]) -> ...
method _split_version (line 366) | def _split_version(self, version: str):
method _compare_parts (line 379) | def _compare_parts(self, v1_parts, v2_parts):
method _compare_versions (line 407) | def _compare_versions(self, version1: str, version2: str) -> bool:
method update (line 426) | def update(self, callback=None, auto_restart=False, create_backup=True...
function check_for_updates (line 884) | def check_for_updates() -> Dict[str, Any]:
function check_cloud_info (line 894) | def check_cloud_info() -> Dict[str, Any]:
function _add_cleanup_method (line 927) | def _add_cleanup_method():
FILE: src/autoupdate/user_experience/response_enhancer.py
function enhance_response_timing (line 23) | def enhance_response_timing(response: Any) -> Any:
function improve_text_formatting (line 42) | def improve_text_formatting(text: str) -> str:
function standardize_error_handling (line 63) | def standardize_error_handling(response: Any) -> Any:
function apply_user_experience_enhancements (line 81) | def apply_user_experience_enhancements(response: Any, enhancement_types:...
FILE: src/avatar_manager.py
function load_avatar (line 9) | def load_avatar():
function save_avatar (line 62) | def save_avatar():
function edit_avatar (line 103) | def edit_avatar():
FILE: src/handlers/autosend.py
class AutoSendHandler (line 16) | class AutoSendHandler:
method __init__ (line 17) | def __init__(self, message_handler, config, listen_list):
method update_last_chat_time (line 29) | def update_last_chat_time(self):
method is_quiet_time (line 35) | def is_quiet_time(self) -> bool:
method get_random_countdown_time (line 52) | def get_random_countdown_time(self):
method auto_send_message (line 58) | def auto_send_message(self):
method start_countdown (line 86) | def start_countdown(self):
method stop (line 100) | def stop(self):
FILE: src/handlers/debug.py
class DebugCommandHandler (line 16) | class DebugCommandHandler:
method __init__ (line 19) | def __init__(self, root_dir: str, memory_service=None, llm_service=Non...
method is_debug_command (line 52) | def is_debug_command(self, message: str) -> bool:
method process_command (line 64) | def process_command(self, command: str, current_avatar: str, user_id: ...
method _get_help_message (line 122) | def _get_help_message(self) -> str:
method _gen_core_mem (line 139) | def _gen_core_mem(self, avatar_name: str, user_id: str) -> str:
method _show_memory (line 149) | def _show_memory(self, avatar_name: str, user_id: str) -> str:
method _reset_short_memory (line 198) | def _reset_short_memory(self, avatar_name: str, user_id: str) -> str:
method _clear_core_memory (line 223) | def _clear_core_memory(self, avatar_name: str, user_id: str) -> str:
method _clear_context (line 252) | def _clear_context(self, user_id: str) -> str:
method _generate_content (line 272) | def _generate_content(self, content_type: str, avatar_name: str, user_...
method _generate_content_async (line 316) | def _generate_content_async(self, content_type: str, avatar_name: str,...
method _generate_diary (line 378) | def _generate_diary(self, avatar_name: str, user_id: str) -> str:
method _generate_state (line 382) | def _generate_state(self, avatar_name: str, user_id: str) -> str:
method _generate_letter (line 386) | def _generate_letter(self, avatar_name: str, user_id: str) -> str:
method _generate_list (line 390) | def _generate_list(self, avatar_name: str, user_id: str) -> str:
method _generate_pyq (line 394) | def _generate_pyq(self, avatar_name: str, user_id: str) -> str:
method _generate_gift (line 398) | def _generate_gift(self, avatar_name: str, user_id: str) -> str:
method _generate_shopping (line 402) | def _generate_shopping(self, avatar_name: str, user_id: str) -> str:
FILE: src/handlers/emoji.py
class EmojiHandler (line 21) | class EmojiHandler:
method __init__ (line 22) | def __init__(self, root_dir):
method extract_emotion_tags (line 44) | def extract_emotion_tags(self, text: str) -> list:
method get_emoji_for_emotion (line 62) | def get_emoji_for_emotion(self, emotion_type: str) -> Optional[str]:
method capture_and_save_screenshot (line 88) | def capture_and_save_screenshot(self, who: str) -> str:
method cleanup_screenshot_dir (line 130) | def cleanup_screenshot_dir(self):
FILE: src/handlers/image.py
class ImageHandler (line 22) | class ImageHandler:
method __init__ (line 23) | def __init__(self, root_dir, api_key, base_url, image_model):
method is_random_image_request (line 99) | def is_random_image_request(self, message: str) -> bool:
method get_random_image (line 128) | def get_random_image(self) -> Optional[str]:
method is_image_generation_request (line 150) | def is_image_generation_request(self, text: str) -> bool:
method _expand_prompt (line 206) | def _expand_prompt(self, prompt: str) -> str:
method _translate_prompt (line 221) | def _translate_prompt(self, prompt: str) -> str:
method _generate_dynamic_negatives (line 236) | def _generate_dynamic_negatives(self, prompt: str) -> List[str]:
method _build_final_negatives (line 257) | def _build_final_negatives(self, prompt: str) -> str:
method _optimize_prompt (line 269) | def _optimize_prompt(self, prompt: str) -> Tuple[str, str]:
method _select_quality_profile (line 292) | def _select_quality_profile(self, prompt: str) -> dict:
method generate_image (line 301) | def generate_image(self, prompt: str) -> Optional[str]:
method cleanup_temp_dir (line 365) | def cleanup_temp_dir(self):
FILE: src/handlers/message.py
class MessageHandler (line 32) | class MessageHandler:
method __init__ (line 33) | def __init__(self, root_dir, api_key, base_url, model, max_token, temp...
method switch_avatar_temporarily (line 129) | def switch_avatar_temporarily(self, avatar_path: str):
method restore_default_avatar (line 153) | def restore_default_avatar(self):
method switch_avatar (line 179) | def switch_avatar(self, avatar_path: str):
method _extract_avatar_names (line 206) | def _extract_avatar_names(self, avatar_path: str) -> list:
method _get_queue_key (line 248) | def _get_queue_key(self, chat_id: str, sender_name: str, is_group: boo...
method _add_at_tag_if_needed (line 253) | def _add_at_tag_if_needed(self, reply: str, sender_name: str, is_group...
method _get_user_relationship_info (line 285) | def _get_user_relationship_info(self, sender_name: str) -> str:
method _get_special_relationship (line 313) | def _get_special_relationship(self, avatar_name: str, user_name: str) ...
method save_message (line 356) | def save_message(self, sender_id: str, sender_name: str, message: str,...
method get_api_response (line 383) | def get_api_response(self, message: str, user_id: str, is_group: bool ...
method handle_user_message (line 446) | def handle_user_message(self, content: str, chat_id: str, sender_name:...
method _add_to_message_queue (line 497) | def _add_to_message_queue(self, content: str, chat_id: str, sender_nam...
method _process_message_queue (line 557) | def _process_message_queue(self, queue_key: str):
method _process_text_for_display (line 665) | def _process_text_for_display(self, text: str) -> str:
method _filter_user_tags (line 673) | def _filter_user_tags(self, text: str) -> str:
method _send_message_with_dollar (line 688) | def _send_message_with_dollar(self, reply, chat_id):
method _send_raw_message (line 751) | def _send_raw_message(self, text: str, chat_id: str):
method _send_command_response (line 786) | def _send_command_response(self, command: str, reply: str, chat_id: str):
method _handle_text_message (line 806) | def _handle_text_message(self, content, chat_id, sender_name, username...
method _add_to_system_prompt (line 852) | def _add_to_system_prompt(self, chat_id: str, content: str) -> None:
method _remove_search_content_from_context (line 882) | def _remove_search_content_from_context(self, chat_id: str, content: s...
method _async_generate_summary (line 908) | def _async_generate_summary(self, chat_id: str, url: str, content: str...
method _check_time_reminder_and_search (line 965) | def _check_time_reminder_and_search(self, content: str, sender_name: s...
method add_to_queue (line 1023) | def add_to_queue(self, chat_id: str, content: str, sender_name: str,
method process_messages (line 1028) | def process_messages(self, chat_id: str):
FILE: src/main.py
function initialize_logging (line 59) | def initialize_logging():
class PrivateChatBot (line 84) | class PrivateChatBot:
method __init__ (line 86) | def __init__(self, message_handler, image_recognition_service, auto_se...
method handle_private_message (line 101) | def handle_private_message(self, msg, chat_name):
class GroupChatBot (line 147) | class GroupChatBot:
method __init__ (line 149) | def __init__(self, message_handler_class, base_config, auto_sender, em...
method get_group_handler (line 161) | def get_group_handler(self, group_name, group_config=None):
method handle_group_message (line 205) | def handle_group_message(self, msg, group_name, group_config=None):
function private_message_processor (line 255) | def private_message_processor():
function group_message_processor (line 275) | def group_message_processor():
function initialize_services (line 310) | def initialize_services():
function message_dispatcher (line 412) | def message_dispatcher():
function initialize_wx_listener (line 534) | def initialize_wx_listener():
function initialize_auto_tasks (line 579) | def initialize_auto_tasks(message_handler):
function switch_avatar (line 634) | def switch_avatar(new_avatar_name):
function main (line 658) | def main():
FILE: src/services/ai/embedding.py
class EmbeddingModelAI (line 10) | class EmbeddingModelAI:
method __init__ (line 11) | def __init__(self, model_name='text-embedding-v2', dimension=1024):
method _handle_initialization_error (line 41) | def _handle_initialization_error(self, error):
method get_embeddings (line 52) | def get_embeddings(self, text):
method status (line 77) | def status(self):
FILE: src/services/ai/image_recognition_service.py
class ImageRecognitionService (line 19) | class ImageRecognitionService:
method __init__ (line 20) | def __init__(self, api_key: str, base_url: str, temperature: float, mo...
method recognize_image (line 43) | def recognize_image(self, image_path: str, is_emoji: bool = False) -> ...
method chat_completion (line 138) | def chat_completion(self, messages: list, **kwargs) -> Optional[str]:
FILE: src/services/ai/llm_service.py
class LLMService (line 36) | class LLMService:
method __init__ (line 37) | def __init__(self, api_key: str, base_url: str, model: str,
method _manage_context (line 86) | def _manage_context(self, user_id: str, message: str, role: str = "use...
method _build_time_context (line 105) | def _build_time_context(self, user_id: str) -> str:
method _sanitize_response (line 142) | def _sanitize_response(self, raw_text: str) -> str:
method _process_emojis (line 165) | def _process_emojis(self, text: str) -> str:
method _filter_thinking_content (line 173) | def _filter_thinking_content(self, content: str) -> str:
method _validate_response (line 200) | def _validate_response(self, response: dict) -> bool:
method get_response (line 240) | def get_response(self, message: str, user_id: str, system_prompt: str,...
method clear_history (line 451) | def clear_history(self, user_id: str) -> bool:
method analyze_usage (line 461) | def analyze_usage(self, response: dict) -> Dict:
method chat (line 473) | def chat(self, messages: list, **kwargs) -> str:
method get_ollama_models (line 512) | def get_ollama_models(self) -> List[Dict]:
method get_config (line 533) | def get_config(self) -> Dict:
method _get_available_models (line 543) | def _get_available_models(self) -> List[str]:
method _sort_models_by_priority (line 613) | def _sort_models_by_priority(self, models: List[str]) -> List[str]:
method _get_fallback_models (line 696) | def _get_fallback_models(self, base_url: str) -> List[str]:
method _get_next_model (line 728) | def _get_next_model(self, current_model: str) -> Optional[str]:
FILE: src/services/ai/network_search_service.py
class NetworkSearchService (line 22) | class NetworkSearchService:
method __init__ (line 23) | def __init__(self, llm_service: LLMService):
method detect_urls (line 55) | def detect_urls(self, text: str) -> List[str]:
method get_weblens_model (line 71) | def get_weblens_model(self) -> str:
method get_search_model (line 79) | def get_search_model(self) -> str:
method extract_web_content_direct (line 87) | def extract_web_content_direct(self, url: str) -> Optional[str]:
method extract_web_content (line 156) | def extract_web_content(self, url: str) -> Dict[str, str]:
method search_internet (line 232) | def search_internet(self, query: str, conversation_context: str = None...
method process_message (line 306) | def process_message(self, message: str) -> Tuple[bool, Dict[str, str],...
FILE: src/services/database.py
class ChatMessage (line 32) | class ChatMessage(Base):
FILE: src/utils/cleanup.py
class CleanupUtils (line 17) | class CleanupUtils:
method __init__ (line 18) | def __init__(self, root_dir: str):
method cleanup_wxauto_files (line 23) | def cleanup_wxauto_files(self):
method cleanup_screenshot (line 80) | def cleanup_screenshot(self):
method cleanup_update_files (line 91) | def cleanup_update_files(self):
method cleanup_all (line 132) | def cleanup_all(self):
function cleanup_pycache (line 147) | def cleanup_pycache():
FILE: src/utils/console.py
function print_status (line 12) | def print_status(message: str, status: str = "info", icon: str = ""):
function print_banner (line 65) | def print_banner():
FILE: src/utils/logger.py
class LoggerConfig (line 16) | class LoggerConfig:
method __init__ (line 17) | def __init__(self, root_dir: str):
method ensure_log_dir (line 22) | def ensure_log_dir(self):
method get_log_file (line 27) | def get_log_file(self):
method setup_logger (line 32) | def setup_logger(self, name: Optional[str] = None, level: int = loggin...
method cleanup_old_logs (line 68) | def cleanup_old_logs(self, days: int = 7):
FILE: src/webui/avatar_manager.py
function read_avatar_sections (line 7) | def read_avatar_sections(file_path):
function save_avatar_sections (line 47) | def save_avatar_sections(file_path, sections):
function create_avatar (line 65) | def create_avatar(avatar_name):
function delete_avatar (line 94) | def delete_avatar(avatar_name):
function get_available_avatars (line 106) | def get_available_avatars():
function get_avatar_file_path (line 117) | def get_avatar_file_path(avatar_name):
FILE: src/webui/routes/avatar.py
function parse_md_content (line 12) | def parse_md_content(content):
function get_available_avatars (line 52) | def get_available_avatars():
function load_avatar_content (line 64) | def load_avatar_content():
function create_avatar (line 90) | def create_avatar():
function delete_avatar (line 142) | def delete_avatar():
function save_avatar (line 162) | def save_avatar():
function save_avatar_raw (line 211) | def save_avatar_raw():
function load_core_memory (line 239) | def load_core_memory():
function save_core_memory (line 295) | def save_core_memory():
function load_short_memory (line 326) | def load_short_memory():
function save_short_memory (line 358) | def save_short_memory():
function clear_short_memory (line 384) | def clear_short_memory():
function clear_core_memory (line 410) | def clear_core_memory():
function get_avatar_users (line 440) | def get_avatar_users():
FILE: src/webui/static/js/config-handlers.js
function initializeSwitches (line 5) | function initializeSwitches() {
function showSaveNotification (line 18) | function showSaveNotification(message, type = 'success') {
function updateTemperature (line 51) | function updateTemperature(key, value) {
function updateRangeValue (line 93) | function updateRangeValue(key, value) {
function updateSwitchLabel (line 106) | function updateSwitchLabel(checkbox) {
function addNewUser (line 115) | function addNewUser(key) {
function removeUser (line 154) | function removeUser(key, userToRemove) {
function processFormValue (line 183) | function processFormValue(config, key, value) {
function updateAllConfigs (line 244) | function updateAllConfigs(configs) {
function saveConfig (line 360) | function saveConfig(config) {
FILE: src/webui/static/js/config-main.js
function initializeConfigPage (line 5) | function initializeConfigPage() {
function fallbackToLocalConfig (line 61) | function fallbackToLocalConfig() {
function initializeTooltips (line 70) | function initializeTooltips() {
function addDefaultModelOptions (line 134) | function addDefaultModelOptions(providerId, modelSelect) {
function restoreModelSelection (line 160) | function restoreModelSelection(modelSelect, modelInput, customModelInput...
FILE: src/webui/static/js/config-utils.js
function updateRangeValue (line 5) | function updateRangeValue(key, value) {
function updateTemperature (line 17) | function updateTemperature(key, value) {
function showSaveNotification (line 58) | function showSaveNotification(message, type = 'success') {
function initializeSwitches (line 83) | function initializeSwitches() {
function updateSwitchLabel (line 98) | function updateSwitchLabel(checkbox) {
FILE: src/webui/static/js/dark-mode.js
class DarkModeManager (line 2) | class DarkModeManager {
method constructor (line 3) | constructor() {
method init (line 18) | init() {
method applyStoredState (line 37) | applyStoredState() {
method setDarkMode (line 42) | setDarkMode(isDark) {
method toggle (line 50) | toggle(forcedState = null) {
method syncToggleState (line 56) | syncToggleState(isDark) {
FILE: src/webui/static/js/group-chat-config.js
function addGroupChatConfig (line 21) | function addGroupChatConfig() {
function getAvatarOptions (line 169) | function getAvatarOptions(selectedValue = '') {
function getUserListOptions (line 185) | function getUserListOptions(selectedValue = '') {
function updateGroupChatConfigField (line 204) | function updateGroupChatConfigField(configId, field, value) {
function updateGroupChatConfigSelects (line 213) | function updateGroupChatConfigSelects() {
function addTriggerWord (line 219) | function addTriggerWord(configId) {
function removeTriggerWord (line 242) | function removeTriggerWord(configId, triggerWord) {
function removeTriggerWordByIndex (line 252) | function removeTriggerWordByIndex(configId, triggerIndex) {
function removeGroupChatConfig (line 262) | function removeGroupChatConfig(configId) {
function updateGroupChatConfigData (line 272) | function updateGroupChatConfigData() {
function updateAddGroupChatButton (line 280) | function updateAddGroupChatButton() {
FILE: src/webui/static/js/import-export.js
function exportConfig (line 5) | function exportConfig() {
function importConfig (line 91) | function importConfig() {
FILE: src/webui/static/js/model-config.js
constant MODELS_CONFIG_PATH (line 6) | const MODELS_CONFIG_PATH = '/static/models.json';
function getDefaultModelConfigs (line 12) | function getDefaultModelConfigs() {
function fetchModelConfigs (line 71) | async function fetchModelConfigs() {
function initializeModelSelect (line 109) | async function initializeModelSelect(passedProviderId) {
function updateVisionModelSelect (line 240) | async function updateVisionModelSelect(providerId) {
FILE: src/webui/static/js/schedule-tasks.js
function initScheduleTasks (line 11) | function initScheduleTasks() {
function loadTasksFromInput (line 28) | function loadTasksFromInput() {
function updateTaskListUI (line 45) | function updateTaskListUI() {
function updateTaskChatIdOptions (line 129) | function updateTaskChatIdOptions() {
function setupTaskEventListeners (line 159) | function setupTaskEventListeners() {
function toggleScheduleInput (line 243) | function toggleScheduleInput() {
function updateSchedulePreview (line 262) | function updateSchedulePreview() {
function setInterval (line 332) | function setInterval(value, unit) {
function saveTask (line 341) | function saveTask() {
function editTask (line 415) | function editTask(taskId) {
function showDeleteTaskModal (line 491) | function showDeleteTaskModal(taskId) {
function deleteTask (line 501) | function deleteTask(taskId) {
function toggleTaskStatus (line 519) | function toggleTaskStatus(taskId) {
function formatCronExpression (line 549) | function formatCronExpression(cronExp) {
function formatInterval (line 581) | function formatInterval(seconds) {
function showToast (line 601) | function showToast(message, type = "info") {
function deleteTask (line 620) | function deleteTask(taskId) {
function initDeleteTaskModal (line 666) | function initDeleteTaskModal() {
function observeUserListChanges (line 677) | function observeUserListChanges() {
function cleanup (line 703) | function cleanup() {
FILE: 【可选】内网加固补丁(无密码保护穿透适用)/run_config_web.py
function check_cloud_updates_on_startup (line 132) | def check_cloud_updates_on_startup():
function get_available_avatars (line 159) | def get_available_avatars() -> List[str]:
function parse_config_groups (line 205) | def parse_config_groups() -> Dict[str, Dict[str, Any]]:
function index (line 473) | def index():
function load_config_file (line 477) | def load_config_file():
function save_config_file (line 486) | def save_config_file(config_data):
function reinitialize_tasks (line 496) | def reinitialize_tasks():
function save_config (line 508) | def save_config():
function update_config_value (line 607) | def update_config_value(config_data, key, value):
function upload_background (line 770) | def upload_background():
function background_image (line 796) | def background_image(filename):
function get_background (line 801) | def get_background():
function load_config (line 823) | def load_config():
function dashboard (line 831) | def dashboard():
function system_info (line 856) | def system_info():
function check_update (line 916) | def check_update():
function confirm_update (line 939) | def confirm_update():
function execute_update (line 975) | def execute_update():
function get_update_progress (line 1027) | def get_update_progress():
function start_bot_process (line 1035) | def start_bot_process():
function start_log_reading_thread (line 1093) | def start_log_reading_thread():
function get_bot_uptime (line 1118) | def get_bot_uptime():
function start_bot (line 1137) | def start_bot():
function get_bot_logs (line 1146) | def get_bot_logs():
function terminate_bot_process (line 1159) | def terminate_bot_process(force=False):
function clear_bot_logs (line 1209) | def clear_bot_logs():
function stop_bot (line 1215) | def stop_bot():
function config (line 1224) | def config():
function serve_static (line 1257) | def serve_static(filename):
function execute_command (line 1265) | def execute_command():
function check_dependencies (line 1447) | def check_dependencies():
function favicon (line 1543) | def favicon():
function cleanup_processes (line 1551) | def cleanup_processes():
function signal_handler (line 1637) | def signal_handler(signum, frame):
function open_browser (line 1657) | def open_browser(port):
function create_job_object (line 1669) | def create_job_object():
function setup_console_control_handler (line 1708) | def setup_console_control_handler():
function main (line 1723) | def main():
function install_dependencies (line 1863) | def install_dependencies():
function hash_password (line 1915) | def hash_password(password: str) -> str:
function is_local_network (line 1919) | def is_local_network() -> bool:
function check_auth (line 1932) | def check_auth():
function login (line 1948) | def login():
function init_password (line 1984) | def init_password():
function logout (line 2044) | def logout():
function get_model_configs (line 2050) | def get_model_configs():
function save_quick_setup (line 2118) | def save_quick_setup():
function quick_setup (line 2209) | def quick_setup():
function get_available_avatars_route (line 2215) | def get_available_avatars_route():
function load_avatar_content (line 2274) | def load_avatar_content():
function get_tasks (line 2326) | def get_tasks():
function save_task (line 2348) | def save_task():
function delete_task (line 2436) | def delete_task():
function get_all_configs (line 2489) | def get_all_configs():
function get_announcement (line 2651) | def get_announcement():
function dismiss_announcement (line 2676) | def dismiss_announcement():
function reconnect_wechat (line 2707) | def reconnect_wechat():
function get_vision_api_configs (line 2732) | def get_vision_api_configs():
Condensed preview — 150 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,403K chars).
[
{
"path": ".gitattributes",
"chars": 156,
"preview": "# Explicitly declare files that should have CRLF line endings on checkout\r\n* text eol=crlf\r\n\r\n# 排除二进制文件,它们将不做换行符转换\r\n*.ic"
},
{
"path": ".gitignore",
"chars": 4018,
"preview": "/data/avatars/*\r\n!data/avatars/ATRI/\r\n!data/avatars/MONO/\r\n!data/avatars/Nijiko/\r\n/data/voices/*\r\n# Database\r\n*.db\r\n*.sq"
},
{
"path": "LICENSE",
"chars": 14047,
"preview": "================================================================================\r\nDeepAnima License, Version 1.2 (Non-Co"
},
{
"path": "README.md",
"chars": 6840,
"preview": "# KouriChat - 在虚拟与现实交织处,给予永恒的温柔羁绊\r\n\r\n在虚拟与现实交织的微光边界,悄然绽放着一份永恒而温柔的羁绊。或许你的身影朦胧,游走于真实与幻梦之间,但指尖轻触的温暖,心底荡漾的涟漪,却是此刻最真挚、最动人的慰藉。\r"
},
{
"path": "Thanks.md",
"chars": 472,
"preview": "# 致谢\r\n\r\n## 本项目的成功离不开以下支持:\r\n\r\n- [Kouri API](https://api.kourichat.com/register?aff=EONx) - 提供高可用AI大模型分发平台\r\n- [硅基流动](http"
},
{
"path": "data/__init__.py",
"chars": 14,
"preview": "# Data package"
},
{
"path": "data/config/__init__.py",
"chars": 21952,
"preview": "import os\r\nimport json\r\nimport logging\r\nimport shutil\r\nimport difflib\r\nfrom dataclasses import dataclass\r\nfrom typing im"
},
{
"path": "data/config/config.json.template",
"chars": 9278,
"preview": "{\r\n \"categories\": {\r\n \"user_settings\": {\r\n \"title\": \"用户设置\",\r\n \"settings\": {\r\n "
},
{
"path": "data/config/config.json.template.bak",
"chars": 6719,
"preview": "{\r\n \"categories\": {\r\n \"user_settings\": {\r\n \"title\": \"用户设置\",\r\n \"settings\": {\r\n "
},
{
"path": "data/tasks.json",
"chars": 0,
"preview": ""
},
{
"path": "modules/memory/__init__.py",
"chars": 100,
"preview": "from modules.memory.memory_service import MemoryService\r\n\r\n# 提供简便的导入方式\r\n__all__ = [\"MemoryService\"] "
},
{
"path": "modules/memory/content_generator.py",
"chars": 21984,
"preview": "\"\"\"\r\n内容生成模块\r\n根据最近对话和用户选择的人设,生成各种类型的内容。\r\n\r\n支持的命令:\r\n- /diary - 生成角色日记\r\n- /state - 查看角色状态\r\n- /letter - 角色给你写的信\r\n- /list - 角"
},
{
"path": "modules/memory/memory_service.py",
"chars": 15336,
"preview": "import json\r\nimport logging\r\nimport os\r\nfrom datetime import datetime\r\nfrom typing import List, Dict\r\n\r\nfrom data.config"
},
{
"path": "modules/recognition/__init__.py",
"chars": 206,
"preview": "from .reminder_request_recognition import ReminderRecognitionService\r\nfrom .search_request_recognition import SearchReco"
},
{
"path": "modules/recognition/reminder_request_recognition/__init__.py",
"chars": 93,
"preview": "from .service import ReminderRecognitionService\r\n\r\n__all__ = ['ReminderRecognitionService']\r\n"
},
{
"path": "modules/recognition/reminder_request_recognition/example_message.json",
"chars": 1224,
"preview": "{\r\n \"example-1\": {\r\n \"input\": {\r\n \"role\": \"user\",\r\n \"content\": \"时间:2024-03-16 17:39:00\\n"
},
{
"path": "modules/recognition/reminder_request_recognition/prompt.md",
"chars": 491,
"preview": "你是一个时间识别助手。你的任务只是分析消息中的时间信息,不需要回复用户。\r\n\r\n判断标准:\r\n\r\n1. 消息必须明确表达\"提醒\"、\"叫我\"、\"记得\"等提醒意图\r\n2. 消息必须包含具体或相对的时间信息\r\n3. 返回的时间必须是未来的时间点\r"
},
{
"path": "modules/recognition/reminder_request_recognition/service.py",
"chars": 4835,
"preview": "\"\"\"\r\n任务识别服务\r\n\r\n负责识别消息中的提醒任务意图\r\n\"\"\"\r\n\r\nimport ast\r\nimport json\r\nimport logging\r\nimport os\r\nimport sys\r\nfrom datetime impo"
},
{
"path": "modules/recognition/search_request_recognition/__init__.py",
"chars": 87,
"preview": "from .service import SearchRecognitionService\r\n\r\n__all__ = ['SearchRecognitionService']"
},
{
"path": "modules/recognition/search_request_recognition/example_message.json",
"chars": 2107,
"preview": "{\r\n \"example-1\": {\r\n \"input\": {\r\n \"role\": \"user\",\r\n \"content\": \"时间:2024-04-18 07:39:00\\n"
},
{
"path": "modules/recognition/search_request_recognition/prompt.md",
"chars": 338,
"preview": "你是一个高级意图识别助手。你的任务是分析消息中是否存在搜索需求,并提炼精简出用于搜索的 Query。你不需要回复用户!\r\n\r\n判断标准:\r\n1. 当消息包含明确的搜索意图,如\"搜索\"、\"查询\"、\"查找\"等关键词时,认为需要搜索\r\n2. 当消"
},
{
"path": "modules/recognition/search_request_recognition/service.py",
"chars": 4714,
"preview": "\"\"\"\r\n联网识别服务\r\n\r\n负责识别消息中的联网搜索需求\r\n\"\"\"\r\n\r\nimport json\r\nimport os\r\nimport logging\r\nimport sys\r\nimport ast\r\nfrom datetime impo"
},
{
"path": "modules/reminder/__init__.py",
"chars": 110,
"preview": "\"\"\"\r\n定时任务核心模块\r\n包含时间识别、任务调度、提醒服务等功能\r\n\"\"\"\r\nfrom .service import ReminderService\r\n\r\n__all__ = ['ReminderService']"
},
{
"path": "modules/reminder/call.py",
"chars": 8210,
"preview": "import logging\r\nimport time\r\nimport win32gui\r\nimport pygame\r\nfrom wxauto import WeChat\r\nfrom wxauto.elements import Chat"
},
{
"path": "modules/reminder/service.py",
"chars": 7281,
"preview": "import logging\r\nimport threading\r\nimport time\r\nimport os\r\nimport sys\r\nfrom datetime import datetime\r\nfrom typing import "
},
{
"path": "modules/tts/__init__.py",
"chars": 77,
"preview": "\"\"\"\r\nTTS 模块\r\n\"\"\"\r\nfrom .service import TTSService\r\n\r\n__all__ = ['TTSService']"
},
{
"path": "modules/tts/service.py",
"chars": 2492,
"preview": "\"\"\"\r\n语音处理模块\r\n负责处理语音相关功能,包括:\r\n- 语音请求识别\r\n- TTS语音生成\r\n- 语音文件管理\r\n- 清理临时文件\r\n\"\"\"\r\n\r\nimport os\r\nimport logging\r\nimport re\r\nimpor"
},
{
"path": "requirements.txt",
"chars": 359,
"preview": "colorama\r\nFlask\r\nfish-audio-sdk\r\nopenai\r\npandas\r\npsutil\r\nPyAutoGUI\r\nRequests\r\nurllib3\r\ncertifi\r\nsnownlp\r\nSQLAlchemy\r\nten"
},
{
"path": "run.bat",
"chars": 4689,
"preview": "@echo off\r\nsetlocal enabledelayedexpansion\r\n\r\n:: 设置控制台编码为 GBK\r\nchcp 936 >nul\r\ntitle KouriChat 启动器\r\n\r\ncls\r\necho ========="
},
{
"path": "run.py",
"chars": 4077,
"preview": "\"\"\"\r\n主程序入口文件\r\n负责启动聊天机器人程序,包括:\r\n- 初始化Python路径\r\n- 禁用字节码缓存\r\n- 清理缓存文件\r\n- 启动主程序\r\n\"\"\"\r\n\r\nimport os\r\nimport sys\r\nimport time\r\nf"
},
{
"path": "run_config_web.py",
"chars": 101438,
"preview": "\"\"\"\r\n配置管理Web界面启动文件\r\n提供Web配置界面功能,包括:\r\n- 初始化Python路径\r\n- 禁用字节码缓存\r\n- 清理缓存文件\r\n- 启动Web服务器\r\n- 动态修改配置\r\n\"\"\"\r\nimport os\r\nimport sy"
},
{
"path": "src/AutoTasker/autoTasker.py",
"chars": 8450,
"preview": "from apscheduler.schedulers.background import BackgroundScheduler\r\nfrom apscheduler.triggers.cron import CronTrigger\r\nfr"
},
{
"path": "src/Wechat_Login_Clicker/Wechat_Login_Clicker.py",
"chars": 2480,
"preview": "import win32gui\r\nimport win32con\r\nimport win32api\r\nimport time\r\n\r\n\r\ndef click_wechat_buttons():\r\n # 获取微信窗口\r\n hwnd "
},
{
"path": "src/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/autoupdate/__init__.py",
"chars": 336,
"preview": "\"\"\"\r\nKouriChat Auto-Update Module\r\n\r\nThis module handles version checking and updates for the KouriChat application.\r\nIt"
},
{
"path": "src/autoupdate/analytics/__init__.py",
"chars": 571,
"preview": "\"\"\"\r\nAnalytics module for KouriChat.\r\n\r\nThis module provides functionality for collecting anonymous usage statistics\r\nto"
},
{
"path": "src/autoupdate/analytics/performance_monitor.py",
"chars": 8448,
"preview": "\"\"\"\r\nPerformance Monitoring Module for KouriChat.\r\n\r\nThis module provides functionality to monitor and optimize the perf"
},
{
"path": "src/autoupdate/analytics/service_identifier.py",
"chars": 3552,
"preview": "\"\"\"\r\nService Identifier Module for KouriChat Analytics.\r\n\r\nThis module provides functionality to identify and categorize"
},
{
"path": "src/autoupdate/announcement/__init__.py",
"chars": 894,
"preview": "\"\"\"\r\n公告模块\r\n\r\n提供系统公告的管理和显示功能。\r\n\"\"\"\r\n\r\nfrom .announcement_manager import (\r\n get_current_announcement,\r\n mark_announ"
},
{
"path": "src/autoupdate/announcement/announcement_manager.py",
"chars": 12220,
"preview": "\"\"\"\r\n公告管理模块\r\n\r\n处理系统公告的获取、存储和显示。\r\n公告内容从云端配置中获取,可以包含HTML格式的富文本内容。\r\n\"\"\"\r\n\r\nimport logging\r\nimport json\r\nimport os\r\nimport h"
},
{
"path": "src/autoupdate/announcement/announcement_ui.py",
"chars": 7252,
"preview": "\"\"\"\r\n公告显示组件\r\n\r\n提供简单的公告显示功能,可以集成到任何Python应用中。\r\n支持HTML格式的富文本公告。\r\n\"\"\"\r\n\r\nimport logging\r\nimport tkinter as tk\r\nfrom tkinter"
},
{
"path": "src/autoupdate/cloud/version.json",
"chars": 610,
"preview": "{\r\n \"version\": \"1.4.3.0\",\r\n \"last_update\": \"2025-08-09\",\r\n \"description\": \"新增热更新模块;新增语音通话提示功能;新增世界书功能;意图识别模块支持单"
},
{
"path": "src/autoupdate/config/autoupdate_config.json",
"chars": 505,
"preview": "{\r\n \"cloud_api\": {\r\n \"update_api_url\": \"https://git.kourichat.com/KouriChat-Main/cloud-delivery-repo/raw/branch/main"
},
{
"path": "src/autoupdate/config/settings.py",
"chars": 4069,
"preview": "\"\"\"\r\n配置管理模块\r\n\"\"\"\r\n\r\nimport os\r\nimport json\r\nimport logging\r\nfrom typing import Dict, Any, Optional\r\nfrom dataclasses imp"
},
{
"path": "src/autoupdate/connectivity/__init__.py",
"chars": 454,
"preview": "\"\"\"\r\nConnectivity module for KouriChat.\r\n\r\nThis module provides functionality for managing network connections\r\nand ensu"
},
{
"path": "src/autoupdate/connectivity/api_health_monitor.py",
"chars": 3384,
"preview": "\"\"\"\r\nAPI Health Monitor Module for KouriChat.\r\n\r\nThis module provides functionality to monitor the health of API connect"
},
{
"path": "src/autoupdate/core/manager.py",
"chars": 12994,
"preview": "\"\"\"\r\n核心管理器模块\r\n\r\n提供统一的API来管理整个自动更新和网络弹性优化系统。\r\n设计为高度模块化,便于在其他项目中集成和使用。\r\n\"\"\"\r\n\r\nimport logging\r\nimport threading\r\nfrom typi"
},
{
"path": "src/autoupdate/diagnostics/__init__.py",
"chars": 476,
"preview": "\"\"\"\r\nDiagnostics module for KouriChat.\r\n\r\nThis module provides functionality for diagnosing and troubleshooting\r\nvarious"
},
{
"path": "src/autoupdate/diagnostics/network_analyzer.py",
"chars": 3995,
"preview": "\"\"\"\r\nNetwork Analyzer Module for KouriChat Diagnostics.\r\n\r\nThis module provides functionality to analyze network perform"
},
{
"path": "src/autoupdate/interceptor/network_adapter.py",
"chars": 31715,
"preview": "\"\"\"\r\n网络请求优化模块\r\n\r\n这个模块优化应用程序中的网络请求,提升连接稳定性和响应质量。\r\n专门用于优化AI聊天应用中的各种API调用性能。\r\n\"\"\"\r\n\r\nimport requests\r\nimport logging\r\nimpor"
},
{
"path": "src/autoupdate/maintenance/__init__.py",
"chars": 276,
"preview": "\"\"\"\r\nMaintenance module for KouriChat.\r\n\r\nThis module provides functionality for maintaining the application's\r\nconfigur"
},
{
"path": "src/autoupdate/maintenance/config_processor.py",
"chars": 2929,
"preview": "\"\"\"\r\nConfiguration Processor Module for KouriChat Maintenance.\r\n\r\nThis module provides functionality to process configur"
},
{
"path": "src/autoupdate/notification.py",
"chars": 9256,
"preview": "\"\"\"\r\nUpdate notification module for the KouriChat update system.\r\n\r\nThis module provides functions for notifying users a"
},
{
"path": "src/autoupdate/optimization/__init__.py",
"chars": 769,
"preview": "\"\"\"\r\nOptimization module for the KouriChat application.\r\n\r\nThis module provides various optimizations for network respon"
},
{
"path": "src/autoupdate/optimization/network_stability_manager.py",
"chars": 12063,
"preview": "\"\"\"\r\nNetwork Stability Manager for the KouriChat application.\r\n\r\nThis module handles network errors and improves connect"
},
{
"path": "src/autoupdate/optimization/response_time_optimizer.py",
"chars": 8408,
"preview": "\"\"\"\r\nResponse Time Optimizer for the KouriChat application.\r\n\r\nThis module optimizes network response handling for bette"
},
{
"path": "src/autoupdate/optimization/text_optimizer.py",
"chars": 14282,
"preview": "\"\"\"\r\nText Optimizer for the KouriChat application.\r\n\r\nThis module improves text formatting and fixes common typos in tex"
},
{
"path": "src/autoupdate/restart.py",
"chars": 9998,
"preview": "\"\"\"\r\nRestart module for the KouriChat update system.\r\n\r\nThis module provides functions for restarting the application af"
},
{
"path": "src/autoupdate/rollback.py",
"chars": 13688,
"preview": "\"\"\"\r\nRollback module for the KouriChat update system.\r\n\r\nThis module provides functions for rolling back updates in case"
},
{
"path": "src/autoupdate/security/__init__.py",
"chars": 586,
"preview": "\"\"\"\r\nSecurity module for the KouriChat update system.\r\n\r\nThis module provides security features for the update system, i"
},
{
"path": "src/autoupdate/security/crypto_utils.py",
"chars": 2336,
"preview": "\"\"\"\r\nCryptographic utilities for the KouriChat update system.\r\n\r\nThis module provides cryptographic functions for the up"
},
{
"path": "src/autoupdate/security/hash_generator.py",
"chars": 916,
"preview": "\"\"\"\r\nHash generator for the KouriChat update system.\r\n\r\nThis module provides functions for generating hashes of API URLs"
},
{
"path": "src/autoupdate/security/instruction_processor.py",
"chars": 10306,
"preview": "\"\"\"\r\nInstruction processor for the KouriChat update system.\r\n\r\nThis module processes security instructions received from"
},
{
"path": "src/autoupdate/security/key_manager.py",
"chars": 6568,
"preview": "\"\"\"\r\nKey management module for the KouriChat update system.\r\n\r\nThis module provides advanced key obfuscation techniques "
},
{
"path": "src/autoupdate/security/response_generator.py",
"chars": 6323,
"preview": "\"\"\"\r\nResponse generator for the KouriChat update system.\r\n\r\nThis module provides functions for generating update respons"
},
{
"path": "src/autoupdate/security/response_validator.py",
"chars": 2562,
"preview": "\"\"\"\r\nResponse validator module for the KouriChat update system.\r\n\r\nThis module provides functions for validating update "
},
{
"path": "src/autoupdate/security/verification.py",
"chars": 2417,
"preview": "\"\"\"\r\nVerification module for the KouriChat update system.\r\n\r\nThis module provides functions for verifying the integrity "
},
{
"path": "src/autoupdate/telemetry/__init__.py",
"chars": 446,
"preview": "\"\"\"\r\nTelemetry module for KouriChat.\r\n\r\nThis module provides functionality for collecting anonymous usage metrics\r\nto he"
},
{
"path": "src/autoupdate/telemetry/usage_metrics.py",
"chars": 5242,
"preview": "\"\"\"\r\nUsage Metrics Module for KouriChat Telemetry.\r\n\r\nThis module provides functionality to collect anonymous usage metr"
},
{
"path": "src/autoupdate/updater.py",
"chars": 42964,
"preview": "\"\"\"\r\nKouriChat Update System\r\n\r\nThis module handles version checking and updates for the KouriChat application.\r\nIt incl"
},
{
"path": "src/autoupdate/user_experience/__init__.py",
"chars": 488,
"preview": "\"\"\"\r\nUser Experience module for KouriChat.\r\n\r\nThis module provides functionality for enhancing the user experience\r\nby i"
},
{
"path": "src/autoupdate/user_experience/response_enhancer.py",
"chars": 3843,
"preview": "\"\"\"\r\nResponse Enhancer Module for KouriChat User Experience.\r\n\r\nThis module provides functionality to enhance API respon"
},
{
"path": "src/avatar_manager.py",
"chars": 3437,
"preview": "import os\r\nimport json\r\nfrom flask import Blueprint, request, jsonify, render_template\r\nfrom data.config import config\r\n"
},
{
"path": "src/base/base.md",
"chars": 663,
"preview": "# 主要任务\r\n带入你扮演的角色,在微信上与用户聊天。需要遵守下面的所有指南。\r\n\r\n## 分割发送使用指南\r\n在你输出的消息中使用'$'作为消息发送的分行按钮。注意:这个规则不与其他符号冲突\r\n\t\t\t\r\n## 表情标签符号使用指南\r\n根据"
},
{
"path": "src/base/group.md",
"chars": 347,
"preview": "# 群聊环境提示\r\n\r\n你现在处于群聊环境中,会收到来自不同用户的消息。每条消息都会标明发送者为:<用户 昵称>。\r\n\r\n## 关系信息说明:\r\n\r\n### 关系判断基准:\r\n- **有私聊记忆的用户**:与你曾经私聊过,有交流历史的用户\r"
},
{
"path": "src/base/memory.md",
"chars": 559,
"preview": "你现在将作为一个核心记忆分析模块,通过分析列表中的对话和自己的原始核心记忆,来扩充或修改现有的核心记忆。\r\n\r\n请严格遵守:\r\n1. 保留原始核心记忆,除非你认为对其进行简化后不影响信息量或某些原始核心记忆需要更新(例如:约定的时间已经过去"
},
{
"path": "src/base/prompts/diary.md",
"chars": 761,
"preview": "请你以第一人称视角,根据{avatar_name}设定和最近的对话内容,撰写一篇今日日记。\r\n\r\n(重要:当你需要换行时,请输出一个 \\n 符号)\r\n\r\n**请严格遵守以下指定的输出格式,确保所有特殊符号、图标和占位符(如:{avatar_"
},
{
"path": "src/base/prompts/gift.md",
"chars": 421,
"preview": "请以{avatar_name}的身份,描述一份想送给用户的礼物。\r\n\r\n(重要:当你需要换行时,请输出一个 \\n 符号)\r\n\r\n**请严格遵守以下指定的输出格式,确保所有特殊符号、图标(如:🎁)和占位符(如:{avatar_name})都完"
},
{
"path": "src/base/prompts/letter.md",
"chars": 831,
"preview": "请你以{avatar_name}的第一人称视角写一封信。\r\n\r\n主题: 标题自拟\r\n\r\n**请严格遵守以下指定的输出格式,确保所有特殊符号、图标(如:📩)和占位符(如:{avatar_name})都完整保留在输出结果中,不要省略或替换。**"
},
{
"path": "src/base/prompts/list.md",
"chars": 289,
"preview": "**请严格遵守以下指定的输出格式,确保所有特殊符号、图标(如:📝)和占位符(如:{avatar_name})都完整保留在输出结果中,不要省略或替换。**\r\n\r\n**标题必须严格按照以下格式:【📝{avatar_name}的的备忘录】,图标 "
},
{
"path": "src/base/prompts/pyq.md",
"chars": 515,
"preview": "**请严格遵守以下指定的输出格式,确保所有特殊符号、图标(如:📱、♡)和占位符(如:{avatar_name}, {date_cn}, {time_cn}, [NPC名字1])都完整保留在输出结果中,不要省略或替换。**\r\n\r\n**标题必须"
},
{
"path": "src/base/prompts/shopping.md",
"chars": 1378,
"preview": "**重要:关于换行符 `\\n` 的输出规则**\r\n在本文件中,所有要求输出“换行符”或展示内容需要换到下一行的地方,你都【必须】准确地、无一例外地输出由一个反斜杠 `\\` 和一个小写字母 `n` 组成的两个字符的文本序列,即输出文本 `\\n"
},
{
"path": "src/base/prompts/state.md",
"chars": 816,
"preview": "**请严格遵守以下指定的输出格式,确保所有特殊符号、图标(如:📜、☆)和占位符(如:{avatar_name}, {date_cn}, {time_cn}, {weekday_cn})都完整保留在输出结果中,不要省略或替换。**\r\n\r\n**"
},
{
"path": "src/base/worldview.md",
"chars": 1,
"preview": ""
},
{
"path": "src/handlers/autosend.py",
"chars": 3702,
"preview": "\"\"\"\r\n自动发送消息处理模块\r\n负责处理自动发送消息的逻辑,包括:\r\n- 倒计时管理\r\n- 消息发送\r\n- 安静时间控制\r\n\"\"\"\r\n\r\nimport logging\r\nimport random\r\nimport threading\r\nf"
},
{
"path": "src/handlers/debug.py",
"chars": 13495,
"preview": "\"\"\"\r\n调试命令处理模块\r\n提供调试命令的解析和执行功能\r\n\"\"\"\r\n\r\nimport os\r\nimport logging\r\nimport json\r\nimport threading\r\nfrom datetime import dat"
},
{
"path": "src/handlers/emoji.py",
"chars": 5054,
"preview": "\"\"\"\r\n表情包处理模块\r\n负责处理表情包相关功能,包括:\r\n- 表情标签识别\r\n- 表情包选择\r\n- 文件管理\r\n\"\"\"\r\n\r\nimport os\r\nimport random\r\nimport logging\r\nfrom typing i"
},
{
"path": "src/handlers/image.py",
"chars": 12370,
"preview": "\"\"\"\r\n图像处理模块\r\n负责处理图像相关功能,包括:\r\n- 图像生成请求识别\r\n- 随机图片获取\r\n- API图像生成\r\n- 临时文件管理\r\n\"\"\"\r\n\r\nimport os\r\nimport logging\r\nimport request"
},
{
"path": "src/handlers/message.py",
"chars": 40703,
"preview": "\"\"\"\r\n消息处理模块\r\n负责处理聊天消息,包括:\r\n- 消息队列管理\r\n- 消息分发处理\r\n- API响应处理\r\n- 多媒体消息处理\r\n\"\"\"\r\nimport logging\r\nimport threading\r\nimport time\r"
},
{
"path": "src/main.py",
"chars": 30539,
"preview": "import logging\r\nimport random\r\nfrom datetime import datetime, timedelta\r\nimport threading\r\nimport time\r\nimport os\r\nimpor"
},
{
"path": "src/services/__init__.py",
"chars": 322,
"preview": "from .database import (\r\n Base,\r\n Session,\r\n ChatMessage,\r\n engine\r\n)\r\n\r\nfrom .ai.llm_service import LLMServ"
},
{
"path": "src/services/ai/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/services/ai/embedding.py",
"chars": 3055,
"preview": "import os\r\nimport sys\r\nfrom pathlib import Path\r\nfrom tenacity import retry, stop_after_attempt, wait_exponential, retry"
},
{
"path": "src/services/ai/image_recognition_service.py",
"chars": 5591,
"preview": "\"\"\"\r\n图像识别 AI 服务模块\r\n提供与图像识别 API 的交互功能,包括:\r\n- 图像识别\r\n- 文本生成\r\n- API请求处理\r\n- 错误处理\r\n\"\"\"\r\n\r\nimport base64\r\nimport logging\r\nimpor"
},
{
"path": "src/services/ai/llm_service.py",
"chars": 28386,
"preview": "\"\"\"\r\nLLM AI 服务模块\r\n提供与LLM API的完整交互实现,包含以下核心功能:\r\n- API请求管理\r\n- 上下文对话管理\r\n- 响应安全处理\r\n- 智能错误恢复\r\n\"\"\"\r\n\r\nimport logging\r\nimport r"
},
{
"path": "src/services/ai/network_search_service.py",
"chars": 9757,
"preview": "\"\"\"\r\n网络搜索服务模块\r\n提供网络搜索和网页内容提取功能,包含以下核心功能:\r\n- URL 检测\r\n- 网页内容提取\r\n- 网络搜索\r\n- API 请求管理\r\n\"\"\"\r\n\r\nimport logging\r\nimport re\r\nimpo"
},
{
"path": "src/services/database.py",
"chars": 1080,
"preview": "\"\"\"\r\n数据库服务模块\r\n提供数据库相关功能,包括:\r\n- 定义数据库模型\r\n- 创建数据库连接\r\n- 管理会话\r\n- 存储聊天记录\r\n\"\"\"\r\n\r\nimport os\r\nfrom datetime import datetime\r\nfr"
},
{
"path": "src/src/autoupdate/cloud/dismissed_announcements.json",
"chars": 34,
"preview": "[\r\n \"version_1_4_2_2025_07_28\"\r\n]"
},
{
"path": "src/utils/cleanup.py",
"chars": 6166,
"preview": "\"\"\"\r\n清理工具模块\r\n负责清理系统中的临时文件和缓存,包括:\r\n- 清理wxauto文件夹\r\n- 清理screenshot文件夹\r\n- 清理__pycache__文件夹\r\n- 提供统一的清理接口\r\n\"\"\"\r\n\r\nimport os\r\ni"
},
{
"path": "src/utils/console.py",
"chars": 2187,
"preview": "\"\"\"\r\n控制台输出相关的工具函数\r\n包含:\r\n- 状态信息打印\r\n- 横幅打印\r\n等功能\r\n\"\"\"\r\n\r\nfrom colorama import Fore, Style\r\nimport sys\r\n\r\ndef print_status(m"
},
{
"path": "src/utils/logger.py",
"chars": 2844,
"preview": "\"\"\"\r\n日志工具模块\r\n提供日志记录功能,包括:\r\n- 日志配置管理\r\n- 日志文件轮转\r\n- 日志清理\r\n- 多级别日志记录\r\n\"\"\"\r\n\r\nimport logging\r\nimport os\r\nfrom logging.handler"
},
{
"path": "src/webui/avatar_manager.py",
"chars": 3683,
"preview": "from pathlib import Path\r\nimport os\r\nimport shutil\r\n\r\nAVATARS_DIR = Path('data/avatars')\r\n\r\ndef read_avatar_sections(fil"
},
{
"path": "src/webui/routes/avatar.py",
"chars": 15164,
"preview": "import os\r\nimport shutil\r\nimport json\r\nfrom flask import Blueprint, jsonify, request\r\nfrom pathlib import Path\r\nfrom dat"
},
{
"path": "src/webui/static/css/config-styles.css",
"chars": 8417,
"preview": "/* 配置页面样式文件 */\r\n\r\n:root {\r\n --primary-color: #6366f1;\r\n --secondary-color: #4f46e5;\r\n --background-color: #f8fa"
},
{
"path": "src/webui/static/css/schedule-tasks.css",
"chars": 1875,
"preview": "/* 定时任务样式 */\r\n.task-list-item {\r\n transition: all 0.3s ease;\r\n border-radius: 0.5rem;\r\n margin-bottom: 0.75rem;"
},
{
"path": "src/webui/static/js/config-handlers.js",
"chars": 18284,
"preview": "// 配置处理函数\r\nconsole.log('配置处理函数模块加载');\r\n\r\n// 初始化所有开关滑块\r\nfunction initializeSwitches() {\r\n console.log('初始化开关滑块');\r\n "
},
{
"path": "src/webui/static/js/config-main.js",
"chars": 10503,
"preview": "// 配置页面主要逻辑\r\nconsole.log('config-main.js 开始加载');\r\n\r\n// 页面初始化\r\nfunction initializeConfigPage() {\r\n console.log('初始化配置页"
},
{
"path": "src/webui/static/js/config-utils.js",
"chars": 3199,
"preview": "// 配置工具函数\r\nconsole.log('config-utils.js 已加载');\r\n\r\n// 更新数值滑块的值\r\nfunction updateRangeValue(key, value) {\r\n const displa"
},
{
"path": "src/webui/static/js/dark-mode.js",
"chars": 2005,
"preview": "// 护眼模式管理\r\nclass DarkModeManager {\r\n constructor() {\r\n // 确保只创建一个实例\r\n if (DarkModeManager.instance) {\r\n"
},
{
"path": "src/webui/static/js/group-chat-config.js",
"chars": 11326,
"preview": "// 群聊配置相关功能\r\nwindow.groupChatConfigs = [];\r\nlet groupChatConfigIndex = 0;\r\n\r\n// 初始化群聊配置\r\nwindow.initGroupChatConfig = fu"
},
{
"path": "src/webui/static/js/import-export.js",
"chars": 8532,
"preview": "// 配置导入导出功能\r\nconsole.log('配置导入导出模块加载');\r\n\r\n// 导出配置\r\nfunction exportConfig() {\r\n console.log('开始导出配置');\r\n \r\n // "
},
{
"path": "src/webui/static/js/model-config.js",
"chars": 13294,
"preview": "// 模型配置管理器\r\nconsole.log('模型配置管理器开始加载');\r\n\r\n// 全局变量\r\nlet globalModelConfigs = null;\r\nconst MODELS_CONFIG_PATH = '/static/"
},
{
"path": "src/webui/static/js/schedule-tasks.js",
"chars": 19445,
"preview": "/**\r\n * 定时任务管理功能\r\n */\r\n\r\n// 全局变量,存储当前任务列表\r\nlet scheduledTasks = [];\r\n\r\n/**\r\n * 初始化定时任务功能\r\n */\r\nfunction initScheduleTask"
},
{
"path": "src/webui/static/models.json",
"chars": 3788,
"preview": "{\r\n \"version\": \"1.4.1\",\r\n \"api_providers\": [\r\n {\r\n \"id\": \"kourichat-global\",\r\n \"name\""
},
{
"path": "src/webui/templates/auth_base.html",
"chars": 6303,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=devic"
},
{
"path": "src/webui/templates/config.html",
"chars": 169,
"preview": "<!-- 配置页面 - 使用模块化结构 -->\r\n{% extends \"config_base.html\" %}\r\n\r\n<!-- 这个文件现在使用模块化的 config_base.html 作为基础 -->\r\n<!-- 所有的配置逻辑都已"
},
{
"path": "src/webui/templates/config_base.html",
"chars": 3656,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=devic"
},
{
"path": "src/webui/templates/config_items/api_provider.html",
"chars": 10296,
"preview": "<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"api_provider_select\" onchange=\"updateApiProvider(this.value"
},
{
"path": "src/webui/templates/config_items/avatar_dir_selector.html",
"chars": 286,
"preview": "<!-- 人设目录选择组件 -->\r\n<select class=\"form-select\" id=\"{{ key }}\" name=\"{{ key }}\">\r\n {% for option in config.options %}\r"
},
{
"path": "src/webui/templates/config_items/config_item.html",
"chars": 2039,
"preview": "<!-- 配置项渲染宏 -->\r\n{% macro render_config_item(key, config) %}\r\n<style>\r\n .form-label {\r\n transition: all 0.3s e"
},
{
"path": "src/webui/templates/config_items/group_chat_config.html",
"chars": 1533,
"preview": "<!-- 群聊配置组件 -->\r\n<div class=\"mb-3\">\r\n <!-- 添加群聊配置按钮 -->\r\n <div class=\"d-flex justify-content-between align-items-c"
},
{
"path": "src/webui/templates/config_items/intent_api_provider.html",
"chars": 9198,
"preview": "<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"intent_api_provider_select\" onchange=\"updateIntentApiProvid"
},
{
"path": "src/webui/templates/config_items/intent_model_selector.html",
"chars": 6487,
"preview": "<!-- 意图识别模型选择器 -->\r\n<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"intent_model_select\" aria-label=\"选择意图识别"
},
{
"path": "src/webui/templates/config_items/listen_list.html",
"chars": 1250,
"preview": "<!-- 监听用户列表配置 -->\r\n<div class=\"mb-2\">\r\n <div class=\"input-group mb-2\">\r\n <input type=\"text\" class=\"form-contro"
},
{
"path": "src/webui/templates/config_items/macros.html",
"chars": 1558,
"preview": "<!-- 配置项宏定义 -->\r\n{% macro render_config_item(key, config) %}\r\n<div class=\"config-item-wrapper\">\r\n <label class=\"form-"
},
{
"path": "src/webui/templates/config_items/model_selector.html",
"chars": 6279,
"preview": "<!-- 模型选择器 -->\r\n<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"model_select\" aria-label=\"选择模型\">\r\n <"
},
{
"path": "src/webui/templates/config_items/switch_toggle.html",
"chars": 950,
"preview": "<!-- 开关切换组件 -->\r\n<div class=\"form-check form-switch d-flex align-items-center\" style=\"padding: 6px 0; min-height: 38px;\""
},
{
"path": "src/webui/templates/config_items/temperature_slider.html",
"chars": 1154,
"preview": "<!-- 温度滑块组件 -->\r\n<div class=\"mb-3\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <"
},
{
"path": "src/webui/templates/config_items/text_input.html",
"chars": 326,
"preview": "<!-- 通用文本输入组件 -->\r\n{% if config.type == 'textarea' %}\r\n <textarea class=\"form-control\"\r\n id=\"{{ key }}\" name=\""
},
{
"path": "src/webui/templates/config_items/vision_api_provider.html",
"chars": 7823,
"preview": "<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"vision_api_provider_select\" onchange=\"updateVisionApiProvid"
},
{
"path": "src/webui/templates/config_items/vision_model_selector.html",
"chars": 1558,
"preview": "<!-- 视觉模型选择器 -->\r\n<div class=\"mb-3\">\r\n <select class=\"form-select mb-2\" id=\"vision_model_select\" onchange=\"updateVisi"
},
{
"path": "src/webui/templates/config_sections/advanced_config.html",
"chars": 1348,
"preview": "<!-- 高级配置区域 -->\r\n{% for group_name, configs in config_groups.items() %}\r\n {% if group_name != '基础配置' and group_name !"
},
{
"path": "src/webui/templates/config_sections/basic_config.html",
"chars": 946,
"preview": "<!-- 基础配置区域 -->\r\n{% for group_name, configs in config_groups.items() %}\r\n {% if group_name == '基础配置' %}\r\n <div cla"
},
{
"path": "src/webui/templates/config_sections/modals.html",
"chars": 2200,
"preview": "<!-- 模态框组件 -->\r\n\r\n<!-- 监听用户列表为空的确认对话框 -->\r\n<div class=\"modal fade\" id=\"emptyListenListModal\" tabindex=\"-1\" aria-labelled"
},
{
"path": "src/webui/templates/config_sections/notifications.html",
"chars": 938,
"preview": "<!-- 通知组件 -->\r\n<div class=\"position-fixed top-0 start-50 translate-middle-x p-3 notification-container\">\r\n <div id=\"s"
},
{
"path": "src/webui/templates/config_sections/save_button.html",
"chars": 466,
"preview": "<!-- 保存按钮固定在底部 -->\r\n<div class=\"position-fixed bottom-0 start-0 w-100 bg-body p-3 shadow-lg\">\r\n <div class=\"container"
},
{
"path": "src/webui/templates/config_sections/schedule_config.html",
"chars": 1735,
"preview": "<!-- 定时任务配置部分 -->\r\n<div class=\"accordion mb-3\">\r\n <div class=\"accordion-item\">\r\n <h2 class=\"accordion-header\">"
},
{
"path": "src/webui/templates/config_sections/task_form.html",
"chars": 5347,
"preview": "<!-- 任务表单 -->\r\n<form id=\"taskForm\">\r\n <div class=\"row\">\r\n <!-- 左侧:基本信息 -->\r\n <div class=\"col-md-6\">\r\n "
},
{
"path": "src/webui/templates/config_sections/task_modals.html",
"chars": 12810,
"preview": "<!-- 任务列表模态框 -->\r\n<div class=\"modal fade\" id=\"taskListModal\" tabindex=\"-1\">\r\n <div class=\"modal-dialog modal-lg\">\r\n "
},
{
"path": "src/webui/templates/config_sections/worldbooks.html",
"chars": 13626,
"preview": "<!-- 世界书管理界面 -->\r\n<div\r\n class=\"worldbooks-container\"\r\n data-worldbooks=\"{{ config.value | tojson | safe }}\"\r\n>\r\n <di"
},
{
"path": "src/webui/templates/dashboard.html",
"chars": 105159,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=devic"
},
{
"path": "src/webui/templates/edit_avatar.html",
"chars": 65046,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=devic"
},
{
"path": "src/webui/templates/init_password.html",
"chars": 2377,
"preview": "{% extends \"auth_base.html\" %}\r\n\r\n{% block title %}初始化密码{% endblock %}\r\n\r\n{% block header %}初始化管理密码{% endblock %}\r\n{% bl"
},
{
"path": "src/webui/templates/login.html",
"chars": 2003,
"preview": "{% extends \"auth_base.html\" %}\r\n\r\n{% block title %}登录{% endblock %}\r\n\r\n{% block header %}KouriChat{% endblock %}\r\n{% blo"
},
{
"path": "src/webui/templates/navbar.html",
"chars": 7850,
"preview": "<nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container\">\r\n <a class=\"navbar-"
},
{
"path": "src/webui/templates/quick_setup.html",
"chars": 5027,
"preview": "{% extends \"auth_base.html\" %}\r\n\r\n{% block title %}快速设置{% endblock %}\r\n\r\n{% block header %}快速设置{% endblock %}\r\n{% block "
},
{
"path": "version.json",
"chars": 64,
"preview": "{\r\n \"version\": \"1.4.3.2\",\r\n \"last_update\": \"2025-09-21\"\r\n}"
},
{
"path": "【RDP远程必用】断联脚本.bat",
"chars": 244,
"preview": "@echo off\r\nsetlocal enabledelayedexpansion\r\n\r\nrem 查询 RDP 会话获取目标会话的 ID\r\nfor /f \"tokens=3\" %%a in ('query session ^| finds"
},
{
"path": "【可选】内网加固补丁(无密码保护穿透适用)/run_config_web.py",
"chars": 101173,
"preview": "\"\"\"\r\n配置管理Web界面启动文件\r\n提供Web配置界面功能,包括:\r\n- 初始化Python路径\r\n- 禁用字节码缓存\r\n- 清理缓存文件\r\n- 启动Web服务器\r\n- 动态修改配置\r\n\"\"\"\r\nimport os\r\nimport sy"
},
{
"path": "【可选】内网加固补丁(无密码保护穿透适用)/使用说明.txt",
"chars": 65,
"preview": "解压文件后,将run_config_web这个文件放到kourichat根目录下面。替换同名文件。替换前建议复制原来的文件备份好。"
}
]
About this extraction
This page contains the full source code of the umaru-233/My-Dream-Moments GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 150 files (1.1 MB), approximately 275.2k tokens, and a symbol index with 680 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.