Project Workspace view, abort button, session invalidation, workspace fixes
Workspace (/workspace/:projectId): - Three-panel layout (tasks / chat / notes) with CSS grid collapse toggles - WorkspaceTaskPanel: tasks grouped by milestone, collapsible groups, task detail slide-over with status cycling, TaskLogSection work log, and inline-confirm delete - WorkspaceNoteEditor: list view (sorted by updated_at, inline tag pills, inline-confirm delete) with editor view (TipTap, TagInput, Suggest tags, 60s autosave) - Persistent workspace conversation stored in localStorage per project; reused on return visits - Thinking enabled (think: true) with Reasoning block in streaming bubble - workspace_project_id backend pipeline: chat.py → generation_task.py → llm.py; system prompt uses project title so agent passes project="Title" to tools (fixes create_note failing with numeric project string) - SSE tool-call watcher bridges agent actions to panel updates - Height fix: workspace-root uses height 100%; app-content switches to overflow hidden via :has() selector - Entry point: "Open Workspace" button on ProjectView Abort button: - Stop button in ChatView header and WorkspaceView input bar - Calls existing cancelGeneration() / POST .../generation/cancel Session invalidation: - POST /api/auth/invalidate-sessions bumps session_version, keeps current session alive; useful after SSO/OAuth password rotation - Button in Settings → Active Sessions section Other: - Dashboard recent notes limit increased from 8 to 16 - Workspace chat abort replaces Send button while streaming Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,19 @@ async def change_password(user_id: int, current_password: str, new_password: str
|
||||
return user.session_version
|
||||
|
||||
|
||||
async def invalidate_other_sessions(user_id: int) -> int:
|
||||
"""Bump session_version so all sessions except the current one are invalidated.
|
||||
|
||||
Returns the new session_version so the caller can update the active session cookie.
|
||||
"""
|
||||
async with async_session() as session:
|
||||
user = await session.get(User, user_id)
|
||||
user.session_version += 1
|
||||
await session.commit()
|
||||
logger.info("Sessions invalidated for user %d (%s)", user_id, user.username)
|
||||
return user.session_version
|
||||
|
||||
|
||||
async def is_registration_open() -> bool:
|
||||
"""Check if new user registration is allowed.
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ async def run_generation(
|
||||
excluded_note_ids: list[int] | None = None,
|
||||
think: bool = False,
|
||||
rag_project_id: int | None = None,
|
||||
workspace_project_id: int | None = None,
|
||||
) -> None:
|
||||
"""Stream LLM response into buffer with periodic DB flushes."""
|
||||
MAX_TOOL_ROUNDS = 5
|
||||
@@ -181,6 +182,7 @@ async def run_generation(
|
||||
include_note_ids=include_note_ids,
|
||||
excluded_note_ids=excluded_note_ids,
|
||||
rag_project_id=rag_project_id,
|
||||
workspace_project_id=workspace_project_id,
|
||||
))
|
||||
|
||||
messages, context_meta = await context_task
|
||||
|
||||
@@ -451,6 +451,7 @@ async def build_context(
|
||||
include_note_ids: list[int] | None = None,
|
||||
excluded_note_ids: list[int] | None = None,
|
||||
rag_project_id: int | None = None,
|
||||
workspace_project_id: int | None = None,
|
||||
) -> tuple[list[dict], dict]:
|
||||
"""Build messages array for Ollama with system prompt and context.
|
||||
|
||||
@@ -637,6 +638,22 @@ async def build_context(
|
||||
f"\n\n--- Content from {url} ---\n{content}\n--- End URL Content ---"
|
||||
)
|
||||
|
||||
# Inject workspace context when user is in a project workspace
|
||||
if workspace_project_id is not None:
|
||||
from fabledassistant.services.projects import get_project
|
||||
try:
|
||||
wp = await get_project(user_id, workspace_project_id)
|
||||
if wp:
|
||||
system_parts.append(
|
||||
f"\n\n--- Active Workspace ---\n"
|
||||
f"You are in the \"{wp.title}\" project workspace.\n"
|
||||
f"All notes and tasks you create or update MUST belong to this project.\n"
|
||||
f"Always pass project=\"{wp.title}\" when calling create_note or create_task.\n"
|
||||
f"--- End Active Workspace ---"
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("Failed to fetch workspace project %d", workspace_project_id)
|
||||
|
||||
# Inject compressed summary of older exchanges when history has been trimmed
|
||||
if history_summary:
|
||||
system_parts.append(
|
||||
|
||||
Reference in New Issue
Block a user