- 新增带导航+侧边栏的独立页面 DashboardAgentPage:按 taskId 取/存会话, 有有效 report-json 直显大屏(DashboardApp),否则进生成过程(不自动跳)+输入框可改 - 后端 dashboard_sessions 存储(model/sql/__init__)+路由/by-task(GET/PUT 不分权) + deps/app/models 接线;新表靠 create_all 建,无迁移 - 扩展 useStep3Dashboard:buildInitialPrompt(输入任务JSON) + hydrateTranscript(恢复聊天记录) - 抽出共享 DashboardChatPanel;report-template 增 buildDashboardPromptFromJson - 输入 seam core/auth/dashboard-input:JSON 接口未就绪先用占位/演示数据兜底 - 路由 StrategyRoutes dashboard-agent + 侧边栏「问答管理→大屏绘制」 - 圆桌业务链卡片「关系图」改横向布局 - 顺带提交:AIWritingContext 登录切换处理、thread-access-error helper Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
"""SQLAlchemy-backed 大屏绘制智能体 session store (按 taskId,不分权).
|
|
|
|
``transcript`` / ``report_json`` are JSON serialized into ``PortableLongText``
|
|
and round-tripped here with ``json.dumps`` / ``json.loads`` — same approach as
|
|
``roundtable_drafts``. All access keys on ``task_id`` and ignores ``user_id``
|
|
(open read + shared writes); ``user_id`` is recorded for audit only.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
|
|
from deerflow.persistence.dashboard_sessions.model import DashboardSessionRow
|
|
|
|
|
|
def _loads(value: Any) -> Any:
|
|
if not isinstance(value, str):
|
|
return value
|
|
if not value:
|
|
return None
|
|
try:
|
|
return json.loads(value)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def _dumps(value: Any) -> str | None:
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, str):
|
|
return value # already a JSON string (report_json) — store verbatim
|
|
return json.dumps(value, ensure_ascii=False)
|
|
|
|
|
|
class DashboardSessionRepository:
|
|
def __init__(self, session_factory: async_sessionmaker[AsyncSession]) -> None:
|
|
self._sf = session_factory
|
|
|
|
@staticmethod
|
|
def _to_dict(row: DashboardSessionRow) -> dict[str, Any]:
|
|
return {
|
|
"id": row.id,
|
|
"task_id": row.task_id,
|
|
"user_id": row.user_id,
|
|
"transcript": _loads(row.transcript),
|
|
# report_json is a structured-data JSON string; return it as a string
|
|
# so the frontend parses it with the same robust parser it uses live.
|
|
"report_json": row.report_json,
|
|
"created_at": row.created_at.isoformat() if isinstance(row.created_at, datetime) else row.created_at,
|
|
"updated_at": row.updated_at.isoformat() if isinstance(row.updated_at, datetime) else row.updated_at,
|
|
}
|
|
|
|
async def get_by_task(self, task_id: str) -> dict[str, Any] | None:
|
|
"""取该 task 的会话(**忽略 user_id**,不分权)。无则 None。"""
|
|
stmt = select(DashboardSessionRow).where(DashboardSessionRow.task_id == task_id).limit(1)
|
|
async with self._sf() as session:
|
|
result = await session.execute(stmt)
|
|
row = result.scalars().first()
|
|
return self._to_dict(row) if row is not None else None
|
|
|
|
async def upsert_by_task(
|
|
self,
|
|
task_id: str,
|
|
*,
|
|
transcript: Any = None,
|
|
report_json: Any = None,
|
|
user_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""按 task_id upsert:无则建、有则更新(**不分权**)。
|
|
|
|
``transcript`` / ``report_json`` 仅在传入非 None 时覆盖,便于只更新其中一项。
|
|
"""
|
|
now = datetime.now(UTC)
|
|
async with self._sf() as session:
|
|
stmt = select(DashboardSessionRow).where(DashboardSessionRow.task_id == task_id).limit(1)
|
|
row = (await session.execute(stmt)).scalars().first()
|
|
if row is None:
|
|
row = DashboardSessionRow(
|
|
id=uuid4().hex,
|
|
task_id=task_id,
|
|
user_id=user_id,
|
|
transcript=_dumps(transcript),
|
|
report_json=_dumps(report_json),
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
session.add(row)
|
|
else:
|
|
if transcript is not None:
|
|
row.transcript = _dumps(transcript)
|
|
if report_json is not None:
|
|
row.report_json = _dumps(report_json)
|
|
if user_id is not None:
|
|
row.user_id = user_id
|
|
row.updated_at = now
|
|
await session.commit()
|
|
await session.refresh(row)
|
|
return self._to_dict(row)
|