AstrBot 4.11.3__py3-none-any.whl → 4.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +10 -8
- astrbot/core/config/default.py +66 -13
- astrbot/core/db/__init__.py +84 -2
- astrbot/core/db/po.py +65 -0
- astrbot/core/db/sqlite.py +225 -4
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +103 -49
- astrbot/core/pipeline/process_stage/utils.py +40 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +2 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -0
- astrbot/core/platform/sources/webchat/webchat_adapter.py +3 -2
- astrbot/core/platform/sources/webchat/webchat_event.py +17 -4
- astrbot/core/provider/sources/anthropic_source.py +44 -0
- astrbot/core/sandbox/booters/base.py +31 -0
- astrbot/core/sandbox/booters/boxlite.py +186 -0
- astrbot/core/sandbox/booters/shipyard.py +67 -0
- astrbot/core/sandbox/olayer/__init__.py +5 -0
- astrbot/core/sandbox/olayer/filesystem.py +33 -0
- astrbot/core/sandbox/olayer/python.py +19 -0
- astrbot/core/sandbox/olayer/shell.py +21 -0
- astrbot/core/sandbox/sandbox_client.py +52 -0
- astrbot/core/sandbox/tools/__init__.py +10 -0
- astrbot/core/sandbox/tools/fs.py +188 -0
- astrbot/core/sandbox/tools/python.py +74 -0
- astrbot/core/sandbox/tools/shell.py +55 -0
- astrbot/core/star/context.py +162 -44
- astrbot/core/utils/metrics.py +2 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +40 -12
- astrbot/dashboard/routes/chatui_project.py +245 -0
- astrbot/dashboard/routes/session_management.py +545 -0
- astrbot/dashboard/server.py +1 -0
- {astrbot-4.11.3.dist-info → astrbot-4.12.0.dist-info}/METADATA +2 -3
- {astrbot-4.11.3.dist-info → astrbot-4.12.0.dist-info}/RECORD +37 -28
- astrbot/builtin_stars/python_interpreter/main.py +0 -536
- astrbot/builtin_stars/python_interpreter/metadata.yaml +0 -4
- astrbot/builtin_stars/python_interpreter/requirements.txt +0 -1
- astrbot/builtin_stars/python_interpreter/shared/api.py +0 -22
- {astrbot-4.11.3.dist-info → astrbot-4.12.0.dist-info}/WHEEL +0 -0
- {astrbot-4.11.3.dist-info → astrbot-4.12.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.11.3.dist-info → astrbot-4.12.0.dist-info}/licenses/LICENSE +0 -0
astrbot/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.
|
|
1
|
+
__version__ = "4.12.0"
|
|
@@ -227,7 +227,8 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
227
227
|
encrypted=llm_resp.reasoning_signature,
|
|
228
228
|
)
|
|
229
229
|
)
|
|
230
|
-
|
|
230
|
+
if llm_resp.completion_text:
|
|
231
|
+
parts.append(TextPart(text=llm_resp.completion_text))
|
|
231
232
|
self.run_context.messages.append(Message(role="assistant", content=parts))
|
|
232
233
|
|
|
233
234
|
# call the on_agent_done hook
|
|
@@ -277,7 +278,8 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
277
278
|
encrypted=llm_resp.reasoning_signature,
|
|
278
279
|
)
|
|
279
280
|
)
|
|
280
|
-
|
|
281
|
+
if llm_resp.completion_text:
|
|
282
|
+
parts.append(TextPart(text=llm_resp.completion_text))
|
|
281
283
|
tool_calls_result = ToolCallsResult(
|
|
282
284
|
tool_calls_info=AssistantMessageSegment(
|
|
283
285
|
tool_calls=llm_resp.to_openai_to_calls_model(),
|
|
@@ -361,7 +363,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
361
363
|
ToolCallMessageSegment(
|
|
362
364
|
role="tool",
|
|
363
365
|
tool_call_id=func_tool_id,
|
|
364
|
-
content=f"error:
|
|
366
|
+
content=f"error: Tool {func_tool_name} not found.",
|
|
365
367
|
),
|
|
366
368
|
)
|
|
367
369
|
continue
|
|
@@ -427,7 +429,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
427
429
|
ToolCallMessageSegment(
|
|
428
430
|
role="tool",
|
|
429
431
|
tool_call_id=func_tool_id,
|
|
430
|
-
content="
|
|
432
|
+
content="The tool has successfully returned an image and sent directly to the user. You can describe it in your next response.",
|
|
431
433
|
),
|
|
432
434
|
)
|
|
433
435
|
yield MessageChain(type="tool_direct_result").base64_image(
|
|
@@ -452,7 +454,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
452
454
|
ToolCallMessageSegment(
|
|
453
455
|
role="tool",
|
|
454
456
|
tool_call_id=func_tool_id,
|
|
455
|
-
content="
|
|
457
|
+
content="The tool has successfully returned an image and sent directly to the user. You can describe it in your next response.",
|
|
456
458
|
),
|
|
457
459
|
)
|
|
458
460
|
yield MessageChain(
|
|
@@ -463,7 +465,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
463
465
|
ToolCallMessageSegment(
|
|
464
466
|
role="tool",
|
|
465
467
|
tool_call_id=func_tool_id,
|
|
466
|
-
content="
|
|
468
|
+
content="The tool has returned a data type that is not supported.",
|
|
467
469
|
),
|
|
468
470
|
)
|
|
469
471
|
|
|
@@ -480,7 +482,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
480
482
|
ToolCallMessageSegment(
|
|
481
483
|
role="tool",
|
|
482
484
|
tool_call_id=func_tool_id,
|
|
483
|
-
content="
|
|
485
|
+
content="The tool has no return value, or has sent the result directly to the user.",
|
|
484
486
|
),
|
|
485
487
|
)
|
|
486
488
|
else:
|
|
@@ -492,7 +494,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
492
494
|
ToolCallMessageSegment(
|
|
493
495
|
role="tool",
|
|
494
496
|
tool_call_id=func_tool_id,
|
|
495
|
-
content="
|
|
497
|
+
content="*The tool has returned an unsupported type. Please tell the user to check the definition and implementation of this tool.*",
|
|
496
498
|
),
|
|
497
499
|
)
|
|
498
500
|
|
astrbot/core/config/default.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
|
|
|
5
5
|
|
|
6
6
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
7
7
|
|
|
8
|
-
VERSION = "4.
|
|
8
|
+
VERSION = "4.12.0"
|
|
9
9
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
10
10
|
|
|
11
11
|
WEBHOOK_SUPPORTED_PLATFORMS = [
|
|
@@ -113,6 +113,14 @@ DEFAULT_CONFIG = {
|
|
|
113
113
|
"provider": "moonshotai",
|
|
114
114
|
"moonshotai_api_key": "",
|
|
115
115
|
},
|
|
116
|
+
"sandbox": {
|
|
117
|
+
"enable": False,
|
|
118
|
+
"booter": "shipyard",
|
|
119
|
+
"shipyard_endpoint": "",
|
|
120
|
+
"shipyard_access_token": "",
|
|
121
|
+
"shipyard_ttl": 3600,
|
|
122
|
+
"shipyard_max_sessions": 10,
|
|
123
|
+
},
|
|
116
124
|
},
|
|
117
125
|
"provider_stt_settings": {
|
|
118
126
|
"enable": False,
|
|
@@ -242,7 +250,7 @@ CONFIG_METADATA_2 = {
|
|
|
242
250
|
"callback_server_host": "0.0.0.0",
|
|
243
251
|
"port": 6196,
|
|
244
252
|
},
|
|
245
|
-
"OneBot v11
|
|
253
|
+
"OneBot v11": {
|
|
246
254
|
"id": "default",
|
|
247
255
|
"type": "aiocqhttp",
|
|
248
256
|
"enable": False,
|
|
@@ -989,17 +997,6 @@ CONFIG_METADATA_2 = {
|
|
|
989
997
|
"api_base": "http://127.0.0.1:1234/v1",
|
|
990
998
|
"custom_headers": {},
|
|
991
999
|
},
|
|
992
|
-
"ModelStack": {
|
|
993
|
-
"id": "modelstack",
|
|
994
|
-
"provider": "modelstack",
|
|
995
|
-
"type": "openai_chat_completion",
|
|
996
|
-
"provider_type": "chat_completion",
|
|
997
|
-
"enable": True,
|
|
998
|
-
"key": [],
|
|
999
|
-
"api_base": "https://modelstack.app/v1",
|
|
1000
|
-
"timeout": 120,
|
|
1001
|
-
"custom_headers": {},
|
|
1002
|
-
},
|
|
1003
1000
|
"Gemini_OpenAI_API": {
|
|
1004
1001
|
"id": "google_gemini_openai",
|
|
1005
1002
|
"provider": "google",
|
|
@@ -2550,6 +2547,62 @@ CONFIG_METADATA_3 = {
|
|
|
2550
2547
|
# "provider_settings.enable": True,
|
|
2551
2548
|
# },
|
|
2552
2549
|
# },
|
|
2550
|
+
"sandbox": {
|
|
2551
|
+
"description": "Agent 沙箱环境",
|
|
2552
|
+
"type": "object",
|
|
2553
|
+
"items": {
|
|
2554
|
+
"provider_settings.sandbox.enable": {
|
|
2555
|
+
"description": "启用沙箱环境",
|
|
2556
|
+
"type": "bool",
|
|
2557
|
+
"hint": "启用后,Agent 可以使用沙箱环境中的工具和资源,如 Python 代码执行、Shell 等。",
|
|
2558
|
+
},
|
|
2559
|
+
"provider_settings.sandbox.booter": {
|
|
2560
|
+
"description": "沙箱环境驱动器",
|
|
2561
|
+
"type": "string",
|
|
2562
|
+
"options": ["shipyard"],
|
|
2563
|
+
"condition": {
|
|
2564
|
+
"provider_settings.sandbox.enable": True,
|
|
2565
|
+
},
|
|
2566
|
+
},
|
|
2567
|
+
"provider_settings.sandbox.shipyard_endpoint": {
|
|
2568
|
+
"description": "Shipyard API Endpoint",
|
|
2569
|
+
"type": "string",
|
|
2570
|
+
"hint": "Shipyard 服务的 API 访问地址。",
|
|
2571
|
+
"condition": {
|
|
2572
|
+
"provider_settings.sandbox.enable": True,
|
|
2573
|
+
"provider_settings.sandbox.booter": "shipyard",
|
|
2574
|
+
},
|
|
2575
|
+
"_special": "check_shipyard_connection",
|
|
2576
|
+
},
|
|
2577
|
+
"provider_settings.sandbox.shipyard_access_token": {
|
|
2578
|
+
"description": "Shipyard Access Token",
|
|
2579
|
+
"type": "string",
|
|
2580
|
+
"hint": "用于访问 Shipyard 服务的访问令牌。",
|
|
2581
|
+
"condition": {
|
|
2582
|
+
"provider_settings.sandbox.enable": True,
|
|
2583
|
+
"provider_settings.sandbox.booter": "shipyard",
|
|
2584
|
+
},
|
|
2585
|
+
},
|
|
2586
|
+
"provider_settings.sandbox.shipyard_ttl": {
|
|
2587
|
+
"description": "Shipyard Session TTL",
|
|
2588
|
+
"type": "int",
|
|
2589
|
+
"hint": "Shipyard 会话的生存时间(秒)。",
|
|
2590
|
+
"condition": {
|
|
2591
|
+
"provider_settings.sandbox.enable": True,
|
|
2592
|
+
"provider_settings.sandbox.booter": "shipyard",
|
|
2593
|
+
},
|
|
2594
|
+
},
|
|
2595
|
+
"provider_settings.sandbox.shipyard_max_sessions": {
|
|
2596
|
+
"description": "Shipyard Max Sessions",
|
|
2597
|
+
"type": "int",
|
|
2598
|
+
"hint": "Shipyard 最大会话数量。",
|
|
2599
|
+
"condition": {
|
|
2600
|
+
"provider_settings.sandbox.enable": True,
|
|
2601
|
+
"provider_settings.sandbox.booter": "shipyard",
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
},
|
|
2605
|
+
},
|
|
2553
2606
|
"truncate_and_compress": {
|
|
2554
2607
|
"description": "上下文管理策略",
|
|
2555
2608
|
"type": "object",
|
astrbot/core/db/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_asyn
|
|
|
9
9
|
|
|
10
10
|
from astrbot.core.db.po import (
|
|
11
11
|
Attachment,
|
|
12
|
+
ChatUIProject,
|
|
12
13
|
CommandConfig,
|
|
13
14
|
CommandConflict,
|
|
14
15
|
ConversationV2,
|
|
@@ -17,6 +18,7 @@ from astrbot.core.db.po import (
|
|
|
17
18
|
PlatformSession,
|
|
18
19
|
PlatformStat,
|
|
19
20
|
Preference,
|
|
21
|
+
SessionProjectRelation,
|
|
20
22
|
Stats,
|
|
21
23
|
)
|
|
22
24
|
|
|
@@ -446,8 +448,11 @@ class BaseDatabase(abc.ABC):
|
|
|
446
448
|
platform_id: str | None = None,
|
|
447
449
|
page: int = 1,
|
|
448
450
|
page_size: int = 20,
|
|
449
|
-
) -> list[
|
|
450
|
-
"""Get all Platform sessions for a specific creator (username) and optionally platform.
|
|
451
|
+
) -> list[dict]:
|
|
452
|
+
"""Get all Platform sessions for a specific creator (username) and optionally platform.
|
|
453
|
+
|
|
454
|
+
Returns a list of dicts containing session info and project info (if session belongs to a project).
|
|
455
|
+
"""
|
|
451
456
|
...
|
|
452
457
|
|
|
453
458
|
@abc.abstractmethod
|
|
@@ -463,3 +468,80 @@ class BaseDatabase(abc.ABC):
|
|
|
463
468
|
async def delete_platform_session(self, session_id: str) -> None:
|
|
464
469
|
"""Delete a Platform session by its ID."""
|
|
465
470
|
...
|
|
471
|
+
|
|
472
|
+
# ====
|
|
473
|
+
# ChatUI Project Management
|
|
474
|
+
# ====
|
|
475
|
+
|
|
476
|
+
@abc.abstractmethod
|
|
477
|
+
async def create_chatui_project(
|
|
478
|
+
self,
|
|
479
|
+
creator: str,
|
|
480
|
+
title: str,
|
|
481
|
+
emoji: str | None = "📁",
|
|
482
|
+
description: str | None = None,
|
|
483
|
+
) -> ChatUIProject:
|
|
484
|
+
"""Create a new ChatUI project."""
|
|
485
|
+
...
|
|
486
|
+
|
|
487
|
+
@abc.abstractmethod
|
|
488
|
+
async def get_chatui_project_by_id(self, project_id: str) -> ChatUIProject | None:
|
|
489
|
+
"""Get a ChatUI project by its ID."""
|
|
490
|
+
...
|
|
491
|
+
|
|
492
|
+
@abc.abstractmethod
|
|
493
|
+
async def get_chatui_projects_by_creator(
|
|
494
|
+
self,
|
|
495
|
+
creator: str,
|
|
496
|
+
page: int = 1,
|
|
497
|
+
page_size: int = 100,
|
|
498
|
+
) -> list[ChatUIProject]:
|
|
499
|
+
"""Get all ChatUI projects for a specific creator."""
|
|
500
|
+
...
|
|
501
|
+
|
|
502
|
+
@abc.abstractmethod
|
|
503
|
+
async def update_chatui_project(
|
|
504
|
+
self,
|
|
505
|
+
project_id: str,
|
|
506
|
+
title: str | None = None,
|
|
507
|
+
emoji: str | None = None,
|
|
508
|
+
description: str | None = None,
|
|
509
|
+
) -> None:
|
|
510
|
+
"""Update a ChatUI project."""
|
|
511
|
+
...
|
|
512
|
+
|
|
513
|
+
@abc.abstractmethod
|
|
514
|
+
async def delete_chatui_project(self, project_id: str) -> None:
|
|
515
|
+
"""Delete a ChatUI project by its ID."""
|
|
516
|
+
...
|
|
517
|
+
|
|
518
|
+
@abc.abstractmethod
|
|
519
|
+
async def add_session_to_project(
|
|
520
|
+
self,
|
|
521
|
+
session_id: str,
|
|
522
|
+
project_id: str,
|
|
523
|
+
) -> SessionProjectRelation:
|
|
524
|
+
"""Add a session to a project."""
|
|
525
|
+
...
|
|
526
|
+
|
|
527
|
+
@abc.abstractmethod
|
|
528
|
+
async def remove_session_from_project(self, session_id: str) -> None:
|
|
529
|
+
"""Remove a session from its project."""
|
|
530
|
+
...
|
|
531
|
+
|
|
532
|
+
@abc.abstractmethod
|
|
533
|
+
async def get_project_sessions(
|
|
534
|
+
self,
|
|
535
|
+
project_id: str,
|
|
536
|
+
page: int = 1,
|
|
537
|
+
page_size: int = 100,
|
|
538
|
+
) -> list[PlatformSession]:
|
|
539
|
+
"""Get all sessions in a project."""
|
|
540
|
+
...
|
|
541
|
+
|
|
542
|
+
@abc.abstractmethod
|
|
543
|
+
async def get_project_by_session(
|
|
544
|
+
self, session_id: str, creator: str
|
|
545
|
+
) -> ChatUIProject | None:
|
|
546
|
+
"""Get the project that a session belongs to."""
|
|
547
|
+
...
|
astrbot/core/db/po.py
CHANGED
|
@@ -239,6 +239,71 @@ class Attachment(SQLModel, table=True):
|
|
|
239
239
|
)
|
|
240
240
|
|
|
241
241
|
|
|
242
|
+
class ChatUIProject(SQLModel, table=True):
|
|
243
|
+
"""This class represents projects for organizing ChatUI conversations.
|
|
244
|
+
|
|
245
|
+
Projects allow users to group related conversations together.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
__tablename__: str = "chatui_projects"
|
|
249
|
+
|
|
250
|
+
inner_id: int | None = Field(
|
|
251
|
+
primary_key=True,
|
|
252
|
+
sa_column_kwargs={"autoincrement": True},
|
|
253
|
+
default=None,
|
|
254
|
+
)
|
|
255
|
+
project_id: str = Field(
|
|
256
|
+
max_length=36,
|
|
257
|
+
nullable=False,
|
|
258
|
+
unique=True,
|
|
259
|
+
default_factory=lambda: str(uuid.uuid4()),
|
|
260
|
+
)
|
|
261
|
+
creator: str = Field(nullable=False)
|
|
262
|
+
"""Username of the project creator"""
|
|
263
|
+
emoji: str | None = Field(default="📁", max_length=10)
|
|
264
|
+
"""Emoji icon for the project"""
|
|
265
|
+
title: str = Field(nullable=False, max_length=255)
|
|
266
|
+
"""Title of the project"""
|
|
267
|
+
description: str | None = Field(default=None, max_length=1000)
|
|
268
|
+
"""Description of the project"""
|
|
269
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
270
|
+
updated_at: datetime = Field(
|
|
271
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
272
|
+
sa_column_kwargs={"onupdate": datetime.now(timezone.utc)},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
__table_args__ = (
|
|
276
|
+
UniqueConstraint(
|
|
277
|
+
"project_id",
|
|
278
|
+
name="uix_chatui_project_id",
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class SessionProjectRelation(SQLModel, table=True):
|
|
284
|
+
"""This class represents the relationship between platform sessions and ChatUI projects."""
|
|
285
|
+
|
|
286
|
+
__tablename__: str = "session_project_relations"
|
|
287
|
+
|
|
288
|
+
id: int | None = Field(
|
|
289
|
+
primary_key=True,
|
|
290
|
+
sa_column_kwargs={"autoincrement": True},
|
|
291
|
+
default=None,
|
|
292
|
+
)
|
|
293
|
+
session_id: str = Field(nullable=False, max_length=100)
|
|
294
|
+
"""Session ID from PlatformSession"""
|
|
295
|
+
project_id: str = Field(nullable=False, max_length=36)
|
|
296
|
+
"""Project ID from ChatUIProject"""
|
|
297
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
298
|
+
|
|
299
|
+
__table_args__ = (
|
|
300
|
+
UniqueConstraint(
|
|
301
|
+
"session_id",
|
|
302
|
+
name="uix_session_project_relation",
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
242
307
|
class CommandConfig(SQLModel, table=True):
|
|
243
308
|
"""Per-command configuration overrides for dashboard management."""
|
|
244
309
|
|
astrbot/core/db/sqlite.py
CHANGED
|
@@ -11,6 +11,7 @@ from sqlmodel import col, delete, desc, func, or_, select, text, update
|
|
|
11
11
|
from astrbot.core.db import BaseDatabase
|
|
12
12
|
from astrbot.core.db.po import (
|
|
13
13
|
Attachment,
|
|
14
|
+
ChatUIProject,
|
|
14
15
|
CommandConfig,
|
|
15
16
|
CommandConflict,
|
|
16
17
|
ConversationV2,
|
|
@@ -19,6 +20,7 @@ from astrbot.core.db.po import (
|
|
|
19
20
|
PlatformSession,
|
|
20
21
|
PlatformStat,
|
|
21
22
|
Preference,
|
|
23
|
+
SessionProjectRelation,
|
|
22
24
|
SQLModel,
|
|
23
25
|
)
|
|
24
26
|
from astrbot.core.db.po import (
|
|
@@ -1060,12 +1062,35 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
1060
1062
|
platform_id: str | None = None,
|
|
1061
1063
|
page: int = 1,
|
|
1062
1064
|
page_size: int = 20,
|
|
1063
|
-
) -> list[
|
|
1064
|
-
"""Get all Platform sessions for a specific creator (username) and optionally platform.
|
|
1065
|
+
) -> list[dict]:
|
|
1066
|
+
"""Get all Platform sessions for a specific creator (username) and optionally platform.
|
|
1067
|
+
|
|
1068
|
+
Returns a list of dicts containing session info and project info (if session belongs to a project).
|
|
1069
|
+
"""
|
|
1065
1070
|
async with self.get_db() as session:
|
|
1066
1071
|
session: AsyncSession
|
|
1067
1072
|
offset = (page - 1) * page_size
|
|
1068
|
-
|
|
1073
|
+
|
|
1074
|
+
# LEFT JOIN with SessionProjectRelation and ChatUIProject to get project info
|
|
1075
|
+
query = (
|
|
1076
|
+
select(
|
|
1077
|
+
PlatformSession,
|
|
1078
|
+
col(ChatUIProject.project_id),
|
|
1079
|
+
col(ChatUIProject.title).label("project_title"),
|
|
1080
|
+
col(ChatUIProject.emoji).label("project_emoji"),
|
|
1081
|
+
)
|
|
1082
|
+
.outerjoin(
|
|
1083
|
+
SessionProjectRelation,
|
|
1084
|
+
col(PlatformSession.session_id)
|
|
1085
|
+
== col(SessionProjectRelation.session_id),
|
|
1086
|
+
)
|
|
1087
|
+
.outerjoin(
|
|
1088
|
+
ChatUIProject,
|
|
1089
|
+
col(SessionProjectRelation.project_id)
|
|
1090
|
+
== col(ChatUIProject.project_id),
|
|
1091
|
+
)
|
|
1092
|
+
.where(col(PlatformSession.creator) == creator)
|
|
1093
|
+
)
|
|
1069
1094
|
|
|
1070
1095
|
if platform_id:
|
|
1071
1096
|
query = query.where(PlatformSession.platform_id == platform_id)
|
|
@@ -1076,7 +1101,24 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
1076
1101
|
.limit(page_size)
|
|
1077
1102
|
)
|
|
1078
1103
|
result = await session.execute(query)
|
|
1079
|
-
|
|
1104
|
+
|
|
1105
|
+
# Convert to list of dicts with session and project info
|
|
1106
|
+
sessions_with_projects = []
|
|
1107
|
+
for row in result.all():
|
|
1108
|
+
platform_session = row[0]
|
|
1109
|
+
project_id = row[1]
|
|
1110
|
+
project_title = row[2]
|
|
1111
|
+
project_emoji = row[3]
|
|
1112
|
+
|
|
1113
|
+
session_dict = {
|
|
1114
|
+
"session": platform_session,
|
|
1115
|
+
"project_id": project_id,
|
|
1116
|
+
"project_title": project_title,
|
|
1117
|
+
"project_emoji": project_emoji,
|
|
1118
|
+
}
|
|
1119
|
+
sessions_with_projects.append(session_dict)
|
|
1120
|
+
|
|
1121
|
+
return sessions_with_projects
|
|
1080
1122
|
|
|
1081
1123
|
async def update_platform_session(
|
|
1082
1124
|
self,
|
|
@@ -1107,3 +1149,182 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
1107
1149
|
col(PlatformSession.session_id) == session_id,
|
|
1108
1150
|
),
|
|
1109
1151
|
)
|
|
1152
|
+
|
|
1153
|
+
# ====
|
|
1154
|
+
# ChatUI Project Management
|
|
1155
|
+
# ====
|
|
1156
|
+
|
|
1157
|
+
async def create_chatui_project(
|
|
1158
|
+
self,
|
|
1159
|
+
creator: str,
|
|
1160
|
+
title: str,
|
|
1161
|
+
emoji: str | None = "📁",
|
|
1162
|
+
description: str | None = None,
|
|
1163
|
+
) -> ChatUIProject:
|
|
1164
|
+
"""Create a new ChatUI project."""
|
|
1165
|
+
async with self.get_db() as session:
|
|
1166
|
+
session: AsyncSession
|
|
1167
|
+
async with session.begin():
|
|
1168
|
+
project = ChatUIProject(
|
|
1169
|
+
creator=creator,
|
|
1170
|
+
title=title,
|
|
1171
|
+
emoji=emoji,
|
|
1172
|
+
description=description,
|
|
1173
|
+
)
|
|
1174
|
+
session.add(project)
|
|
1175
|
+
await session.flush()
|
|
1176
|
+
await session.refresh(project)
|
|
1177
|
+
return project
|
|
1178
|
+
|
|
1179
|
+
async def get_chatui_project_by_id(self, project_id: str) -> ChatUIProject | None:
|
|
1180
|
+
"""Get a ChatUI project by its ID."""
|
|
1181
|
+
async with self.get_db() as session:
|
|
1182
|
+
session: AsyncSession
|
|
1183
|
+
result = await session.execute(
|
|
1184
|
+
select(ChatUIProject).where(
|
|
1185
|
+
col(ChatUIProject.project_id) == project_id,
|
|
1186
|
+
),
|
|
1187
|
+
)
|
|
1188
|
+
return result.scalar_one_or_none()
|
|
1189
|
+
|
|
1190
|
+
async def get_chatui_projects_by_creator(
|
|
1191
|
+
self,
|
|
1192
|
+
creator: str,
|
|
1193
|
+
page: int = 1,
|
|
1194
|
+
page_size: int = 100,
|
|
1195
|
+
) -> list[ChatUIProject]:
|
|
1196
|
+
"""Get all ChatUI projects for a specific creator."""
|
|
1197
|
+
async with self.get_db() as session:
|
|
1198
|
+
session: AsyncSession
|
|
1199
|
+
offset = (page - 1) * page_size
|
|
1200
|
+
result = await session.execute(
|
|
1201
|
+
select(ChatUIProject)
|
|
1202
|
+
.where(col(ChatUIProject.creator) == creator)
|
|
1203
|
+
.order_by(desc(ChatUIProject.updated_at))
|
|
1204
|
+
.limit(page_size)
|
|
1205
|
+
.offset(offset),
|
|
1206
|
+
)
|
|
1207
|
+
return list(result.scalars().all())
|
|
1208
|
+
|
|
1209
|
+
async def update_chatui_project(
|
|
1210
|
+
self,
|
|
1211
|
+
project_id: str,
|
|
1212
|
+
title: str | None = None,
|
|
1213
|
+
emoji: str | None = None,
|
|
1214
|
+
description: str | None = None,
|
|
1215
|
+
) -> None:
|
|
1216
|
+
"""Update a ChatUI project."""
|
|
1217
|
+
async with self.get_db() as session:
|
|
1218
|
+
session: AsyncSession
|
|
1219
|
+
async with session.begin():
|
|
1220
|
+
values: dict[str, T.Any] = {"updated_at": datetime.now(timezone.utc)}
|
|
1221
|
+
if title is not None:
|
|
1222
|
+
values["title"] = title
|
|
1223
|
+
if emoji is not None:
|
|
1224
|
+
values["emoji"] = emoji
|
|
1225
|
+
if description is not None:
|
|
1226
|
+
values["description"] = description
|
|
1227
|
+
|
|
1228
|
+
await session.execute(
|
|
1229
|
+
update(ChatUIProject)
|
|
1230
|
+
.where(col(ChatUIProject.project_id) == project_id)
|
|
1231
|
+
.values(**values),
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
async def delete_chatui_project(self, project_id: str) -> None:
|
|
1235
|
+
"""Delete a ChatUI project by its ID."""
|
|
1236
|
+
async with self.get_db() as session:
|
|
1237
|
+
session: AsyncSession
|
|
1238
|
+
async with session.begin():
|
|
1239
|
+
# First remove all session relations
|
|
1240
|
+
await session.execute(
|
|
1241
|
+
delete(SessionProjectRelation).where(
|
|
1242
|
+
col(SessionProjectRelation.project_id) == project_id,
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1245
|
+
# Then delete the project
|
|
1246
|
+
await session.execute(
|
|
1247
|
+
delete(ChatUIProject).where(
|
|
1248
|
+
col(ChatUIProject.project_id) == project_id,
|
|
1249
|
+
),
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
async def add_session_to_project(
|
|
1253
|
+
self,
|
|
1254
|
+
session_id: str,
|
|
1255
|
+
project_id: str,
|
|
1256
|
+
) -> SessionProjectRelation:
|
|
1257
|
+
"""Add a session to a project."""
|
|
1258
|
+
async with self.get_db() as session:
|
|
1259
|
+
session: AsyncSession
|
|
1260
|
+
async with session.begin():
|
|
1261
|
+
# First remove existing relation if any
|
|
1262
|
+
await session.execute(
|
|
1263
|
+
delete(SessionProjectRelation).where(
|
|
1264
|
+
col(SessionProjectRelation.session_id) == session_id,
|
|
1265
|
+
),
|
|
1266
|
+
)
|
|
1267
|
+
# Then create new relation
|
|
1268
|
+
relation = SessionProjectRelation(
|
|
1269
|
+
session_id=session_id,
|
|
1270
|
+
project_id=project_id,
|
|
1271
|
+
)
|
|
1272
|
+
session.add(relation)
|
|
1273
|
+
await session.flush()
|
|
1274
|
+
await session.refresh(relation)
|
|
1275
|
+
return relation
|
|
1276
|
+
|
|
1277
|
+
async def remove_session_from_project(self, session_id: str) -> None:
|
|
1278
|
+
"""Remove a session from its project."""
|
|
1279
|
+
async with self.get_db() as session:
|
|
1280
|
+
session: AsyncSession
|
|
1281
|
+
async with session.begin():
|
|
1282
|
+
await session.execute(
|
|
1283
|
+
delete(SessionProjectRelation).where(
|
|
1284
|
+
col(SessionProjectRelation.session_id) == session_id,
|
|
1285
|
+
),
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
async def get_project_sessions(
|
|
1289
|
+
self,
|
|
1290
|
+
project_id: str,
|
|
1291
|
+
page: int = 1,
|
|
1292
|
+
page_size: int = 100,
|
|
1293
|
+
) -> list[PlatformSession]:
|
|
1294
|
+
"""Get all sessions in a project."""
|
|
1295
|
+
async with self.get_db() as session:
|
|
1296
|
+
session: AsyncSession
|
|
1297
|
+
offset = (page - 1) * page_size
|
|
1298
|
+
result = await session.execute(
|
|
1299
|
+
select(PlatformSession)
|
|
1300
|
+
.join(
|
|
1301
|
+
SessionProjectRelation,
|
|
1302
|
+
col(PlatformSession.session_id)
|
|
1303
|
+
== col(SessionProjectRelation.session_id),
|
|
1304
|
+
)
|
|
1305
|
+
.where(col(SessionProjectRelation.project_id) == project_id)
|
|
1306
|
+
.order_by(desc(PlatformSession.updated_at))
|
|
1307
|
+
.limit(page_size)
|
|
1308
|
+
.offset(offset),
|
|
1309
|
+
)
|
|
1310
|
+
return list(result.scalars().all())
|
|
1311
|
+
|
|
1312
|
+
async def get_project_by_session(
|
|
1313
|
+
self, session_id: str, creator: str
|
|
1314
|
+
) -> ChatUIProject | None:
|
|
1315
|
+
"""Get the project that a session belongs to."""
|
|
1316
|
+
async with self.get_db() as session:
|
|
1317
|
+
session: AsyncSession
|
|
1318
|
+
result = await session.execute(
|
|
1319
|
+
select(ChatUIProject)
|
|
1320
|
+
.join(
|
|
1321
|
+
SessionProjectRelation,
|
|
1322
|
+
col(ChatUIProject.project_id)
|
|
1323
|
+
== col(SessionProjectRelation.project_id),
|
|
1324
|
+
)
|
|
1325
|
+
.where(
|
|
1326
|
+
col(SessionProjectRelation.session_id) == session_id,
|
|
1327
|
+
col(ChatUIProject.creator) == creator,
|
|
1328
|
+
),
|
|
1329
|
+
)
|
|
1330
|
+
return result.scalar_one_or_none()
|