Compare commits
6 Commits
4deee00bcb
...
1f1ac5f87a
| Author | SHA1 | Date | |
|---|---|---|---|
|
1f1ac5f87a
|
|||
|
d62a9d9304
|
|||
|
21cb4ee8c1
|
|||
|
58012e43db
|
|||
|
0796250df8
|
|||
|
1c7d932e31
|
@@ -4,6 +4,22 @@ from sqlalchemy import engine_from_config, pool
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from alembic import context
|
||||
from nyahome.database import ( # noqa
|
||||
AiiModel,
|
||||
AiiModelPublic,
|
||||
AiiProvider,
|
||||
AiiProviderPublic,
|
||||
Chatroom,
|
||||
ChatroomChat,
|
||||
ChatroomChatAccept,
|
||||
ChatroomChatDelete,
|
||||
ChatroomChatEdit,
|
||||
ChatroomPublic,
|
||||
ChatScript,
|
||||
ModelUploadFile,
|
||||
ModelUser,
|
||||
ScriptTemplate,
|
||||
)
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
|
||||
@@ -14,7 +14,9 @@ dependencies = [
|
||||
"jinja2>=3.1.6",
|
||||
"openai>=2.38.0",
|
||||
"passlib[bcrypt]>=1.7.4",
|
||||
"psycopg[binary]>=3.3.4",
|
||||
"pydantic>=2.13.4",
|
||||
"python-dotenv>=1.2.2",
|
||||
"python-jose[cryptography]>=3.5.0",
|
||||
"python-multipart>=0.0.29",
|
||||
"pyyaml>=6.0.3",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
from dotenv import load_dotenv, set_key, unset_key
|
||||
from rich.table import Table
|
||||
|
||||
from .cli import console
|
||||
|
||||
env_app = typer.Typer()
|
||||
|
||||
|
||||
ENV_PATH = Path.cwd() / ".nyahome" / ".env"
|
||||
|
||||
|
||||
@env_app.command(name="list")
|
||||
def list_all_envs() -> None:
|
||||
"""
|
||||
列出所有以 NYAHOME_ 开头的环境变量。
|
||||
|
||||
这些变量可能配置在 .nyahome 内的 .env 文件中,或者通过其他方式预先设置。
|
||||
"""
|
||||
if not load_dotenv(ENV_PATH):
|
||||
console.print(
|
||||
"[bright_black]未在 .nyahome 目录下读取到任何环境变量。如果这是有意为之,则请无需担心。[/bright_black]"
|
||||
)
|
||||
table = Table(title="NyaHome 应用的环境变量")
|
||||
table.add_column("Key", style="cyan", no_wrap=True)
|
||||
table.add_column("Value", style="white")
|
||||
|
||||
for env in os.environ.items():
|
||||
if env[0].startswith("NYAHOME_"):
|
||||
table.add_row(env[0], env[1])
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
@env_app.command(name="set")
|
||||
def set_env(
|
||||
key: Annotated[str, typer.Argument(help="不包含 NYAHOME_ 的键名")],
|
||||
value: Annotated[str, typer.Argument(help="环境变量值")],
|
||||
) -> None:
|
||||
"""
|
||||
设置 NYAHOME_ 环境变量。请参考 NyaHome 文档以了解使用方法。
|
||||
|
||||
保存在 .nyahome 内的 .env 文件。
|
||||
"""
|
||||
set_key(ENV_PATH, f"NYAHOME_{key.upper()}", value)
|
||||
console.print(f"[cyan]已设置环境变量 NYAHOME_{key}。[/cyan]")
|
||||
|
||||
|
||||
@env_app.command(name="unset")
|
||||
def unset_env(key: Annotated[str, typer.Argument(help="不包含 NYAHOME_ 的键名")]) -> None:
|
||||
"""
|
||||
删除 NYAHOME_ 环境变量。
|
||||
|
||||
操作在 .nyahome 内的 .env 文件。
|
||||
"""
|
||||
unset_key(ENV_PATH, f"NYAHOME_{key.upper()}")
|
||||
console.print(f"[cyan]已删除环境变量 NYAHOME_{key}。[/cyan]")
|
||||
@@ -4,7 +4,13 @@ class Config:
|
||||
self.site_url = "http://localhost:5173"
|
||||
self.backend_url = "http://localhost:9000"
|
||||
|
||||
self.default_user_avatar_url = "/nyahome/normal-avatar.png"
|
||||
self.default_user_background_url = "/nyahome/normal-background.png"
|
||||
self.default_chatroom_script_cover_url = "/nyahome/normal-thumbnail.png"
|
||||
self.default_page_cover_url = "/nyahome/normal-header.png"
|
||||
|
||||
self.jwt_secret_key = "see you tomorrow"
|
||||
self.allow_upload_file_extensions = [".jpg", ".jpeg", ".png"]
|
||||
|
||||
self.smtp_enable = False
|
||||
self.smtp_sender = ""
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
sqlite_file_path = Path.cwd() / ".nyahome" / "nyahome.db"
|
||||
|
||||
engine = create_engine(f"sqlite:///{sqlite_file_path!s}", connect_args={"check_same_thread": False})
|
||||
engine = create_engine(os.environ["NYAHOME_SQL_URL"])
|
||||
|
||||
+17
-9
@@ -6,13 +6,13 @@
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from nyahome import __version__
|
||||
from nyahome.cli.cli import console
|
||||
from nyahome.cli.cli_env import ENV_PATH, env_app
|
||||
|
||||
console = Console()
|
||||
app = typer.Typer(
|
||||
name="Nya Home",
|
||||
name="NyaHome",
|
||||
help="🌸 为你而存在的故事之家 ~",
|
||||
rich_markup_mode="rich",
|
||||
no_args_is_help=True,
|
||||
@@ -21,7 +21,7 @@ app = typer.Typer(
|
||||
|
||||
def version_callback(value: bool = False) -> None:
|
||||
if value:
|
||||
console.print(f"[green]Nya Home[/green] version {__version__}")
|
||||
console.print(f"[green]NyaHome[/green] version {__version__}")
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
@@ -35,21 +35,26 @@ def main(
|
||||
is_eager=True,
|
||||
),
|
||||
) -> None:
|
||||
console.print("[bright_black]Nya Home 仍然处于极早期的阶段。如果遇到任何问题,请告诉芒果帆帆喵![/bright_black]")
|
||||
console.print("[bright_black]NyaHome 仍然处于极早期的阶段。如果遇到任何问题,请告诉芒果帆帆喵![/bright_black]")
|
||||
|
||||
|
||||
@app.command()
|
||||
def run() -> None:
|
||||
"""
|
||||
运行 Nya Home。
|
||||
运行 NyaHome。
|
||||
"""
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(ENV_PATH)
|
||||
|
||||
uvicorn.run(
|
||||
"nyahome.server:app",
|
||||
reload=False,
|
||||
host="0.0.0.0",
|
||||
port=9000,
|
||||
reload=os.getenv("NYAHOME_UVICORN_RELOAD", "false") in ["True", "true", "1"],
|
||||
host=os.getenv("NYAHOME_UVICORN_HOST", "0.0.0.0"),
|
||||
port=int(os.getenv("NYAHOME_UVICORN_PORT", "9000")),
|
||||
timeout_graceful_shutdown=2,
|
||||
log_config="logging.yaml",
|
||||
log_level="debug",
|
||||
@@ -69,5 +74,8 @@ def openapi(
|
||||
console.print(f"[cyan]已经保存 openapi.json 到 {path} 。[/cyan]")
|
||||
|
||||
|
||||
app.add_typer(env_app, name="env", no_args_is_help=True, help="设置 NyaHome 应用的环境变量。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
||||
@@ -5,6 +5,7 @@ from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncGenerator
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -21,6 +22,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app_: FastAPI) -> AsyncGenerator[None, Any]:
|
||||
load_dotenv(Path.cwd() / ".nyahome" / ".env")
|
||||
logger.info("🚀 服务启动中...")
|
||||
create_db()
|
||||
await asyncio.gather(init_admin_user(), config_manager.async_load_config())
|
||||
|
||||
@@ -736,7 +736,9 @@ dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "openai" },
|
||||
{ name = "passlib", extra = ["bcrypt"] },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-jose", extra = ["cryptography"] },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -769,7 +771,9 @@ requires-dist = [
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "openai", specifier = ">=2.38.0" },
|
||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.3.4" },
|
||||
{ name = "pydantic", specifier = ">=2.13.4" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
|
||||
{ name = "python-multipart", specifier = ">=0.0.29" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.3" },
|
||||
@@ -848,6 +852,52 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444, upload-time = "2024-12-19T18:22:11.335Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
binary = [
|
||||
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/a6/828c9185701dab71b234c2a76c38a08b098ebfec5020716b4e93807492b5/psycopg_binary-3.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:28b7398fdd19db3232c884fb24550bdfe951221f510e195e233299e4c9b78f97", size = 4607292, upload-time = "2026-05-01T23:30:38.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/58/5b40dbc9d839045c9dae956960e4fb6d20bcabe6c59a2aa34fc3a371913f/psycopg_binary-3.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fbaa292a3c8bb61b45df1ad3da1908ccee7cb889db9425e3557d9e34e2a4829", size = 4687023, upload-time = "2026-05-01T23:30:47.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/a9/793f0ac107a9003b48441d0d1f9f616d96e0f37458dd8dc12528ceff55fb/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94596f9e7633ee3f6440711d43bb70aa31cc0a46a900ab8b4201a366ace5c9e7", size = 5486985, upload-time = "2026-05-01T23:30:55.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/26/42e8533497e2592334f68ec529cf5f840f7fa4e99575a4bb61aa184dbfbf/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c0056529e68dbe9184cd4019a1f3d8f3a4ead2f6fc7a5afcf27d3314edd1277", size = 5168745, upload-time = "2026-05-01T23:31:01.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/af/b7151776cc08d5935d45c833ec818a9beb417cf7c08239af1aafbdae78ee/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c09aad7051326e7603c14e50636db9c01f78272dc54b3accff03d46370461e6", size = 6761486, upload-time = "2026-05-01T23:31:14.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/ed/c92533b9124712d592cbf1cd6c76da933a2e0acea81dfe1fbe7e735f0cff/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:514404ed543efd620c85602b747df2a23cf1241b4067199e1a66f2d2757aaa41", size = 4997427, upload-time = "2026-05-01T23:31:20.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/23/ccadfd0de416aa188356daa199453af24087b042e296088706d190ae0295/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:46893c26858be12cc49ca4226ed6a60b4bfccadd946b3bebb783a60b38788228", size = 4533549, upload-time = "2026-05-01T23:31:26.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/a0/c8f43cee36386f7bc891ab41a9d31ea07cf9826038e732da79f26b1e5f34/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:df1d567fc430f6df15c9fcf67d87685fc49bdb325adc0db5af1adfb2f44eb5c9", size = 4210256, upload-time = "2026-05-01T23:31:33.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/2c/c1547871be3790676e8868b38655496422f94f0978dfb66b74bdba2f1676/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6b9016b1714da4dd5ecaaa75b82098aa5a0b87854ce9b092e21c27c4ae23e014", size = 3946204, upload-time = "2026-05-01T23:31:39.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/b1/f6670f00fa7ea601584623f6c11602ab92117d83eaff885e0210f6de7418/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:47c656a8a7ba6eb0cff1801a4caaa9c8bdc12d03080e273aff1c8ac39971a77e", size = 4255811, upload-time = "2026-05-01T23:31:44.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/e6/5fff07a70d1f945ed90ae131c3bd76cab32beff7c58c6db15ad5820b6d1f/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8", size = 3666849, upload-time = "2026-05-01T23:31:51.165Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.3"
|
||||
@@ -946,6 +996,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.5.0"
|
||||
@@ -1290,6 +1349,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2026.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.47.0"
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"sortImports": true
|
||||
}
|
||||
|
||||
Vendored
+12
@@ -21,11 +21,13 @@ declare module 'vue' {
|
||||
ChatPromptQuicker: typeof import('./src/components/chatroom/ChatPromptQuicker.vue')['default']
|
||||
ChatroomCard: typeof import('./src/components/chatroom/ChatroomCard.vue')['default']
|
||||
ChatroomCreatorModal: typeof import('./src/components/chatroom/ChatroomCreatorModal.vue')['default']
|
||||
ChatroomEditorModal: typeof import('./src/components/chatroom/ChatroomEditorModal.vue')['default']
|
||||
ChatTable: typeof import('./src/components/chatroom/ChatTable.vue')['default']
|
||||
ConfigCard: typeof import('./src/components/admin/ConfigCard.vue')['default']
|
||||
FileModal: typeof import('./src/components/file/FileModal.vue')['default']
|
||||
FileThumbnail: typeof import('./src/components/file/FileThumbnail.vue')['default']
|
||||
InDev: typeof import('./src/components/InDev.vue')['default']
|
||||
InputFile: typeof import('./src/components/file/InputFile.vue')['default']
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
@@ -36,6 +38,7 @@ declare module 'vue' {
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||
NDynamicTags: typeof import('naive-ui')['NDynamicTags']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NFlex: typeof import('naive-ui')['NFlex']
|
||||
@@ -46,6 +49,7 @@ declare module 'vue' {
|
||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||
NH2: typeof import('naive-ui')['NH2']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NH4: typeof import('naive-ui')['NH4']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
@@ -55,6 +59,7 @@ declare module 'vue' {
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
NP: typeof import('naive-ui')['NP']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
@@ -64,6 +69,7 @@ declare module 'vue' {
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NText: typeof import('naive-ui')['NText']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NUpload: typeof import('naive-ui')['NUpload']
|
||||
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
|
||||
PageHeader: typeof import('./src/components/PageHeader.vue')['default']
|
||||
@@ -91,11 +97,13 @@ declare global {
|
||||
const ChatPromptQuicker: typeof import('./src/components/chatroom/ChatPromptQuicker.vue')['default']
|
||||
const ChatroomCard: typeof import('./src/components/chatroom/ChatroomCard.vue')['default']
|
||||
const ChatroomCreatorModal: typeof import('./src/components/chatroom/ChatroomCreatorModal.vue')['default']
|
||||
const ChatroomEditorModal: typeof import('./src/components/chatroom/ChatroomEditorModal.vue')['default']
|
||||
const ChatTable: typeof import('./src/components/chatroom/ChatTable.vue')['default']
|
||||
const ConfigCard: typeof import('./src/components/admin/ConfigCard.vue')['default']
|
||||
const FileModal: typeof import('./src/components/file/FileModal.vue')['default']
|
||||
const FileThumbnail: typeof import('./src/components/file/FileThumbnail.vue')['default']
|
||||
const InDev: typeof import('./src/components/InDev.vue')['default']
|
||||
const InputFile: typeof import('./src/components/file/InputFile.vue')['default']
|
||||
const NAlert: typeof import('naive-ui')['NAlert']
|
||||
const NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
const NButton: typeof import('naive-ui')['NButton']
|
||||
@@ -106,6 +114,7 @@ declare global {
|
||||
const NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
const NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
const NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||
const NDynamicTags: typeof import('naive-ui')['NDynamicTags']
|
||||
const NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
const NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
const NFlex: typeof import('naive-ui')['NFlex']
|
||||
@@ -116,6 +125,7 @@ declare global {
|
||||
const NGridItem: typeof import('naive-ui')['NGridItem']
|
||||
const NH2: typeof import('naive-ui')['NH2']
|
||||
const NH3: typeof import('naive-ui')['NH3']
|
||||
const NH4: typeof import('naive-ui')['NH4']
|
||||
const NImage: typeof import('naive-ui')['NImage']
|
||||
const NInput: typeof import('naive-ui')['NInput']
|
||||
const NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
@@ -125,6 +135,7 @@ declare global {
|
||||
const NModal: typeof import('naive-ui')['NModal']
|
||||
const NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
const NP: typeof import('naive-ui')['NP']
|
||||
const NPopover: typeof import('naive-ui')['NPopover']
|
||||
const NRadio: typeof import('naive-ui')['NRadio']
|
||||
const NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
@@ -134,6 +145,7 @@ declare global {
|
||||
const NTabs: typeof import('naive-ui')['NTabs']
|
||||
const NTag: typeof import('naive-ui')['NTag']
|
||||
const NText: typeof import('naive-ui')['NText']
|
||||
const NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
const NUpload: typeof import('naive-ui')['NUpload']
|
||||
const NUploadDragger: typeof import('naive-ui')['NUploadDragger']
|
||||
const PageHeader: typeof import('./src/components/PageHeader.vue')['default']
|
||||
|
||||
+5
-4
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { dateZhCN, zhCN } from 'naive-ui'
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { onMounted } from 'vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { dateZhCN, zhCN } from 'naive-ui'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
|
||||
const NOWUSER = useNowUser()
|
||||
|
||||
|
||||
+34
-14
@@ -1,6 +1,9 @@
|
||||
div.message {
|
||||
position: relative;
|
||||
padding: 4px 12px;
|
||||
padding: 0 12px;
|
||||
border-width: 0 0 0 4px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
height: max-content;
|
||||
|
||||
@@ -18,35 +21,52 @@ div.message {
|
||||
}
|
||||
|
||||
div.user-message {
|
||||
border: 2px solid #e1ff20;
|
||||
border-radius: 4px;
|
||||
background: #fffbb1;
|
||||
border-color: #e1ff20;
|
||||
background: #fffbdb;
|
||||
}
|
||||
|
||||
div.aii-message {
|
||||
border: 2px solid #20ff54;
|
||||
border-radius: 4px;
|
||||
background: #b1ffd0;
|
||||
border-color: #20ff54;
|
||||
background: #e6fff1;
|
||||
}
|
||||
|
||||
div.aii-message-streaming {
|
||||
border: 2px solid #20d2ff;
|
||||
border-radius: 4px;
|
||||
background: #b1f8ff;
|
||||
border-color: #20d2ff;
|
||||
background: #dbfaff;
|
||||
|
||||
div.thinking {
|
||||
border: 1px solid #e9ff20;
|
||||
border: solid #5f20ff;
|
||||
border-width: 0 0 0 2px;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
padding: 8px 12px;
|
||||
margin: 6px 3px;
|
||||
background: rgb(34 197 94 / 0.2);
|
||||
background: rgb(205 127 255 / 0.2);
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠消息
|
||||
div.collapse {
|
||||
overflow: hidden;
|
||||
min-height: 80px;
|
||||
min-height: 160px;
|
||||
|
||||
&::after {
|
||||
content: '● ● ●';
|
||||
text-align: center;
|
||||
display: block;
|
||||
position: absolute;
|
||||
font-size: 18px;
|
||||
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
background: rgb(255 255 255 / 0.6);
|
||||
border: 1px solid rgb(255 255 255);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
div.xaml-block {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { createErrorBlock, createXamlBlock, type Xaml } from '@/components/xaml-block.tsx'
|
||||
|
||||
const showModal = defineModel('showModal', { required: true })
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import VerifyCodeModal from '@/components/admin/VerifyCodeModal.vue'
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
const NOWUSER = useNowUser()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { AxiosError } from 'axios'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
const NOWUSER = useNowUser()
|
||||
@@ -20,7 +21,7 @@ const loginForm = ref({
|
||||
|
||||
function login() {
|
||||
api
|
||||
.post(`/admin/login/${loginMethod.value}`, {
|
||||
.post(`/admin/login/${loginMethod.value}/`, {
|
||||
username: loginForm.value.username,
|
||||
password: loginForm.value.password,
|
||||
})
|
||||
@@ -50,11 +51,18 @@ function login() {
|
||||
MESSAGE.error(`登录失败:${err_msg}`)
|
||||
})
|
||||
}
|
||||
|
||||
function logout() {
|
||||
NOWUSER.is_login = false
|
||||
localStorage.removeItem('user-id')
|
||||
localStorage.removeItem('access-token')
|
||||
MESSAGE.success('已注销登录状态……')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="user-action nyahome-card" v-if="NOWUSER.isLogin" style="position: relative">
|
||||
<div class="user-action nyahome-card" v-if="NOWUSER.is_login" style="position: relative">
|
||||
<img :src="NOWUSER.background_url" alt="User Background" class="user-action-background" />
|
||||
<div class="card-content" style="margin-top: auto; margin-bottom: 20px">
|
||||
<n-avatar :size="96" circle :src="NOWUSER.avatar_url" />
|
||||
@@ -69,9 +77,19 @@ function login() {
|
||||
<router-link class="card-button" :to="`/user/${NOWUSER.id}`">
|
||||
<n-button type="info" style="width: 100%" secondary>主页</n-button>
|
||||
</router-link>
|
||||
<router-link class="card-button" to="#">
|
||||
<n-button type="error" style="width: 100%" secondary>注销</n-button>
|
||||
</router-link>
|
||||
<n-popover trigger="click">
|
||||
<template #trigger>
|
||||
<n-button class="card-button" type="error" style="width: 100%; padding: 0" secondary>
|
||||
注销
|
||||
</n-button>
|
||||
</template>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info">
|
||||
确认注销登录吗?注销后,你可以重新登录,或者切换至其他账号。
|
||||
</n-alert>
|
||||
<n-button type="warning" secondary @click="logout()">确认注销</n-button>
|
||||
</n-flex>
|
||||
</n-popover>
|
||||
</n-flex>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { api } from '@/tools/web.ts'
|
||||
|
||||
const ROUTER = useRouter()
|
||||
const MESSAGE = useMessage()
|
||||
const NOWUSER = useNowUser()
|
||||
@@ -21,7 +22,7 @@ function change() {
|
||||
.post('/admin/me/password/', JSON.stringify(changeForm.value))
|
||||
.then(() => {
|
||||
MESSAGE.success('密码修改成功,请重新登录。')
|
||||
NOWUSER.isLogin = false
|
||||
NOWUSER.is_login = false
|
||||
localStorage.removeItem('user-id')
|
||||
localStorage.removeItem('access-token')
|
||||
ROUTER.push('/')
|
||||
@@ -46,8 +47,8 @@ function change() {
|
||||
<n-flex>
|
||||
<n-button type="error" @click="change()">确认修改</n-button>
|
||||
<n-tag type="warning" size="large"
|
||||
>修改密码会注销所有已登录状态,您将需要重新登录。</n-tag
|
||||
>
|
||||
>修改密码会注销所有已登录状态,您将需要重新登录。
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { type SelectOption, useMessage } from 'naive-ui'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import AiiProviderAddModal from '@/components/chatroom/AiiProviderAddModal.vue'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { type SelectOption, useMessage } from 'naive-ui'
|
||||
import type { AiiProviderPublicWithoutKey } from '@/types/aii.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { type SelectOption, useMessage } from 'naive-ui'
|
||||
import AiiModelAddModal from '@/components/chatroom/AiiModelAddModal.vue'
|
||||
import type { AiiModelPublic } from '@/types/aii.js'
|
||||
import ChatPromptQuicker from '@/components/chatroom/ChatPromptQuicker.vue'
|
||||
import ScriptDrawer from '@/components/chatroom/ScriptDrawer.vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import AiiModelAddModal from '@/components/chatroom/AiiModelAddModal.vue'
|
||||
import ChatPromptQuicker from '@/components/chatroom/ChatPromptQuicker.vue'
|
||||
import ChatroomEditorModal from '@/components/chatroom/ChatroomEditorModal.vue'
|
||||
import ScriptDrawer from '@/components/chatroom/ScriptDrawer.vue'
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { AiiModelPublic } from '@/types/aii.js'
|
||||
import type { Chatroom, ChatroomPublic } from '@/types/chatroom.ts'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
const NOWUSER = useNowUser()
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const selectedModel = defineModel<number | null>('selectModel', { required: true })
|
||||
const quickerPrompt = defineModel<string>('quickerPrompt', { required: true })
|
||||
|
||||
const { script } = defineProps<{
|
||||
script: string
|
||||
const { chatroom, loadPage } = defineProps<{
|
||||
chatroom: Chatroom
|
||||
loadPage: () => void
|
||||
}>()
|
||||
|
||||
const showModal = ref(false)
|
||||
const showAddModelModal = ref(false)
|
||||
const models = ref<AiiModelPublic[]>([])
|
||||
const showScriptDrawer = ref(false)
|
||||
|
||||
@@ -32,7 +38,7 @@ const modelOptions = computed(() => {
|
||||
return options
|
||||
})
|
||||
|
||||
function load() {
|
||||
function loadModels() {
|
||||
api
|
||||
.get('/aii/model')
|
||||
.then((res) => res.data as ReturnDto)
|
||||
@@ -49,8 +55,42 @@ function load() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
loadModels()
|
||||
if (chatroom.default_model_id) {
|
||||
selectedModel.value = chatroom.default_model_id
|
||||
} else {
|
||||
MESSAGE.info(
|
||||
'此聊天室还未设置默认模型。你需要选择一个模型然后开始聊天,或者现在就保存一个默认模型嘛?',
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const showChatroomInfoModal = ref(false)
|
||||
const chatroomInfo = ref<ChatroomPublic>({
|
||||
id: chatroom.id,
|
||||
name: chatroom.name,
|
||||
description: chatroom.description,
|
||||
feature_image: chatroom.feature_image,
|
||||
default_model_id: chatroom.default_model_id,
|
||||
script_template_id: chatroom.script_template_id,
|
||||
script_template_version: chatroom.script_template_version,
|
||||
})
|
||||
|
||||
function saveDefaultModel() {
|
||||
if (selectedModel.value) {
|
||||
chatroomInfo.value.default_model_id = selectedModel.value
|
||||
api
|
||||
.post(`/chatroom/${chatroom.id}/`, JSON.stringify(chatroomInfo.value))
|
||||
.then(() => {
|
||||
MESSAGE.success('默认模型设置成功~')
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`设置默认模型失败:${err}`)
|
||||
})
|
||||
} else {
|
||||
MESSAGE.warning('请先选择一个模型哦~')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -58,18 +98,37 @@ onMounted(() => {
|
||||
<n-card title="模型">
|
||||
<template #header-extra>
|
||||
<n-flex>
|
||||
<n-button secondary type="info" size="small" round @click="load()">刷新</n-button>
|
||||
<n-button secondary type="warning" size="small" round @click="showModal = true">
|
||||
<n-button secondary type="info" size="small" round @click="loadModels()">刷新</n-button>
|
||||
<n-button
|
||||
v-if="NOWUSER.is_admin"
|
||||
secondary
|
||||
type="warning"
|
||||
size="small"
|
||||
round
|
||||
@click="showAddModelModal = true"
|
||||
>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button-group>
|
||||
<n-button secondary type="primary" size="small" round>保存</n-button>
|
||||
<n-button secondary type="tertiary" size="small" round>?</n-button>
|
||||
<n-button secondary type="primary" size="small" round @click="saveDefaultModel()">
|
||||
保存
|
||||
</n-button>
|
||||
<n-popover>
|
||||
<template #trigger>
|
||||
<n-button secondary type="tertiary" size="small" round>?</n-button>
|
||||
</template>
|
||||
<n-h4>有哪些模型?</n-h4>
|
||||
<n-p>NyaHome 管理员可以添加模型,然后所有用户都可以使用这些模型。</n-p>
|
||||
<n-p>NyaHome 不提供模型调用。</n-p>
|
||||
<n-h4>默认模型?切换模型?</n-h4>
|
||||
<n-p>可以针对聊天室保存一个默认模型。</n-p>
|
||||
<n-p>你也可以在这里切换其他的模型。但在保存之前,默认模型不会修改。</n-p>
|
||||
</n-popover>
|
||||
</n-button-group>
|
||||
</n-flex>
|
||||
</template>
|
||||
<n-select v-model:value="selectedModel" :options="modelOptions" />
|
||||
<aii-model-add-modal v-model:show-modal="showModal" />
|
||||
<aii-model-add-modal v-model:show-modal="showAddModelModal" />
|
||||
</n-card>
|
||||
|
||||
<chat-prompt-quicker v-model:prompt-prefix="quickerPrompt" />
|
||||
@@ -82,15 +141,22 @@ onMounted(() => {
|
||||
故事设定 · 世界书
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<script-drawer :script v-model:show-drawer="showScriptDrawer" />
|
||||
<script-drawer :script="chatroom.script" v-model:show-drawer="showScriptDrawer" />
|
||||
</n-card>
|
||||
|
||||
<n-card title="设置">
|
||||
<template #header-extra>也许你不需要修改这里</template>
|
||||
<n-flex vertical>
|
||||
<n-button secondary type="primary">聊天室信息</n-button>
|
||||
<n-button secondary type="primary" @click="showChatroomInfoModal = true">
|
||||
聊天室信息
|
||||
</n-button>
|
||||
<n-button secondary type="info">系统设置</n-button>
|
||||
</n-flex>
|
||||
<chatroom-editor-modal
|
||||
:chatroom="chatroomInfo"
|
||||
v-model:show-modal="showChatroomInfoModal"
|
||||
:reload="loadPage"
|
||||
/>
|
||||
</n-card>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { md } from '@/tools/md.js'
|
||||
import { onMounted, ref, useTemplateRef } from 'vue'
|
||||
|
||||
import { md } from '@/tools/md.js'
|
||||
|
||||
const { role, msg } = defineProps<{
|
||||
role: 'aii' | 'user'
|
||||
msg: string
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const promptPrefix = defineModel<string>('promptPrefix', { required: true })
|
||||
|
||||
@@ -8,12 +11,31 @@ const quickerForm = ref({
|
||||
style: '第三人称全知视角,禁止打破第四面墙。',
|
||||
})
|
||||
|
||||
function save() {
|
||||
promptPrefix.value = `<要求><输出字数>${quickerForm.value.length}</输出字数><风格约束>${quickerForm.value.style}</风格约束></要求>`
|
||||
function saveToBrowser() {
|
||||
localStorage.setItem('prompt-quicker', JSON.stringify(quickerForm.value))
|
||||
MESSAGE.success('已保存快速提示词至浏览器。')
|
||||
}
|
||||
|
||||
function loadFromBrowser() {
|
||||
const temp = localStorage.getItem('prompt-quicker')
|
||||
if (temp) {
|
||||
quickerForm.value = JSON.parse(temp)
|
||||
MESSAGE.success('从浏览器中读取到保存的快速提示词。')
|
||||
} else {
|
||||
MESSAGE.info('未找到保存的快速提示词。')
|
||||
}
|
||||
}
|
||||
|
||||
// 将本组件中进行的对快速提示词的修改自动同步到父级
|
||||
watch(
|
||||
() => quickerForm.value,
|
||||
(newValue) => {
|
||||
promptPrefix.value = `<要求><输出字数>${newValue.length}</输出字数><风格约束>${newValue.style}</风格约束></要求>`
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
save()
|
||||
loadFromBrowser()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -21,8 +43,17 @@ onMounted(() => {
|
||||
<n-card title="快速调整">
|
||||
<template #header-extra>
|
||||
<n-button-group>
|
||||
<n-button secondary type="primary" size="small" round @click="save()">保存</n-button>
|
||||
<n-button secondary type="tertiary" size="small" round>?</n-button>
|
||||
<n-button secondary type="primary" size="small" round @click="saveToBrowser()">
|
||||
保存
|
||||
</n-button>
|
||||
<n-popover>
|
||||
<template #trigger>
|
||||
<n-button secondary type="tertiary" size="small" round>?</n-button>
|
||||
</template>
|
||||
<n-h4>这是什么?</n-h4>
|
||||
<n-p>快速预设一些可能需要随时修改的提示词。它们会被结构化地拼接在用户消息中。</n-p>
|
||||
<n-p>您的修改无需保存即可随请求发送。保存可以将提示词存储在浏览器中。</n-p>
|
||||
</n-popover>
|
||||
</n-button-group>
|
||||
</template>
|
||||
<n-form :model="quickerForm">
|
||||
|
||||
@@ -1,42 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { ChatroomPublic } from '@/types/chatroom.js'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import UploadFileModal from '@/components/file/UploadFileModal.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import InputFile from '@/components/file/InputFile.vue'
|
||||
import SelectFileModal from '@/components/file/SelectFileModal.vue'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import UploadFileModal from '@/components/file/UploadFileModal.vue'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ChatroomPublic } from '@/types/chatroom.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel<boolean>('showModal', { required: true })
|
||||
|
||||
const showSelectModal = ref(false)
|
||||
const showUploadModal = ref(false)
|
||||
const files = ref<UploadFileDto[]>([])
|
||||
const selectFiles = ref<UploadFileDto[]>([])
|
||||
const image_url = computed(() => selectFiles.value.at(0)?.download_url)
|
||||
|
||||
const createChatroomForm = ref<ChatroomPublic>({
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
feature_image: '',
|
||||
script_template_id: 0,
|
||||
script_template_version: '',
|
||||
})
|
||||
|
||||
watch(image_url, () => {
|
||||
if (image_url.value) {
|
||||
createChatroomForm.value.feature_image = image_url.value
|
||||
}
|
||||
})
|
||||
|
||||
async function loadFiles() {
|
||||
return await api.get('/file/').then((res) => (files.value = res.data as UploadFileDto[]))
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
api
|
||||
.post('/chatroom/', JSON.stringify(createChatroomForm.value))
|
||||
@@ -76,14 +59,7 @@ function onSubmit() {
|
||||
<n-input type="textarea" v-model:value="createChatroomForm.description" />
|
||||
</n-form-item>
|
||||
<n-form-item path="feature_image" label="特色图像">
|
||||
<n-flex style="width: 100%" :wrap="false">
|
||||
<n-input
|
||||
v-model:value="createChatroomForm.feature_image"
|
||||
placeholder="留空以使用默认图像"
|
||||
/>
|
||||
<n-button secondary type="info" @click="showSelectModal = true">选择</n-button>
|
||||
<n-button secondary type="warning" @click="showUploadModal = true">上传</n-button>
|
||||
</n-flex>
|
||||
<input-file v-model:value="createChatroomForm.feature_image" />
|
||||
</n-form-item>
|
||||
<n-form-item label="确认?">
|
||||
<n-button secondary type="primary" @click="onSubmit()">确认!</n-button>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
import InputFile from '@/components/file/InputFile.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ChatroomPublic } from '@/types/chatroom.ts'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const showModal = defineModel('showModal', { required: true })
|
||||
|
||||
const { chatroom, reload } = defineProps<{
|
||||
chatroom: ChatroomPublic
|
||||
reload: () => void
|
||||
}>()
|
||||
|
||||
function submit() {
|
||||
api
|
||||
.post(`/chatroom/${chatroom.id}/`, JSON.stringify(chatroom))
|
||||
.then(() => {
|
||||
MESSAGE.success('成功修改聊天室基本信息~')
|
||||
showModal.value = false
|
||||
reload() // 由调用方重新获取聊天室数据
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`修改聊天室基本信息失败:${err}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
title="修改聊天室信息"
|
||||
style="width: 800px; max-height: 600px"
|
||||
content-scrollable
|
||||
preset="card"
|
||||
>
|
||||
<n-form label-placement="left" label-align="right" label-width="auto" :model="chatroom">
|
||||
<n-form-item label="聊天室名称" path="name">
|
||||
<n-input v-model:value="chatroom.name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="聊天室描述" path="description">
|
||||
<n-input v-model:value="chatroom.description" type="textarea" :rows="3" />
|
||||
</n-form-item>
|
||||
<n-form-item label="特色图片" path="feature_image">
|
||||
<input-file v-model:value="chatroom.feature_image" />
|
||||
</n-form-item>
|
||||
<n-form-item label="保存">
|
||||
<n-button secondary type="primary" @click="submit()">保存</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { api } from '@/tools/web.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import XamlModal from '@/components/XamlModal.vue'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { ChatScript } from '@/types/chatroom.js'
|
||||
import type { ReturnDto } from '@/types/response.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import XamlModal from '@/components/XamlModal.vue'
|
||||
|
||||
const ROUTE = useRoute()
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
|
||||
const NOWUSER = useNowUser()
|
||||
|
||||
const { file } = defineProps<{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue'
|
||||
|
||||
import FileModal from '@/components/file/FileModal.vue'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
|
||||
const { file, size, enableSelect, onSelect, onRemove } = defineProps<{
|
||||
file: UploadFileDto
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import SelectFileModal from '@/components/file/SelectFileModal.vue'
|
||||
import UploadFileModal from '@/components/file/UploadFileModal.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { UploadFileDto } from '@/types/user.ts'
|
||||
|
||||
const value = defineModel('value', { required: true })
|
||||
|
||||
const showSelectModal = ref(false)
|
||||
const showUploadModal = ref(false)
|
||||
const selectFiles = ref<UploadFileDto[]>([])
|
||||
|
||||
async function loadFiles() {
|
||||
return await api.get('/file/').then((res) => res.data as UploadFileDto[])
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectFiles.value,
|
||||
() => {
|
||||
value.value = selectFiles.value[0]?.download_url
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex style="width: 100%" :wrap="false">
|
||||
<n-input v-model:value="value" placeholder="留空以使用默认图像" />
|
||||
<n-button secondary type="info" @click="showSelectModal = true">选择</n-button>
|
||||
<n-button secondary type="warning" @click="showUploadModal = true">上传</n-button>
|
||||
</n-flex>
|
||||
|
||||
<select-file-modal
|
||||
:max="1"
|
||||
:extensions="['png', 'jpeg', 'jpg']"
|
||||
:load-files="loadFiles"
|
||||
v-model:show-modal="showSelectModal"
|
||||
v-model:select-files="selectFiles"
|
||||
/>
|
||||
<upload-file-modal v-model:show-modal="showUploadModal" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { selectFilesCom } from '@/components/file/upload-files.js'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { selectFilesCom } from '@/components/file/upload-files.js'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { type UploadCustomRequestOptions, type UploadFileInfo } from 'naive-ui'
|
||||
import { shallowRef, useTemplateRef } from 'vue'
|
||||
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import { shallowRef, useTemplateRef } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
afterLeave?: () => void
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { UploadFileDto } from '@/types/user.ts'
|
||||
import FileThumbnail from '@/components/file/FileThumbnail.vue'
|
||||
import { NEmpty, NFlex } from 'naive-ui'
|
||||
|
||||
import FileThumbnail from '@/components/file/FileThumbnail.vue'
|
||||
import type { UploadFileDto } from '@/types/user.ts'
|
||||
|
||||
export function uploadFilesCom(files: UploadFileDto[]) {
|
||||
if (files.length === 0) {
|
||||
return <NEmpty description="你还没有上传任何文件。" size="large" />
|
||||
|
||||
+2
-3
@@ -1,11 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createHead } from '@unhead/vue/client'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import '@/assets/main.scss'
|
||||
import '@/assets/beautiful.scss'
|
||||
import '@/assets/chat.scss'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@unhead/vue'
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import UserAction from '@/components/admin/UserAction.vue'
|
||||
import type {MenuOption} from 'naive-ui'
|
||||
import {computed, onMounted, ref, useTemplateRef} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useNowUser} from '@/stores/now-user.js'
|
||||
import {useHead} from '@unhead/vue'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
|
||||
useHead({
|
||||
titleTemplate: '%s | 管理面板 | NayHome',
|
||||
@@ -76,16 +77,16 @@ onMounted(() => {
|
||||
<template>
|
||||
<div id="user-page">
|
||||
<div id="user-page-sidebar">
|
||||
<user-action/>
|
||||
<user-action />
|
||||
<div class="nyahome-card">
|
||||
<n-menu ref="menu" v-model:value="selectOption" :options @update:value="handleMenuClick"/>
|
||||
<n-menu ref="menu" v-model:value="selectOption" :options @update:value="handleMenuClick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-view v-slot="{ Component }">
|
||||
<div id="user-page-content">
|
||||
<keep-alive>
|
||||
<component :is="Component"/>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</router-view>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import {useRoute} from 'vue-router'
|
||||
import {onMounted, reactive, ref, useTemplateRef, watch} from 'vue'
|
||||
import {api} from '@/tools/web.ts'
|
||||
import type {ReturnDto} from '@/types/response.ts'
|
||||
import type {Chatroom} from '@/types/chatroom.ts'
|
||||
import {useMessage} from 'naive-ui'
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { onMounted, reactive, ref, useTemplateRef, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import ChatControlPanel from '@/components/chatroom/ChatControlPanel.vue'
|
||||
import ChatroomCard from '@/components/chatroom/ChatroomCard.vue'
|
||||
import ChatTable from '@/components/chatroom/ChatTable.vue'
|
||||
import ChatControlPanel from '@/components/chatroom/ChatControlPanel.vue'
|
||||
import {fetchEventSource} from '@microsoft/fetch-event-source'
|
||||
import type {AiiTokenInfo} from '@/types/aii.ts'
|
||||
import {SEE_YOU_TOMORROW} from '@/types/syt.ts'
|
||||
import {useHead} from '@unhead/vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { AiiTokenInfo } from '@/types/aii.ts'
|
||||
import type { Chatroom } from '@/types/chatroom.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
import { SEE_YOU_TOMORROW } from '@/types/syt.ts'
|
||||
|
||||
const pageHead = reactive({
|
||||
title: '正在加载聊天室...',
|
||||
titleTemplate: '%s | 聊天室 | NyaHome'
|
||||
titleTemplate: '%s | 聊天室 | NyaHome',
|
||||
})
|
||||
|
||||
useHead(pageHead)
|
||||
@@ -23,11 +24,7 @@ useHead(pageHead)
|
||||
const ROUTE = useRoute()
|
||||
const MESSAGE = useMessage()
|
||||
|
||||
const crName = ref('')
|
||||
const crDescription = ref('')
|
||||
const crFeatureImage = ref('')
|
||||
const crContent = ref('')
|
||||
const crScript = ref('')
|
||||
const chatroom = ref<Chatroom | null>(null)
|
||||
|
||||
const selectedModel = ref<number | null>(null)
|
||||
const quickerPrompt = ref('')
|
||||
@@ -54,11 +51,7 @@ function load() {
|
||||
})
|
||||
.then((cr) => {
|
||||
pageHead.title = cr.name
|
||||
crName.value = cr.name
|
||||
crDescription.value = cr.description
|
||||
crFeatureImage.value = cr.feature_image
|
||||
crContent.value = cr.content
|
||||
crScript.value = cr.script
|
||||
chatroom.value = cr
|
||||
})
|
||||
.catch((e) => {
|
||||
MESSAGE.error(`访问聊天室失败:${e}`)
|
||||
@@ -92,6 +85,7 @@ function chat() {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${localStorage.getItem('access-token')}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: inputMessage.value,
|
||||
@@ -154,7 +148,7 @@ function accept() {
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
crContent.value = result.content
|
||||
chatroom.value!.content = result.content
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`保存失败:${err}`)
|
||||
@@ -189,8 +183,8 @@ function messageEdit(oldMessage: string, newMessage: string, change: 'aii' | 'us
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
crContent.value = result.content
|
||||
MESSAGE.success('聊天记录已删除,页面已更新~')
|
||||
chatroom.value!.content = result.content
|
||||
MESSAGE.success('聊天记录已更新,页面已更新~')
|
||||
})
|
||||
.catch((err) => {
|
||||
MESSAGE.error(`修改聊天消息失败:${err}`)
|
||||
@@ -200,7 +194,7 @@ function messageEdit(oldMessage: string, newMessage: string, change: 'aii' | 'us
|
||||
function messageDelete(message: string, change: 'aii' | 'user') {
|
||||
const id = Number(ROUTE.params.id)
|
||||
api
|
||||
.post(`/chatroom/${id}/chat/delete/`, JSON.stringify({message, change}))
|
||||
.post(`/chatroom/${id}/chat/delete/`, JSON.stringify({ message, change }))
|
||||
.then((res) => res.data as ReturnDto)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
@@ -210,7 +204,7 @@ function messageDelete(message: string, change: 'aii' | 'user') {
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
crContent.value = result.content
|
||||
chatroom.value!.content = result.content
|
||||
MESSAGE.success('聊天记录已删除,页面已更新~')
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -242,16 +236,16 @@ function enableSidebar() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-container" v-if="chatroom !== null">
|
||||
<div class="main-column">
|
||||
<chatroom-card
|
||||
:id="Number(ROUTE.params.id)"
|
||||
:name="crName"
|
||||
:description="crDescription"
|
||||
:feature_image="crFeatureImage"
|
||||
:name="chatroom.name"
|
||||
:description="chatroom.description"
|
||||
:feature_image="chatroom.feature_image"
|
||||
/>
|
||||
<chat-table
|
||||
:content="crContent"
|
||||
:content="chatroom.content"
|
||||
:aii-thinking
|
||||
:aii-message
|
||||
:aii-token-info
|
||||
@@ -264,15 +258,16 @@ function enableSidebar() {
|
||||
:on-message-edit="messageEdit"
|
||||
:on-message-delete="messageDelete"
|
||||
/>
|
||||
<div id="main-toggle" ref="main-toggle" @click="enableSidebar"/>
|
||||
<div id="main-toggle" ref="main-toggle" @click="enableSidebar" />
|
||||
</div>
|
||||
<div class="sidebar-column" ref="sidebar">
|
||||
<chat-control-panel
|
||||
:script="crScript"
|
||||
:chatroom="chatroom"
|
||||
:load-page="load"
|
||||
v-model:quicker-prompt="quickerPrompt"
|
||||
v-model:select-model="selectedModel"
|
||||
/>
|
||||
<div id="sidebar-toggle" @click="disableSidebar"/>
|
||||
<div id="sidebar-toggle" @click="disableSidebar" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -310,13 +305,14 @@ div.page-container {
|
||||
border-radius: 5px;
|
||||
opacity: var(--opacity);
|
||||
|
||||
transition: background-color 0.8s,
|
||||
transform 0.5s,
|
||||
opacity 1s,
|
||||
height 0.5s,
|
||||
width 0.5s,
|
||||
top 0.5s,
|
||||
border-radius 0.5s;
|
||||
transition:
|
||||
background-color 0.8s,
|
||||
transform 0.5s,
|
||||
opacity 1s,
|
||||
height 0.5s,
|
||||
width 0.5s,
|
||||
top 0.5s,
|
||||
border-radius 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: rgb(0 0 0 / 0.6);
|
||||
@@ -339,9 +335,10 @@ div.page-container {
|
||||
|
||||
position: relative;
|
||||
|
||||
transition: transform 1s,
|
||||
opacity 1s,
|
||||
flex-basis 1s;
|
||||
transition:
|
||||
transform 1s,
|
||||
opacity 1s,
|
||||
flex-basis 1s;
|
||||
transform: translateX(var(--transform-x));
|
||||
flex-basis: var(--width);
|
||||
opacity: var(--opacity);
|
||||
@@ -356,12 +353,13 @@ div.page-container {
|
||||
background: rgb(0 0 0 / 0.1);
|
||||
border-radius: 5px;
|
||||
|
||||
transition: background-color 0.8s,
|
||||
transform 0.5s,
|
||||
height 0.5s,
|
||||
width 0.5s,
|
||||
top 0.5s,
|
||||
border-radius 0.5s;
|
||||
transition:
|
||||
background-color 0.8s,
|
||||
transform 0.5s,
|
||||
height 0.5s,
|
||||
width 0.5s,
|
||||
top 0.5s,
|
||||
border-radius 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: rgb(0 0 0 / 0.6);
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ChatroomCard from '@/components/chatroom/ChatroomCard.vue'
|
||||
import {ref, watch} from 'vue'
|
||||
import {api} from '@/tools/web.ts'
|
||||
import type {ChatroomPublic} from '@/types/chatroom.ts'
|
||||
import type {ReturnDto} from '@/types/response.ts'
|
||||
import ChatroomCreatorModal from '@/components/chatroom/ChatroomCreatorModal.vue'
|
||||
import {useNowUser} from '@/stores/now-user.ts'
|
||||
import {useHead} from "@unhead/vue";
|
||||
import { useNowUser } from '@/stores/now-user.ts'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ChatroomPublic } from '@/types/chatroom.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
|
||||
useHead({
|
||||
title: "聊天室列表",
|
||||
title: '聊天室列表',
|
||||
})
|
||||
|
||||
const NOWUSER = useNowUser()
|
||||
@@ -29,11 +30,11 @@ function load() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => NOWUSER.isLogin,
|
||||
() => NOWUSER.is_login,
|
||||
() => {
|
||||
load()
|
||||
},
|
||||
{immediate: true},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -60,7 +61,7 @@ watch(
|
||||
/>
|
||||
</div>
|
||||
|
||||
<chatroom-creator-modal v-model:show-modal="showModal"/>
|
||||
<chatroom-creator-modal v-model:show-modal="showModal" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@unhead/vue'
|
||||
|
||||
import InDev from '@/components/InDev.vue'
|
||||
|
||||
useHead({
|
||||
title: '剧本市场',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import UserAction from '@/components/admin/UserAction.vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
|
||||
import UserAction from '@/components/admin/UserAction.vue'
|
||||
|
||||
useHead({
|
||||
title: '首页',
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import ConfigCard from '@/components/admin/ConfigCard.vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { ref } from 'vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ConfigCard from '@/components/admin/ConfigCard.vue'
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import type { ReturnDto } from '@/types/response.ts'
|
||||
|
||||
interface SiteConfig {
|
||||
@@ -12,7 +13,13 @@ interface SiteConfig {
|
||||
site_url: string
|
||||
backend_url: string
|
||||
|
||||
default_user_avatar_url: string
|
||||
default_user_background_url: string
|
||||
default_chatroom_script_cover_url: string
|
||||
default_page_cover_url: string
|
||||
|
||||
jwt_secret_key: string
|
||||
allow_upload_file_extensions: string[]
|
||||
|
||||
smtp_enable: boolean
|
||||
smtp_sender: string
|
||||
@@ -101,6 +108,11 @@ function sendTestMail() {
|
||||
<n-form-item label="站点名称">
|
||||
<n-input v-model:value="siteConfig.site_name" />
|
||||
</n-form-item>
|
||||
<n-alert type="info" class="in-form-alert">
|
||||
您在浏览器中访问本站点时使用的地址(主机名/域名)。
|
||||
如果需要前后端分开部署,则是前端所在地址。<br />
|
||||
就默认而言,这两个地址应当一致。
|
||||
</n-alert>
|
||||
<n-form-item label="站点地址">
|
||||
<n-input v-model:value="siteConfig.site_url" />
|
||||
</n-form-item>
|
||||
@@ -120,6 +132,25 @@ function sendTestMail() {
|
||||
</n-flex>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="custom" tab="站点定制" display-directive="show">
|
||||
<config-card title="默认图片">
|
||||
<n-form>
|
||||
<n-form-item label="默认用户头像">
|
||||
<n-input v-model:value="siteConfig.default_user_avatar_url" />
|
||||
</n-form-item>
|
||||
<n-form-item label="默认用户背景">
|
||||
<n-input v-model:value="siteConfig.default_user_background_url" />
|
||||
</n-form-item>
|
||||
<n-form-item label="默认聊天室和剧本封面">
|
||||
<n-input v-model:value="siteConfig.default_chatroom_script_cover_url" />
|
||||
</n-form-item>
|
||||
<n-form-item label="默认页面封面">
|
||||
<n-input v-model:value="siteConfig.default_page_cover_url" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</config-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="permission" tab="权限设置" display-directive="show">
|
||||
<config-card title="用户权限">
|
||||
<in-dev />
|
||||
@@ -135,7 +166,19 @@ function sendTestMail() {
|
||||
修改此密钥会导致所有用户的登录状态丢失(你也会),请一次性设置一个足够安全的。
|
||||
</n-alert>
|
||||
<n-form-item label="JWT 密钥">
|
||||
<n-input v-model:value="siteConfig.jwt_secret_key" />
|
||||
<n-input
|
||||
v-model:value="siteConfig.jwt_secret_key"
|
||||
type="password"
|
||||
show-password-toggle
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</config-card>
|
||||
|
||||
<config-card title="文件上传">
|
||||
<n-form>
|
||||
<n-form-item label="允许上传的文件类型(拓展名)">
|
||||
<n-dynamic-tags v-model:value="siteConfig.allow_upload_file_extensions" type="info" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</config-card>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { computed } from 'vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
|
||||
useHead({
|
||||
title: '总览',
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import {useNowUser} from '@/stores/now-user.js'
|
||||
import {ref, watch} from 'vue'
|
||||
import SelectFileModal from '@/components/file/SelectFileModal.vue'
|
||||
import {api} from '@/tools/web.js'
|
||||
import type {UploadFileDto, UserDto} from '@/types/user.js'
|
||||
import {useHead} from '@unhead/vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ChangeEmailModal from '@/components/admin/ChangeEmailModal.vue'
|
||||
import ChangePhoneModal from '@/components/admin/ChangePhoneModal.vue'
|
||||
import SelectFileModal from '@/components/file/SelectFileModal.vue'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { UploadFileDto, UserDto } from '@/types/user.js'
|
||||
|
||||
useHead({
|
||||
title: '用户资料',
|
||||
@@ -65,11 +66,11 @@ watch(
|
||||
)
|
||||
|
||||
watch(
|
||||
() => NOWUSER.isLogin,
|
||||
() => NOWUSER.is_login,
|
||||
() => {
|
||||
reInitForm()
|
||||
},
|
||||
{immediate: true},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function save() {
|
||||
@@ -91,14 +92,14 @@ async function save() {
|
||||
<div class="ui-content">
|
||||
<n-form style="width: 450px" label-width="auto" label-placement="left" label-align="right">
|
||||
<n-form-item label="用户名">
|
||||
<n-input v-model:value="infoForm.name"/>
|
||||
<n-input v-model:value="infoForm.name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="展示名称">
|
||||
<n-input v-model:value="infoForm.display_name"/>
|
||||
<n-input v-model:value="infoForm.display_name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="头像">
|
||||
<n-flex>
|
||||
<n-avatar v-model:src="infoForm.avatar_url" :size="96" circle/>
|
||||
<n-avatar v-model:src="infoForm.avatar_url" :size="96" circle />
|
||||
<n-flex vertical>
|
||||
<n-tag type="info">需在「内容-上传」中提前上传图像。</n-tag>
|
||||
<n-tag type="warning">使用方形图像以获得最佳效果。</n-tag>
|
||||
@@ -108,7 +109,7 @@ async function save() {
|
||||
secondary
|
||||
type="tertiary"
|
||||
@click="infoForm.avatar_url = NOWUSER.avatar_url"
|
||||
>重置
|
||||
>重置
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
@@ -116,7 +117,7 @@ async function save() {
|
||||
</n-form-item>
|
||||
<n-form-item label="个人背景">
|
||||
<n-flex>
|
||||
<n-avatar v-model:src="infoForm.background_url" :size="96" object-fit="cover"/>
|
||||
<n-avatar v-model:src="infoForm.background_url" :size="96" object-fit="cover" />
|
||||
<n-flex vertical>
|
||||
<n-tag type="info">需在「内容-上传」中提前上传图像。</n-tag>
|
||||
<n-flex>
|
||||
@@ -125,7 +126,7 @@ async function save() {
|
||||
secondary
|
||||
type="tertiary"
|
||||
@click="infoForm.background_url = NOWUSER.background_url"
|
||||
>重置
|
||||
>重置
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
@@ -139,10 +140,10 @@ async function save() {
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="邮箱">
|
||||
<n-input v-model:value="NOWUSER.email" disabled/>
|
||||
<n-input v-model:value="NOWUSER.email" disabled />
|
||||
</n-form-item>
|
||||
<n-form-item label="手机号">
|
||||
<n-input v-model:value="NOWUSER.phone" disabled/>
|
||||
<n-input v-model:value="NOWUSER.phone" disabled />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-flex>
|
||||
@@ -171,8 +172,8 @@ async function save() {
|
||||
v-model:show-modal="showBackgroundModal"
|
||||
v-model:select-files="background_selectFiles"
|
||||
/>
|
||||
<change-email-modal v-model:show-modal="showChangeEmailModal"/>
|
||||
<change-phone-modal v-model:show-modal="showChangePhoneModal"/>
|
||||
<change-email-modal v-model:show-modal="showChangeEmailModal" />
|
||||
<change-phone-modal v-model:show-modal="showChangePhoneModal" />
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
|
||||
import InDev from '@/components/InDev.vue'
|
||||
|
||||
useHead({
|
||||
title: '剧本',
|
||||
})
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import UserPasswordModal from '@/components/admin/UserPasswordModal.vue'
|
||||
import { h, ref } from 'vue'
|
||||
import { api } from '@/tools/web.ts'
|
||||
import { type DataTableColumn, NTag, NText } from 'naive-ui'
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { type DataTableColumn, NTag, NText } from 'naive-ui'
|
||||
import { h, ref } from 'vue'
|
||||
|
||||
import UserPasswordModal from '@/components/admin/UserPasswordModal.vue'
|
||||
import InDev from '@/components/InDev.vue'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { api } from '@/tools/web.ts'
|
||||
|
||||
useHead({
|
||||
title: '用户安全',
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { uploadFilesCom } from '@/components/file/upload-files.js'
|
||||
import UploadFileModal from '@/components/file/UploadFileModal.vue'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { api } from '@/tools/web.js'
|
||||
import type { UploadFileDto } from '@/types/user.js'
|
||||
import { useNowUser } from '@/stores/now-user.js'
|
||||
import { uploadFilesCom } from '@/components/file/upload-files.js'
|
||||
import { useHead } from '@unhead/vue'
|
||||
|
||||
useHead({
|
||||
title: '上传',
|
||||
@@ -24,7 +25,7 @@ function load() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => NOWUSER.isLogin,
|
||||
() => NOWUSER.is_login,
|
||||
() => {
|
||||
load()
|
||||
},
|
||||
@@ -39,7 +40,7 @@ watch(
|
||||
</template>
|
||||
|
||||
<n-flex vertical>
|
||||
<n-alert type="info"> 接受的文件类型: </n-alert>
|
||||
<n-alert type="info"> 接受的文件类型:</n-alert>
|
||||
<n-button @click="showUploadModal = true">打开上传向导</n-button>
|
||||
|
||||
<upload-file-modal
|
||||
@@ -57,7 +58,7 @@ watch(
|
||||
<template #header>
|
||||
<n-h3 prefix="bar" style="margin: 0">个人文件库</n-h3>
|
||||
</template>
|
||||
<template #header-extra> 您已经上传的文件都在这里,可以选择性地删除以及重新下载。 </template>
|
||||
<template #header-extra> 您已经上传的文件都在这里,可以选择性地删除以及重新下载。</template>
|
||||
|
||||
<component :is="uploadFilesCom(files)" />
|
||||
</n-card>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import ChatroomPage from '@/pages/ChatroomPage.vue'
|
||||
import WelcomePage from '@/pages/WelcomePage.vue'
|
||||
import Chatroom1Page from '@/pages/Chatroom1Page.vue'
|
||||
import AdminPage from '@/pages/AdminPage.vue'
|
||||
|
||||
import AdminNyahome from '@/pages/admin/AdminNyahome.vue'
|
||||
import AdminOverview from '@/pages/admin/AdminOverview.vue'
|
||||
import AdminUserInfo from '@/pages/admin/AdminUserInfo.vue'
|
||||
import AdminUserScript from '@/pages/admin/AdminUserScript.vue'
|
||||
import AdminUserSecurity from '@/pages/admin/AdminUserSecurity.vue'
|
||||
import AdminUserUpload from '@/pages/admin/AdminUserUpload.vue'
|
||||
import AdminNyahome from '@/pages/admin/AdminNyahome.vue'
|
||||
import AdminUserScript from '@/pages/admin/AdminUserScript.vue'
|
||||
import AdminPage from '@/pages/AdminPage.vue'
|
||||
import Chatroom1Page from '@/pages/Chatroom1Page.vue'
|
||||
import ChatroomPage from '@/pages/ChatroomPage.vue'
|
||||
import Marketplace from '@/pages/Marketplace.vue'
|
||||
import WelcomePage from '@/pages/WelcomePage.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { api, setApiToken } from '@/tools/web.ts'
|
||||
import type { UserDto } from '@/types/user.ts'
|
||||
|
||||
export const useNowUser = defineStore('now-user', () => {
|
||||
const isLogin = ref(false)
|
||||
const is_login = ref(false)
|
||||
|
||||
const id = ref(0)
|
||||
const name = ref('')
|
||||
@@ -41,11 +42,11 @@ export const useNowUser = defineStore('now-user', () => {
|
||||
description.value = user.description
|
||||
is_admin.value = user.is_admin
|
||||
|
||||
isLogin.value = true
|
||||
is_login.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
isLogin,
|
||||
is_login,
|
||||
id,
|
||||
name,
|
||||
display_name,
|
||||
|
||||
@@ -4,8 +4,10 @@ export interface ChatroomPublic {
|
||||
description: string
|
||||
feature_image: string
|
||||
|
||||
script_template_id: number
|
||||
script_template_version: string
|
||||
script_template_id?: number
|
||||
script_template_version?: string
|
||||
|
||||
default_model_id?: number
|
||||
}
|
||||
|
||||
export interface Chatroom extends ChatroomPublic {
|
||||
|
||||
Reference in New Issue
Block a user