Compare commits
4 Commits
ee81ccefc5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
23f03822f6
|
|||
|
ad3bafcd35
|
|||
|
82723038c3
|
|||
|
03928c6c59
|
@@ -22,7 +22,7 @@ NyaHome 是由 FastAPI 后端、Vue WebUI 实现的在线 AI 文学创作平台
|
|||||||
| 功能 | 阶段 | 优先级 |
|
| 功能 | 阶段 | 优先级 |
|
||||||
|---------------------------|-----|-----|
|
|---------------------------|-----|-----|
|
||||||
| **剧本市场** - 剧本分享、聊天室导出为剧本 | 规划中 | 低 |
|
| **剧本市场** - 剧本分享、聊天室导出为剧本 | 规划中 | 低 |
|
||||||
| **用户功能** - 绑定手机号、接收收集验证码 | 规划中 | 低 |
|
| **用户功能** - 绑定手机号、接收手机验证码 | 规划中 | 低 |
|
||||||
| **用户功能** - 第三方账户 Oauth 登录 | 规划中 | 低 |
|
| **用户功能** - 第三方账户 Oauth 登录 | 规划中 | 低 |
|
||||||
|
|
||||||
## 代码规范
|
## 代码规范
|
||||||
|
|||||||
@@ -7,10 +7,3 @@ console = Console()
|
|||||||
DATA_DIR = Path.cwd() / ".nyahome"
|
DATA_DIR = Path.cwd() / ".nyahome"
|
||||||
ENV_PATH = DATA_DIR / ".env"
|
ENV_PATH = DATA_DIR / ".env"
|
||||||
LOGGING_YAML = DATA_DIR / "logging.yaml"
|
LOGGING_YAML = DATA_DIR / "logging.yaml"
|
||||||
|
|
||||||
db_driver_available = {
|
|
||||||
"sqlite": ["sqlite3"],
|
|
||||||
"mysql": ["pymysql"],
|
|
||||||
"postgresql": ["psycopg"],
|
|
||||||
}
|
|
||||||
db_type_allowlist = ["sqlite", "mysql", "postgresql"]
|
|
||||||
|
|||||||
@@ -81,6 +81,14 @@ def add_model(
|
|||||||
help="该模型所属于的模型提供商 ID",
|
help="该模型所属于的模型提供商 ID",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
reasonable: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
"--reasonable",
|
||||||
|
"-r",
|
||||||
|
help="支持思考",
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
添加 AI 模型。在此之前需要先添加该模型的提供商。
|
添加 AI 模型。在此之前需要先添加该模型的提供商。
|
||||||
@@ -92,7 +100,12 @@ def add_model(
|
|||||||
from nyahome.database import AiiModel, engine
|
from nyahome.database import AiiModel, engine
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
am = AiiModel(model_name=model_name, max_context_length=max_context_length, aii_provider_id=provider_id)
|
am = AiiModel(
|
||||||
|
model_name=model_name,
|
||||||
|
max_context_length=max_context_length,
|
||||||
|
aii_provider_id=provider_id,
|
||||||
|
reasonable=reasonable,
|
||||||
|
)
|
||||||
session.add(am)
|
session.add(am)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(am)
|
session.refresh(am)
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from importlib.util import find_spec
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
|
|
||||||
from nyahome.cli.cli import console, db_driver_available, db_type_allowlist
|
from nyahome.cli.cli import console
|
||||||
|
from nyahome.data import db_driver_available, db_type_allowlist
|
||||||
|
|
||||||
|
|
||||||
class CliWarning:
|
class CliWarning:
|
||||||
@@ -73,7 +74,7 @@ def check_database_type(environ: Mapping[str, str | None]) -> None:
|
|||||||
if not db_host:
|
if not db_host:
|
||||||
cw.warning("NYAHOME_DB_HOST 未设置,将使用 [cyan]localhost[/cyan] 作为默认值。")
|
cw.warning("NYAHOME_DB_HOST 未设置,将使用 [cyan]localhost[/cyan] 作为默认值。")
|
||||||
if not db_port:
|
if not db_port:
|
||||||
cw.warning("NYAHOME_DB_PORT 未设置,将使用 [cyan]3006[/cyan] 作为默认值。")
|
cw.warning("NYAHOME_DB_PORT 未设置,将使用 [cyan]3306[/cyan] 作为默认值。")
|
||||||
cw.info("自检未检查数据库状态是否可用。")
|
cw.info("自检未检查数据库状态是否可用。")
|
||||||
else:
|
else:
|
||||||
cw.info("使用 sqlite 数据库,跳过数据库凭证检查。")
|
cw.info("使用 sqlite 数据库,跳过数据库凭证检查。")
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
from nyahome.config import Config, config_manager
|
||||||
|
|
||||||
|
from .cli import console
|
||||||
|
|
||||||
|
config_app = typer.Typer()
|
||||||
|
|
||||||
|
|
||||||
|
@config_app.command(name="list")
|
||||||
|
def list_all_configs() -> None:
|
||||||
|
"""
|
||||||
|
列出所有 NyaHome 定义的设置项。直接输出,可能包含敏感信息。
|
||||||
|
|
||||||
|
同时包含默认值和当前值。在 NyaHome 首次运行时,所有设置项都会以默认值存储。
|
||||||
|
"""
|
||||||
|
config_manager.sync_load_config()
|
||||||
|
ci = Config()
|
||||||
|
|
||||||
|
table = Table(title="NyaHome 设置")
|
||||||
|
table.add_column("设置键名", style="cyan", no_wrap=True)
|
||||||
|
table.add_column("值类型", style="bright_black", no_wrap=True)
|
||||||
|
table.add_column("当前值", style="white")
|
||||||
|
table.add_column("默认值", style="bright_black")
|
||||||
|
|
||||||
|
for key, value in config_manager.get_config().items():
|
||||||
|
default_value = getattr(ci, key)
|
||||||
|
table.add_row(key, type(default_value).__name__, str(value), str(default_value))
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@config_app.command(name="set")
|
||||||
|
def set_config_item(
|
||||||
|
key: Annotated[str, typer.Argument(help="设置键名")],
|
||||||
|
value: Annotated[list[str], typer.Argument(help="设置键新值,类型会自动转换,多个输入将被视作列表")],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
修改一项设置。
|
||||||
|
|
||||||
|
目前,NyaHome 的设置键所支持的值类型包括:str int bool list
|
||||||
|
"""
|
||||||
|
config_manager.sync_load_config()
|
||||||
|
if len(value) == 1:
|
||||||
|
value: str | int | bool = value[0] # type: ignore[no-redef]
|
||||||
|
try:
|
||||||
|
config_manager.set(key, value)
|
||||||
|
except AttributeError:
|
||||||
|
console.print(f"[yellow]设置失败,设置键 [cyan]{key}[/cyan] 不存在。[/yellow]")
|
||||||
|
config_manager.sync_save_config()
|
||||||
|
console.print(f"已经将设置项 [cyan]{key}[/cyan] 的值设置为 [cyan]{value}[/cyan]")
|
||||||
|
|
||||||
|
|
||||||
|
@config_app.command(name="reset")
|
||||||
|
def reset_config_item(
|
||||||
|
key: Annotated[str, typer.Argument(help="设置键名")],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
重置一项设置至默认值。
|
||||||
|
"""
|
||||||
|
config_manager.sync_load_config()
|
||||||
|
try:
|
||||||
|
config_manager.reset(key)
|
||||||
|
except AttributeError:
|
||||||
|
console.print(f"[yellow]设置失败,设置键 [cyan]{key}[/cyan] 不存在。[/yellow]")
|
||||||
|
config_manager.sync_save_config()
|
||||||
|
console.print(f"已经将设置项 [cyan]{key}[/cyan] 的值重置为默认值。")
|
||||||
@@ -42,8 +42,11 @@ def set_env(
|
|||||||
|
|
||||||
保存在 .nyahome 内的 .env 文件。
|
保存在 .nyahome 内的 .env 文件。
|
||||||
"""
|
"""
|
||||||
set_key(ENV_PATH, f"NYAHOME_{key.upper()}", value)
|
key = key.upper()
|
||||||
console.print(f"[cyan]已设置环境变量 NYAHOME_{key}。[/cyan]")
|
if not key.startswith("NYAHOME_"):
|
||||||
|
key = f"NYAHOME_{key}"
|
||||||
|
set_key(ENV_PATH, key, value)
|
||||||
|
console.print(f"[cyan]已设置环境变量 {key}。[/cyan]")
|
||||||
|
|
||||||
|
|
||||||
@env_app.command(name="unset")
|
@env_app.command(name="unset")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
from .config import Config
|
||||||
from .manager import config_manager
|
from .manager import config_manager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
Config,
|
||||||
config_manager,
|
config_manager,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONFIG_PATH = Path.cwd() / ".nyahome" / "config.json"
|
CONFIG_PATH = Path.cwd() / ".nyahome" / "config.json"
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T", str, int, bool, list)
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
|
"""
|
||||||
|
ConfigManager 携带一个初始化的 Config 实例。在 Config 初始化时,所有的默认设置键的值就都已经加载。
|
||||||
|
因此,如果不 load_config,ConfigManager 也将持有一套默认设置。
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
CONFIG_PATH.parent.mkdir(exist_ok=True)
|
CONFIG_PATH.parent.mkdir(exist_ok=True)
|
||||||
self._config = Config()
|
self._config = Config()
|
||||||
@@ -76,6 +81,53 @@ class ConfigManager:
|
|||||||
"""
|
"""
|
||||||
return getattr(self._config, key, default) # type: ignore[return-value]
|
return getattr(self._config, key, default) # type: ignore[return-value]
|
||||||
|
|
||||||
|
def set(self, key: str, value: T) -> None:
|
||||||
|
"""
|
||||||
|
设置配置项。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 配置键名
|
||||||
|
value: 配置键的新值,可以是(且仅支持)字符串、整型以及列表。
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: 配置键名错误
|
||||||
|
TypeError: 配置键值类型错误
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
old_value = self.get(key)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
match old_value:
|
||||||
|
case str():
|
||||||
|
new_value = str(value)
|
||||||
|
case int():
|
||||||
|
new_value = int(value)
|
||||||
|
case bool():
|
||||||
|
new_value = bool(value)
|
||||||
|
case list():
|
||||||
|
new_value = list(value)
|
||||||
|
case _:
|
||||||
|
raise TypeError(f"不支持 {type(old_value).__name__} 类型的设置项。({key})")
|
||||||
|
setattr(self._config, key, new_value)
|
||||||
|
|
||||||
|
def reset(self, key: str) -> None:
|
||||||
|
"""
|
||||||
|
将配置项恢复至默认值。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 配置键名
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: 配置键名错误
|
||||||
|
"""
|
||||||
|
ci = Config()
|
||||||
|
try:
|
||||||
|
default_value = getattr(ci, key)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise e
|
||||||
|
setattr(self._config, key, default_value)
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = {}
|
config = {}
|
||||||
for attr in dir(self._config):
|
for attr in dir(self._config):
|
||||||
|
|||||||
@@ -42,17 +42,17 @@ class OtpMemoryStore(ABC):
|
|||||||
async def _cleanup(self) -> None:
|
async def _cleanup(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(60)
|
await asyncio.sleep(60)
|
||||||
logger.debug(f"[{self.type_name}] 开始定时清理过期验证码。")
|
# logger.debug(f"[{self.type_name}] 开始定时清理过期验证码。")
|
||||||
expires = []
|
expires = []
|
||||||
count = 0
|
count = 0
|
||||||
for address, item in self._store.items():
|
for address, item in self._store.items():
|
||||||
if item.expire_time < time.time():
|
if item.expire_time < time.time():
|
||||||
logger.debug(f"[{self.type_name}] 移除过期的 {address}")
|
# logger.debug(f"[{self.type_name}] 移除过期的 {address}")
|
||||||
expires.append(address)
|
expires.append(address)
|
||||||
count += 1
|
count += 1
|
||||||
for address in expires:
|
for address in expires:
|
||||||
self._store.pop(address)
|
self._store.pop(address)
|
||||||
logger.debug(f"[{self.type_name}] 清理完成,清理了 {count} 个过期验证码。")
|
# logger.debug(f"[{self.type_name}] 清理完成,清理了 {count} 个过期验证码。")
|
||||||
|
|
||||||
def verify(self, address: str, user_id: int, verify_code: str) -> bool:
|
def verify(self, address: str, user_id: int, verify_code: str) -> bool:
|
||||||
item = self._store.get(address)
|
item = self._store.get(address)
|
||||||
|
|||||||
@@ -95,10 +95,10 @@ class EmailSenderQueue(TaskQueue):
|
|||||||
body=item.body,
|
body=item.body,
|
||||||
sender=config_manager.get("smtp_sender"),
|
sender=config_manager.get("smtp_sender"),
|
||||||
hostname=config_manager.get("smtp_hostname"),
|
hostname=config_manager.get("smtp_hostname"),
|
||||||
port=config_manager.get("smtp_port"),
|
port=config_manager.get("smtp_port", 465),
|
||||||
username=config_manager.get("smtp_username"),
|
username=config_manager.get("smtp_username"),
|
||||||
password=config_manager.get("smtp_password"),
|
password=config_manager.get("smtp_password"),
|
||||||
use_tls=config_manager.get("smtp_use_tls"),
|
use_tls=config_manager.get("smtp_use_tls", True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
db_driver_available = {
|
||||||
|
"sqlite": ["sqlite3"],
|
||||||
|
"mysql": ["pymysql"],
|
||||||
|
"postgresql": ["psycopg"],
|
||||||
|
}
|
||||||
|
db_type_allowlist = ["sqlite", "mysql", "postgresql"]
|
||||||
@@ -1,5 +1,34 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
from sqlalchemy import Engine
|
||||||
from sqlmodel import create_engine
|
from sqlmodel import create_engine
|
||||||
|
|
||||||
engine = create_engine(os.environ["NYAHOME_SQL_URL"])
|
from nyahome.data import db_driver_available, db_type_allowlist
|
||||||
|
|
||||||
|
|
||||||
|
def build_engine() -> Engine:
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
db_type = os.environ.get("NYAHOME_DB_TYPE", "sqlite")
|
||||||
|
db_driver = os.environ.get("NYAHOME_DB_DRIVER")
|
||||||
|
if db_type not in db_type_allowlist:
|
||||||
|
logger.warning(f"数据库类型 {db_type} 不受 NyaHome 官方支持,建议改用受支持的数据库:{db_type_allowlist}")
|
||||||
|
else:
|
||||||
|
if db_driver is None:
|
||||||
|
db_driver = db_driver_available[db_type][0]
|
||||||
|
|
||||||
|
if db_type == "sqlite":
|
||||||
|
if db_driver == "sqlite3": # fix: sqlalchemy 中使用 pysqlite 表示默认的 sqlite3 标准库驱动,气得我直接缺省算惹
|
||||||
|
return create_engine("sqlite:///.nyahome/nyahome.db")
|
||||||
|
return create_engine(f"sqlite+{db_driver}:///.nyahome/nyahome.db")
|
||||||
|
db_name = os.environ.get("NYAHOME_DB_NAME", "nyahome")
|
||||||
|
db_user = os.environ.get("NYAHOME_DB_USER", "nyahome")
|
||||||
|
db_password = os.environ.get("NYAHOME_DB_PASSWORD", "nyahome")
|
||||||
|
db_host = os.environ.get("NYAHOME_DB_HOST", "localhost")
|
||||||
|
db_port = os.environ.get("NYAHOME_DB_PORT", "3306")
|
||||||
|
return create_engine(f"{db_type}+{db_driver}://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}")
|
||||||
|
|
||||||
|
|
||||||
|
engine = build_engine()
|
||||||
|
|||||||
+18
-4
@@ -12,6 +12,7 @@ from rich.table import Table
|
|||||||
from nyahome import __version__
|
from nyahome import __version__
|
||||||
from nyahome.cli.cli import console
|
from nyahome.cli.cli import console
|
||||||
from nyahome.cli.cli_aii import aii_app
|
from nyahome.cli.cli_aii import aii_app
|
||||||
|
from nyahome.cli.cli_config import config_app
|
||||||
from nyahome.cli.cli_env import ENV_PATH, env_app
|
from nyahome.cli.cli_env import ENV_PATH, env_app
|
||||||
|
|
||||||
app = typer.Typer(
|
app = typer.Typer(
|
||||||
@@ -38,7 +39,7 @@ def main(
|
|||||||
is_eager=True,
|
is_eager=True,
|
||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
console.print("[bright_black]NyaHome 仍然处于极早期的阶段。如果遇到任何问题,请告诉芒果帆帆喵![/bright_black]")
|
console.print("(!) [bright_black]NyaHome 仍然处于极早期的阶段。如果遇到任何问题,请告诉芒果帆帆喵![/bright_black]")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -89,8 +90,10 @@ def init() -> None:
|
|||||||
from dotenv import set_key
|
from dotenv import set_key
|
||||||
from rich.prompt import Confirm, IntPrompt, Prompt
|
from rich.prompt import Confirm, IntPrompt, Prompt
|
||||||
|
|
||||||
from nyahome.cli.cli import DATA_DIR, ENV_PATH, LOGGING_YAML, db_driver_available, db_type_allowlist
|
from nyahome.cli.cli import DATA_DIR, ENV_PATH, LOGGING_YAML
|
||||||
from nyahome.cli.cli_check import LOGGING_YAML_CONTENT
|
from nyahome.cli.cli_check import LOGGING_YAML_CONTENT
|
||||||
|
from nyahome.config import config_manager
|
||||||
|
from nyahome.data import db_driver_available, db_type_allowlist
|
||||||
|
|
||||||
console.print("\n准备初始化 NyaHome。")
|
console.print("\n准备初始化 NyaHome。")
|
||||||
|
|
||||||
@@ -152,6 +155,16 @@ def init() -> None:
|
|||||||
set_key(ENV_PATH, "NYAHOME_UVICORN_RELOAD", "true" if un_reload else "false")
|
set_key(ENV_PATH, "NYAHOME_UVICORN_RELOAD", "true" if un_reload else "false")
|
||||||
console.print("已设置 uvicorn 启动配置。")
|
console.print("已设置 uvicorn 启动配置。")
|
||||||
|
|
||||||
|
# 4.NyaHome 设置初始化
|
||||||
|
console.print("\n4. NyaHome 设置初始化")
|
||||||
|
try:
|
||||||
|
config_manager.sync_load_config()
|
||||||
|
except FileNotFoundError:
|
||||||
|
console.print("配置文件 [cyan].nyahome/config.json[/cyan] 不存在,创建默认配置。")
|
||||||
|
config_manager.sync_save_config()
|
||||||
|
else:
|
||||||
|
console.print("配置文件已存在,跳过。")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def check() -> None:
|
def check() -> None:
|
||||||
@@ -202,8 +215,9 @@ def check() -> None:
|
|||||||
console.print(f"\n[yellow]完成自检,共有 {cw.counter} 个警告。[/yellow]")
|
console.print(f"\n[yellow]完成自检,共有 {cw.counter} 个警告。[/yellow]")
|
||||||
|
|
||||||
|
|
||||||
app.add_typer(env_app, name="env", no_args_is_help=True, help="设置 NyaHome 应用的环境变量。")
|
app.add_typer(config_app, name="config", no_args_is_help=True, help="设置 NyaHome 的设置。(需要初始化)")
|
||||||
app.add_typer(aii_app, name="aii", no_args_is_help=True, help="添加、设置、修改 AI 提供商和模型。")
|
app.add_typer(env_app, name="env", no_args_is_help=True, help="设置 NyaHome 应用的环境变量。(需要初始化)")
|
||||||
|
app.add_typer(aii_app, name="aii", no_args_is_help=True, help="添加、设置、修改 AI 提供商和模型。(需要初始化)")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user