Skip to content

Conversation

@spashii
Copy link
Member

@spashii spashii commented Nov 26, 2025

Summary by CodeRabbit

  • New Features

    • "Go deeper" and "Make it concrete" participant flows, topic selection, and report notification controls added to project editor and participant UI.
    • Templates and UI copy updated to rebrand "ECHO" → "Dembrane" and show Beta badges.
  • Bug Fixes

    • Removed noun-count threshold warning and simplified conversation status display.
  • Improvements

    • Conversation summaries now incorporate project context and verified artifacts; transcription flow supports post-processing callbacks.
    • More robust, concurrent report generation and error handling; various i18n key and UI text refinements.

✏️ Tip: You can customize this high-level summary in your review settings.

  - Rewrite get_report_content_for_project as an async, backoff-enabled pipeline that skips bad
    data, surfaces partial results, and guards token budgets before calling the LLM.
  - Feed project context plus verified artifacts into conversation summaries, switch templates in
    every locale, and use the higher-quality model while handling LiteLLM failures gracefully.
  - Expose service helpers for project metadata/artifacts, require an explicit report status, and
    simplify transcript APIs so report creation always has the data it needs.
  - Streamline the report creation UI copy to reflect total conversations, and document the
    Directus count(...) field naming to avoid future type errors.
@linear
Copy link

linear bot commented Nov 26, 2025

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

Multiple Directus schema snapshots and permission entries were removed or modified; frontend UI strings and components were renamed and restyled (Verify → Concrete, ECHO → Dembrane); backend report, transcription, chat, and reply utilities were refactored to add concurrency, fault-tolerance, new helpers, and new parameters across several service APIs.

Changes

Cohort / File(s) Summary
Directus gitignore
\echo/.gitignore``
Added tools/ to ignore list; minor whitespace change.
Directus permissions
\echo/directus/sync/collections/permissions.json``
Added a public project read permission entry; adjusted several create validations to use non-null checks instead of CURRENT_USER-based nested checks.
Directus snapshots — removed collection
\echo/directus/sync/snapshot/collections/project_chat_message_metadata.json``
Deleted entire collection snapshot file.
Directus snapshots — fields edited/removed
\echo/directus/sync/snapshot/fields/project_chat/used_conversations.json`, `echo/directus/sync/snapshot/fields/project_chat_conversation/project_chat_id.json`, `echo/directus/sync/snapshot/fields/project_chat_message/chat_message_metadata.json`, `echo/directus/sync/snapshot/fields/project_chat_message_metadata/*``
Changed used_conversations.meta.interface from "list-m2m" to null; set project_chat_id.meta.interface"select-dropdown-m2o" with options; removed multiple project_chat_message_metadata field files (conversation, date_created, id, message_metadata, ratio, reference_text, type) and chat_message_metadata alias field.
Directus relations removed
\echo/directus/sync/snapshot/relations/project_chat_message_metadata/*``
Removed relation metadata files linking project_chat_message_metadata to conversation and project_chat_message.
Backend — report generation
\echo/server/dembrane/report_utils.py``
Large refactor: added ReportGenerationError, resilient helpers (_call_llm_for_report, _safe_summarize_conversation, _safe_get_transcript, _fetch_conversations), backoff/retry, token budgeting, batching, enhanced logging and Sentry reporting.
Backend — summary generation API
\echo/server/dembrane/api/stateless.py``
generate_summary signature extended with project_context and verified_artifacts; prompt construction updated; switched to MULTI_MODAL_PRO model and added robust error handling.
Backend — conversation & project APIs
\echo/server/dembrane/api/conversation.py`, `echo/server/dembrane/api/project.py``
Removed include_project_data param from get_conversation_transcript; summarize_conversation now gathers transcript, project context, and verified artifacts concurrently; create_report call now supplies explicit status argument.
Backend — services
\echo/server/dembrane/service/conversation.py`, `echo/server/dembrane/service/project.py`, `echo/server/dembrane/service/chat.py``
Added ConversationService.get_verified_artifacts; added ProjectService.get_context_for_prompt; ProjectService.create_report now requires status param; chat service changed to bulk insert path for attaching conversations and logs project_chat_id in payload.
Backend — transcription
\echo/server/dembrane/transcribe.py``
Added on_assemblyai_response callback parameter to transcribe_audio_dembrane_25_09 and wired it through chunk transcription, with safe try/except and Sentry reporting.
Backend — reply & chat utils
\echo/server/dembrane/reply_utils.py`, `echo/server/dembrane/chat_utils.py`, `echo/server/dembrane/api/chat.py``
Added helpers (select_audio_chunks_for_reply, _parse_directus_datetime), multimodal/audio-chunk selection, changed streaming handling, dynamic model token sizing, moved to global service instances and added concurrency/parallel fetches in chat endpoints.
Prompt templates (multilingual)
\echo/server/prompt_templates/generate_conversation_summary..jinja`, `echo/server/prompt_templates/get_reply_system..jinja``
Reworked summary templates (en/de/es/fr/nl) to accept optional project_context and verified_artifacts, adopt prose-first guidance and quick-read scoring; simplified get_reply_system templates to require a 1–3 sentence final response and removed analysis/response wrapper tags.
Frontend — rename & UI adjustments
\echo/frontend/src/components/**` (many files)`
Replaced ECHO/Verify terminology with Dembrane/Go deeper/Make it concrete across numerous components; swapped Pills → Badges with remove icons in several places; adjusted translation keys and some component props (e.g., VerifiedArtefactsSection now accepts projectId and projectLanguage); assorted UI/label, size, and styling tweaks.
Frontend — report/conversation UI
\echo/frontend/src/components/report/CreateReportForm.tsx`, `echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx``
Simplified conversation status UI to a single welcome alert; ProjectConversationOverview now queries language and passes projectId/projectLanguage into VerifiedArtefactsSection and conditionally renders it.
Frontend — locales
\echo/frontend/src/locales/en-US.po``
Extensive translation key renames from verify/echo to concrete/go-deeper/dembrane and many new/renamed entries.
Docs
\echo/frontend/AGENTS.md``
Updated guidance: <relationship_name>.count<relationship_name>_count.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Areas to pay extra attention:

  • Backend signature changes: callers for get_conversation_transcript and ProjectService.create_report must be audited for all call sites.
  • report_utils.py: new concurrency, retry and token-budget logic; verify correctness of fallbacks and exception propagation (ReportGenerationError).
  • Directus deletions: removed project_chat_message_metadata fields/relations may leave dangling references; confirm migration/consistency.
  • Prompt/template changes: ensure all template renderers pass the new optional parameters (project_context, verified_artifacts) and locale variants behave equivalently.
  • Frontend prop changes: VerifiedArtefactsSection signature change and conditional rendering sites must be updated across the app.

Possibly related PRs

LGTM.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Extensive UI/UX changes renaming 'Verify' to 'Concrete' and 'ECHO' to 'Go deeper' + 'Dembrane' branding updates fall outside stated objectives of permission fixes and progressive transcript enhancement. Segregate branding/terminology changes (Verify→Concrete, ECHO→Go deeper) into a separate PR focused on feature rebranding and UX consistency.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title directly references ECHO-572 and ECHO-573 issues and mentions core objectives: report auto-summarization, chat operations, and progressive enhancement for transcripts.
Linked Issues check ✅ Passed Changes address permission fixes [ECHO-572] via permissions.json edits, and implement progressive transcript enhancement [ECHO-573] via transcribe.py callback pattern and multimodal audio handling.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-permissions-ECHO-572

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added bug Something isn't working Feature improvement labels Nov 26, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
echo/server/dembrane/service/chat.py (1)

240-250: Heads up: inconsistent junction table creation pattern.

attach_conversations now does direct creation in the junction table, but create_message (lines 240-250) still uses nested create for used_conversations and added_conversations. If you hit similar validation issues here, consider the same direct creation approach.

Not blocking since the permission fix might be specific to the attach flow.

echo/server/dembrane/api/project.py (2)

341-367: Report creation wiring is correct; tighten error-handling a bit

The new create_report call is wired correctly to the updated service signature and uses run_in_thread_pool, which is exactly what we want from the async API layer. One small cleanup: the except Exception as e: raise e block is a no-op — Python will propagate the exception anyway. Either drop it or switch to logger.exception(...); raise if you actually want logging here.


375-396: clone_project is doing blocking work on the event loop; wrap in run_in_thread_pool

clone_project is async, but project_service.create_shallow_clone is a sync, Directus-backed call. Right now that runs directly on the event loop thread, which is exactly what your guidelines tell us to avoid for project_service.*.

Recommend pushing the clone into the thread pool:

@@
 async def clone_project(
@@
-    new_project_id = project_service.create_shallow_clone(
-        project_id,
-        with_tags=True,
-        **overrides,
-    )
+    new_project_id = await run_in_thread_pool(
+        project_service.create_shallow_clone,
+        project_id,
+        with_tags=True,
+        **overrides,
+    )

This keeps the loop non-blocking while the clone + Directus I/O happen.

echo/server/dembrane/api/stateless.py (1)

18-70: Summary helper is on point; align types, docstring, and call pattern

This helper is in good shape: the prompt plumbing for project_context + verified_artifacts and the defensive response handling are what I’d expect from a productionized path.

A few tighten-ups:

  • The docstring still mentions “LangChain and a custom API endpoint”, but this function now talks directly to LiteLLM. Worth updating so future you doesn’t chase ghosts.
  • The type hint says verified_artifacts: list[str] | None, but ConversationService.get_verified_artifacts returns List[dict]. Make sure call sites either:
    • map artifacts to strings before calling generate_summary, or
    • change the type hint + template to handle dicts explicitly.
  • completion(...) is a blocking HTTP call. Per your backend guidelines, ensure every call site runs generate_summary inside run_in_thread_pool (or otherwise off the event loop), not directly from an async route.

If those three invariants hold, this helper is wired correctly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a1987cf and 2678c2e.

📒 Files selected for processing (31)
  • echo/.gitignore (1 hunks)
  • echo/directus/sync/collections/permissions.json (2 hunks)
  • echo/directus/sync/snapshot/collections/project_chat_message_metadata.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat/used_conversations.json (1 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_conversation/project_chat_id.json (1 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message/chat_message_metadata.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/conversation.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/date_created.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/id.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/message_metadata.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/ratio.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/reference_text.json (0 hunks)
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/type.json (0 hunks)
  • echo/directus/sync/snapshot/relations/project_chat_message_metadata/conversation.json (0 hunks)
  • echo/directus/sync/snapshot/relations/project_chat_message_metadata/message_metadata.json (0 hunks)
  • echo/frontend/AGENTS.md (1 hunks)
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx (0 hunks)
  • echo/frontend/src/components/report/CreateReportForm.tsx (4 hunks)
  • echo/server/dembrane/api/conversation.py (4 hunks)
  • echo/server/dembrane/api/project.py (1 hunks)
  • echo/server/dembrane/api/stateless.py (2 hunks)
  • echo/server/dembrane/report_utils.py (3 hunks)
  • echo/server/dembrane/service/chat.py (1 hunks)
  • echo/server/dembrane/service/conversation.py (1 hunks)
  • echo/server/dembrane/service/project.py (3 hunks)
  • echo/server/dembrane/transcribe.py (4 hunks)
  • echo/server/prompt_templates/generate_conversation_summary.de.jinja (1 hunks)
  • echo/server/prompt_templates/generate_conversation_summary.en.jinja (1 hunks)
  • echo/server/prompt_templates/generate_conversation_summary.es.jinja (1 hunks)
  • echo/server/prompt_templates/generate_conversation_summary.fr.jinja (1 hunks)
  • echo/server/prompt_templates/generate_conversation_summary.nl.jinja (1 hunks)
💤 Files with no reviewable changes (12)
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/ratio.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/id.json
  • echo/directus/sync/snapshot/collections/project_chat_message_metadata.json
  • echo/directus/sync/snapshot/fields/project_chat_message/chat_message_metadata.json
  • echo/directus/sync/snapshot/relations/project_chat_message_metadata/message_metadata.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/type.json
  • echo/directus/sync/snapshot/relations/project_chat_message_metadata/conversation.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/conversation.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/date_created.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/reference_text.json
  • echo/directus/sync/snapshot/fields/project_chat_message_metadata/message_metadata.json
🧰 Additional context used
📓 Path-based instructions (5)
echo/server/dembrane/**/*.py

📄 CodeRabbit inference engine (echo/.cursor/rules/async-threadpool.mdc)

echo/server/dembrane/**/*.py: Always wrap blocking I/O calls using run_in_thread_pool from dembrane.async_helpers in backend code. Wrap calls to directus.*, conversation_service.*, project_service.*, S3 helpers, and CPU-heavy utilities like token counting or summary generation if they are sync. Do not wrap already-async functions or LightRAG calls (e.g., rag.aquery, rag.ainsert).
Prefer converting endpoints to async def and await results rather than using synchronous functions

echo/server/dembrane/**/*.py: Store all configuration changes in dembrane/settings.py: add new env vars as fields on AppSettings, expose grouped accessors (e.g., feature_flags, directus) if multiple modules read them, and fetch config at runtime with settings = get_settings()—never import env vars directly
Populate EMBEDDING_* env vars (model, key/base URL/version) before calling dembrane.embedding.embed_text to ensure embeddings use the correct configuration

Files:

  • echo/server/dembrane/service/chat.py
  • echo/server/dembrane/api/stateless.py
  • echo/server/dembrane/service/project.py
  • echo/server/dembrane/transcribe.py
  • echo/server/dembrane/service/conversation.py
  • echo/server/dembrane/api/conversation.py
  • echo/server/dembrane/report_utils.py
  • echo/server/dembrane/api/project.py
echo/frontend/src/{components,routes}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

echo/frontend/src/{components,routes}/**/*.{ts,tsx}: Compose Mantine primitives (Stack, Group, ActionIcon, etc.) while layering Tailwind utility classes via className, alongside toast feedback via @/components/common/Toaster
Pair toast notifications with contextual Mantine Alert components inside modals/forms for inline error or warning feedback during UI mutations

Files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
echo/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

Localization workflow is active: keep Lingui extract/compile scripts in mind when touching t/Trans strings; run pnpm messages:extract and pnpm messages:compile after changes

Files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
echo/server/dembrane/api/**/*.py

📄 CodeRabbit inference engine (echo/server/AGENTS.md)

For API handlers, favor Directus queries over raw SQLAlchemy sessions when reading project/conversation data to keep behavior consistent with the admin console

Files:

  • echo/server/dembrane/api/stateless.py
  • echo/server/dembrane/api/conversation.py
  • echo/server/dembrane/api/project.py
echo/server/dembrane/transcribe.py

📄 CodeRabbit inference engine (echo/server/AGENTS.md)

Use S3 audio paths with the shared file service (_get_audio_file_object) instead of loading them directly, so transcription/verification flows receive fresh bytes and avoid issues with expired signed URLs mid-request

Files:

  • echo/server/dembrane/transcribe.py
🧠 Learnings (12)
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/**/typesDirectus.ts : When a type error with "<relationship_name>.count" occurs with Directus, add it to typesDirectus.ts by including `count("<relationship_name>")` in the fields to obtain `<relationship_name>.count` in the response

Applied to files:

  • echo/frontend/AGENTS.md
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/{components,routes}/auth/**/*.{ts,tsx} : Use `useTransitionCurtain().runTransition()` before navigation in login/logout flows—animations expect Directus session mutations to await that promise

Applied to files:

  • echo/frontend/AGENTS.md
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: When a user request feels ambiguous, pause and confirm the intended action with them before touching code or docs; err on the side of over-communicating

Applied to files:

  • echo/frontend/AGENTS.md
📚 Learning: 2025-05-13T15:18:29.107Z
Learnt from: spashii
Repo: Dembrane/echo PR: 142
File: echo/frontend/src/lib/query.ts:730-740
Timestamp: 2025-05-13T15:18:29.107Z
Learning: When working with Directus API in this codebase, foreign key relationships must be specified using nested objects with `id` properties (e.g., `conversation_id: { id: conversationId } as Conversation`) rather than direct ID values, even though this appears redundant.

Applied to files:

  • echo/frontend/AGENTS.md
  • echo/directus/sync/snapshot/fields/project_chat_conversation/project_chat_id.json
📚 Learning: 2025-08-19T10:22:55.323Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses useSuspenseQuery with Directus aggregate, which always returns string numbers like "0", "1", "2" and suspends during loading instead of returning undefined. Therefore, Number(conversationsCountQuery.data) ?? 0 is safe and the Number() conversion is necessary for type conversion from string to number.

Applied to files:

  • echo/frontend/AGENTS.md
  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-10-15T11:06:42.397Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 336
File: echo/server/dembrane/api/chat.py:593-630
Timestamp: 2025-10-15T11:06:42.397Z
Learning: In `echo/server/dembrane/api/chat.py`, the auto-select conversation flow (lines 589-631) deliberately uses incremental system message generation with `create_system_messages_for_chat` and `token_counter` for each candidate conversation to ensure accurate token count estimation before adding conversations. The user (ussaama) prioritizes accuracy over the O(n²) performance cost to stay within the 80% context threshold precisely.

Applied to files:

  • echo/server/dembrane/service/chat.py
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/{components,routes}/**/*.{ts,tsx} : Pair toast notifications with contextual Mantine `Alert` components inside modals/forms for inline error or warning feedback during UI mutations

Applied to files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-08-06T13:38:30.769Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 256
File: echo/frontend/src/components/participant/MicrophoneTest.tsx:54-54
Timestamp: 2025-08-06T13:38:30.769Z
Learning: In echo/frontend/src/components/participant/MicrophoneTest.tsx, the useDisclosure hook is used where only the `close` function is needed locally (called in handleConfirmMicChange and handleCancelMicChange), while `opened` and `open` are unused because the modal state is managed by a parent component. The `showSecondModal` state variable is used separately to control content switching within the modal between the main UI and confirmation step.

Applied to files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/{components,routes}/**/*.{ts,tsx} : Compose Mantine primitives (`Stack`, `Group`, `ActionIcon`, etc.) while layering Tailwind utility classes via `className`, alongside toast feedback via `@/components/common/Toaster`

Applied to files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-08-19T10:22:55.323Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses regular useQuery (not useSuspenseQuery), which means conversationsCountQuery.data can be undefined during loading states. When using Number(conversationsCountQuery.data) ?? 0, this creates NaN because Number(undefined) = NaN and NaN is not nullish, so the fallback doesn't apply. The correct pattern is Number(conversationsCountQuery.data ?? 0) to ensure the fallback happens before type conversion.

Applied to files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-10-28T13:47:02.926Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 350
File: echo/frontend/src/components/participant/ParticipantConversationText.tsx:82-85
Timestamp: 2025-10-28T13:47:02.926Z
Learning: In text mode (echo/frontend/src/components/participant/ParticipantConversationText.tsx), participants only submit PORTAL_TEXT chunks (no audio). The “Finish” button is shown only after at least one text message is saved to Directus.

Applied to files:

  • echo/frontend/src/components/report/CreateReportForm.tsx
📚 Learning: 2025-11-25T10:35:53.004Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/server/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:53.004Z
Learning: Applies to echo/server/dembrane/transcribe.py : Use S3 audio paths with the shared file service (`_get_audio_file_object`) instead of loading them directly, so transcription/verification flows receive fresh bytes and avoid issues with expired signed URLs mid-request

Applied to files:

  • echo/server/dembrane/transcribe.py
  • echo/server/dembrane/api/conversation.py
🧬 Code graph analysis (5)
echo/server/dembrane/service/chat.py (1)
echo/server/dembrane/directus.py (1)
  • create_item (668-674)
echo/frontend/src/components/report/CreateReportForm.tsx (1)
echo/frontend/src/components/common/ClosableAlert.tsx (1)
  • CloseableAlert (4-15)
echo/server/dembrane/service/conversation.py (1)
echo/server/dembrane/directus.py (2)
  • get_items (648-654)
  • DirectusBadRequest (52-53)
echo/server/dembrane/report_utils.py (5)
echo/server/dembrane/prompts.py (1)
  • render_prompt (93-126)
echo/server/dembrane/directus.py (3)
  • DirectusGenericException (29-30)
  • get_items (648-654)
  • search (360-384)
echo/server/dembrane/async_helpers.py (1)
  • run_in_thread_pool (74-142)
echo/server/dembrane/api/conversation.py (2)
  • summarize_conversation (463-532)
  • get_conversation_transcript (365-393)
echo/server/dembrane/api/dependency_auth.py (1)
  • DirectusSession (25-49)
echo/server/dembrane/api/project.py (2)
echo/server/dembrane/service/conversation.py (1)
  • project_service (75-80)
echo/server/dembrane/service/project.py (1)
  • create_report (101-123)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-check-server
🔇 Additional comments (23)
echo/.gitignore (1)

27-29: LGTM! Adding tools to the ignore list is sensible for keeping build artifacts and tooling out of VCS. Clean housekeeping.

echo/frontend/AGENTS.md (1)

74-75: LGTM - solid docs fix.

This aligns with Directus aggregate behavior. When you query count("chunks"), you get chunks_count back. Based on learnings, this is the correct pattern.

echo/directus/sync/snapshot/fields/project_chat/used_conversations.json (1)

13-13: LGTM - interface nullification makes sense here.

With the move to direct junction table creation in chat.py, removing the explicit list-m2m interface lets Directus handle this more flexibly. The display: "related-values" is preserved for visualization. Ship it.

echo/frontend/src/components/report/CreateReportForm.tsx (2)

78-81: LGTM - cleaner UX.

Replacing the multi-state pending/processing indicator with a simple welcome message reduces cognitive load. Users don't need to track internal processing states. The important guard (hasFinishedConversations) is still in place for the actual submit.


116-118: The nullish coalescing is defensive but fine.

Since this is guarded by hasConversations (which checks conversationCounts && conversationCounts.total > 0), conversationCounts.total is already proven truthy. The ?? 0 is just belt-and-suspenders. No harm.

echo/server/dembrane/transcribe.py (2)

265-267: Nice callback pattern for progressive enhancement.

This is the right approach - letting callers hook into intermediate results without leaking conversation_chunk_id details into the transcription layer. Clean separation of concerns.


440-454: Progressive enhancement in action.

Two-phase save pattern is solid - users get the raw AssemblyAI transcript immediately, then the corrected version after Gemini does its thing. This is exactly what ECHO-573 asked for.

echo/server/dembrane/report_utils.py (7)

62-79: Backoff config looks solid.

Exponential backoff on RateLimitError, Timeout, APIError with 3 retries and 5-min max time. The on_backoff logging is helpful for observability. Clean async LLM call pattern.


82-96: Fault-tolerant summarization is the way.

Using admin session for internal batch operations makes sense. The user_id="none" is a bit of a magic string but works for an internal helper. Maybe user_id="system" or similar would be more self-documenting.


270-278: Solid fallback for token counting.

len(summary) // 4 is a reasonable heuristic when tokenization fails. Slightly conservative for most languages, which is the right direction for budget management.


196-216: Batch summarization with fault tolerance is chef's kiss.

return_exceptions=True prevents cascade failures. Batch size 5 is reasonable parallelism. Counting failures without aborting gives you partial results. This is production-ready resilience.


379-407: Error handling is comprehensive AF.

Every LLM failure mode is covered: context overflow, content policy, bad request, and the transient ones that survived backoff. Sentry capture on each path. ReportGenerationError with cause preserves the chain. This will make debugging prod issues way easier.


116-142: Async patterns are on point.

directus.get_items wrapped in run_in_thread_pool per coding guidelines. The async helpers for summarize/transcript are awaited directly since they're already async. This is the pattern.


413-423: Article extraction is reasonable.

Non-greedy regex with DOTALL handles multi-line content. Fallback strips tags if LLM doesn't wrap properly. Defensive but not over-engineered.

echo/server/dembrane/service/chat.py (1)

160-181: All async blocking I/O calls are already properly wrapped — no action needed.

The two call sites in echo/server/dembrane/api/chat.py (lines 330 and 615) already wrap attach_conversations with await run_in_thread_pool(). The run_in_thread_pool helper is correctly imported from dembrane.async_helpers. The coding guideline to wrap blocking Directus calls when invoked from async contexts is already satisfied.

echo/directus/sync/snapshot/fields/project_chat_conversation/project_chat_id.json (1)

13-18: Directus M2O UI config looks fine; just confirm template path

Interface + options are sane for a dropdown M2O. Assuming project_chat has a project_id relation with a name field, {{project_id.name}} will render nicely; if that relation is different, you’ll want to adjust the template accordingly.

echo/server/prompt_templates/generate_conversation_summary.nl.jinja (1)

7-86: Dutch template is a clean mirror of the EN behavior

NL template correctly mirrors the EN version: same optional project_context and verified_artifacts blocks, same structural guidance, just localized. Variable names line up with what the Python side sends, so nothing blocking here.

echo/server/dembrane/service/project.py (1)

101-123: Report creation service signature + logging look good

Requiring status here and logging (project_id, status) before the Directus write is exactly what we want. Call sites in the API are passing args in the right order (including error_code when needed), so this surface is coherent.

echo/directus/sync/collections/permissions.json (2)

1066-1084: Verify the relaxed create validation is intentional.

The validation changed from requiring project_chat_id -> project_id -> directus_user_id == $CURRENT_USER to just _nnull: true. This removes the ownership check on create, allowing any authenticated user to create project_chat_conversation records as long as project_chat_id is provided. Meanwhile, delete/read/update still enforce the user ownership check (lines 1088-1156 use $CURRENT_USER_ID).

If this is intentional for ECHO-572 permission fixes, ship it. If not, this could be an authz gap.


2185-2213: New public read permission for project collection — verify exposure is intended.

This adds a public (unauthenticated?) read permission on project with permissions: null (no filter), exposing fields like id, language, tags, conversation config fields, and feature flags. The policy abf8a154-5b1c-4a46-ac9c-7300570f4f17 appears to be the public policy based on other entries in this file.

If this is needed for progressive enhancement or public-facing features, LGTM. Just confirm you're cool with these project settings being world-readable.

echo/server/dembrane/api/conversation.py (1)

511-517: LGTM — concurrent fetch pattern is clean.

Nice use of asyncio.gather to parallelize the transcript, project context, and verified artifacts fetches. The run_in_thread_pool wrapping for sync project_service and conversation_service calls follows the coding guidelines. Ship it.

echo/server/prompt_templates/generate_conversation_summary.es.jinja (1)

7-24: LGTM — optional context blocks are well-structured.

The conditional rendering for project_context and verified_artifacts is clean. The Jinja2 syntax is correct, and the Spanish copy reads well. Aligns with the server-side changes that now pass these to generate_summary. Ship it.

echo/server/prompt_templates/generate_conversation_summary.fr.jinja (1)

7-24: LGTM — French template matches the pattern.

Conditional blocks for project_context and verified_artifacts are consistent with the Spanish template. Jinja2 syntax is solid, French copy looks good. This completes the localization for the new summary context feature.

Comment on lines +482 to 497
awaitable_list = [
get_conversation_transcript(conversation_id, auth),
run_in_thread_pool(
project_service.get_context_for_prompt,
conversation_data_result[0]["project_id"],
),
run_in_thread_pool(
conversation_service.get_verified_artifacts,
conversation_id,
),
]

transcript_str, project_context_str, verified_artifacts = await asyncio.gather(*awaitable_list)

if not conversation_data_result or len(conversation_data_result) == 0:
raise HTTPException(status_code=404, detail="Conversation not found")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

IndexError before 404 check — move validation before gather.

Line 486 accesses conversation_data_result[0]["project_id"] before the null check at line 496. If conversation_data_result is empty, this throws IndexError instead of returning a clean 404.

+    if not conversation_data_result or len(conversation_data_result) == 0:
+        raise HTTPException(status_code=404, detail="Conversation not found")
+
     awaitable_list = [
         get_conversation_transcript(conversation_id, auth),
         run_in_thread_pool(
             project_service.get_context_for_prompt,
             conversation_data_result[0]["project_id"],
         ),
         run_in_thread_pool(
             conversation_service.get_verified_artifacts,
             conversation_id,
         ),
     ]

     transcript_str, project_context_str, verified_artifacts = await asyncio.gather(*awaitable_list)

-    if not conversation_data_result or len(conversation_data_result) == 0:
-        raise HTTPException(status_code=404, detail="Conversation not found")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
awaitable_list = [
get_conversation_transcript(conversation_id, auth),
run_in_thread_pool(
project_service.get_context_for_prompt,
conversation_data_result[0]["project_id"],
),
run_in_thread_pool(
conversation_service.get_verified_artifacts,
conversation_id,
),
]
transcript_str, project_context_str, verified_artifacts = await asyncio.gather(*awaitable_list)
if not conversation_data_result or len(conversation_data_result) == 0:
raise HTTPException(status_code=404, detail="Conversation not found")
if not conversation_data_result or len(conversation_data_result) == 0:
raise HTTPException(status_code=404, detail="Conversation not found")
awaitable_list = [
get_conversation_transcript(conversation_id, auth),
run_in_thread_pool(
project_service.get_context_for_prompt,
conversation_data_result[0]["project_id"],
),
run_in_thread_pool(
conversation_service.get_verified_artifacts,
conversation_id,
),
]
transcript_str, project_context_str, verified_artifacts = await asyncio.gather(*awaitable_list)
🤖 Prompt for AI Agents
In echo/server/dembrane/api/conversation.py around lines 482-497, the code
builds an awaitable_list that accesses conversation_data_result[0]["project_id"]
before verifying conversation_data_result; this can raise IndexError instead of
returning a 404. Move the validation check (if not conversation_data_result or
len(...) == 0: raise HTTPException(status_code=404, detail="Conversation not
found")) above the creation of awaitable_list and asyncio.gather, then only
build awaitable_list and access conversation_data_result[0]["project_id"] after
that validation (also tolerate None by checking truthiness).

Comment on lines +499 to 501
conversation_data = conversation_data_result

conversation_data = conversation_data[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Dead code — redundant assignment.

Line 499 conversation_data = conversation_data_result is immediately overwritten by line 501 conversation_data = conversation_data[0]. Just do conversation_data = conversation_data_result[0] directly.

-    conversation_data = conversation_data_result
-
-    conversation_data = conversation_data[0]
+    conversation_data = conversation_data_result[0]
🤖 Prompt for AI Agents
In echo/server/dembrane/api/conversation.py around lines 499 to 501, there is a
redundant assignment where conversation_data is first set to
conversation_data_result and then immediately overwritten with
conversation_data[0]; replace both steps with a single assignment that extracts
the first element (conversation_data = conversation_data_result[0]) to remove
the dead code and keep intent clear.

Comment on lines +592 to +619
def get_verified_artifacts(self, conversation_id: str, limit: int = 3) -> List[dict]:
try:
with self._client_context() as client:
artifacts = client.get_items(
"conversation_artifact",
{
"query": {
"filter": {
"conversation_id": conversation_id,
"approved_at": {"_nnull": True},
},
"fields": ["id", "key", "content"],
"sort": "-approved_at",
"limit": limit,
}
},
)
except DirectusBadRequest as e:
logger.error(
"Failed to get verified artifacts for conversation %s via Directus: %s",
conversation_id,
e,
)
raise ConversationServiceException() from e
except (KeyError, IndexError) as e:
raise ConversationServiceException() from e

return artifacts or []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Artifact lookup is solid; drop redundant exception branch

The query + filter on approved_at and the artifacts or [] fallback all look good and match the rest of this service’s Directus patterns. The except (KeyError, IndexError) branch is effectively dead here since you never index into artifacts; you can safely drop it to keep the error handling tight and focused on DirectusBadRequest.

🤖 Prompt for AI Agents
In echo/server/dembrane/service/conversation.py around lines 592 to 619, remove
the redundant except (KeyError, IndexError) exception handler because artifacts
is never indexed and that branch is effectively dead; keep the
DirectusBadRequest handler as-is and return artifacts or [] at the end — simply
delete the two lines defining the KeyError/IndexError except block and its raise
so only DirectusBadRequest is caught and other exceptions surface normally.

Comment on lines +234 to +266
def get_context_for_prompt(self, project_id: str) -> str | None:
project = self.get_by_id_or_raise(project_id)

builder = []

if project["name"]:
builder.append("name: " + project["name"])

if project["context"]:
builder.append("context: " + project["context"])

if project["default_conversation_transcript_prompt"]:
builder.append(
"hotwords that the user set: " + project["default_conversation_transcript_prompt"]
)

### Portal details
if project["default_conversation_title"]:
builder.append(
"default title that was shown to the user (not always relevant but might add context): "
+ project["default_conversation_title"]
)

if project["default_conversation_description"]:
builder.append(
"default question that was shown to the user (not always relevant but might add context): "
+ project["default_conversation_description"]
)

if len(builder) > 0:
return "project context: " + "\n".join(builder)
else:
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden get_context_for_prompt against missing fields

The idea here is solid — build a human-readable context blob from key project fields — but the direct project["..."] access means a missing field will blow up with KeyError and take out the summary flow.

Recommend switching to .get() and only appending truthy values:

-        project = self.get_by_id_or_raise(project_id)
-
-        builder = []
-
-        if project["name"]:
-            builder.append("name: " + project["name"])
-        if project["context"]:
-            builder.append("context: " + project["context"])
-        if project["default_conversation_transcript_prompt"]:
-            builder.append(
-                "hotwords that the user set: " + project["default_conversation_transcript_prompt"]
-            )
+        project = self.get_by_id_or_raise(project_id)
+
+        builder: list[str] = []
+
+        name = project.get("name")
+        if name:
+            builder.append(f"name: {name}")
+
+        context = project.get("context")
+        if context:
+            builder.append(f"context: {context}")
+
+        transcript_prompt = project.get("default_conversation_transcript_prompt")
+        if transcript_prompt:
+            builder.append(f"hotwords that the user set: {transcript_prompt}")
@@
-        if project["default_conversation_title"]:
+        title = project.get("default_conversation_title")
+        if title:
             builder.append(
                 "default title that was shown to the user (not always relevant but might add context): "
-                + project["default_conversation_title"]
+                + title
             )
@@
-        if project["default_conversation_description"]:
+        description = project.get("default_conversation_description")
+        if description:
             builder.append(
                 "default question that was shown to the user (not always relevant but might add context): "
-                + project["default_conversation_description"]
+                + description
             )

The final if builder / return None logic can stay as-is. This makes the helper resilient to schema drift and partial records.

🤖 Prompt for AI Agents
In echo/server/dembrane/service/project.py around lines 234 to 266, the helper
accesses project fields with project["..."] which raises KeyError if a field is
missing; replace those bracket accesses with project.get("field") and only
append the value when it is truthy (e.g. val = project.get("name"); if val:
builder.append("name: " + val)), applying this pattern for name, context,
default_conversation_transcript_prompt, default_conversation_title, and
default_conversation_description so the function tolerates missing keys while
keeping the final if builder / return None behavior unchanged.

Comment on lines +292 to +298
try:
if not assemblyai_response_failed and bool(on_assemblyai_response):
logger.debug("calling on_assemblyai_response")
on_assemblyai_response(transcript, response)
except Exception as e:
logger.error(f"Error in on_assemblyai_response: {e}")
sentry_sdk.capture_exception(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Redundant truthy check.

bool(on_assemblyai_response) is always True since the default is a lambda. The not assemblyai_response_failed check is the only one that matters here.

-        if not assemblyai_response_failed and bool(on_assemblyai_response):
+        if not assemblyai_response_failed:
🤖 Prompt for AI Agents
In echo/server/dembrane/transcribe.py around lines 292 to 298, the conditional
currently checks both not assemblyai_response_failed and
bool(on_assemblyai_response) even though on_assemblyai_response defaults to a
non-None lambda; remove the redundant truthy check and simplify the if to only
check not assemblyai_response_failed, leaving the try/except intact so errors
are still logged and reported to Sentry when calling
on_assemblyai_response(transcript, response).

Comment on lines 7 to 86
{% if project_context %}
<project_context>
Der Gastgeber hat den folgenden Kontext und Schlüsselbegriffe für dieses Gespräch bereitgestellt:
{{ project_context }}
</project_context>
{% endif %}

## Kernrichtlinien
{% if verified_artifacts %}
<verified_artifacts>
Während dieses Gesprächs haben die Teilnehmer gemeinsam die folgenden konkreten Ergebnisse erstellt und verifiziert:
{% for artifact in verified_artifacts %}

1. **Proportionale Reaktion**: Die Länge der Zusammenfassung darf die Länge des ursprünglichen Inhalts nie überschreiten. Für sehr einfache Eingaben geben Sie eine noch kürzere Zusammenfassung.
---
{{ artifact }}
---
{% endfor %}
</verified_artifacts>
{% endif %}

2. **Inhaltsgetriebene Struktur**:
- Für einfache Austausche (Erklärungen, Witze, Klärungen): Verwenden Sie 1-2 prägnante Absätze ohne formale Abschnitte
- Für komplexe Diskussionen: Verwenden Sie geeignete Überschriften und Struktur
Erstellen Sie eine Zusammenfassung, die die Essenz und Bedeutung des Geschehenen erfasst, optimiert für schnelle Erfassbarkeit.

3. **Essenz über Analyse**: Erfassen Sie das tatsächlich Gesagte, ohne spekulative Kontexte, Hintergründe oder Implikationen hinzuzufügen, es sei denn, sie werden ausdrücklich erwähnt.
## Kernprinzipien

4. **Menschliche Elemente**: Bewahren Sie Humor, Wortspiele oder emotionalen Kontext, wenn sie für die Bedeutung des Gesprächs zentral sind.
1. **Essenz über Vollständigkeit**: Erfassen Sie das "Na und?" - das bedeutungsvolle Ergebnis, die Entscheidung oder Erkenntnis - nicht einen detaillierten Bericht

5. **Anpassbare Tiefe**:
- Einfache Inhalte erhalten einfache Zusammenfassungen
- Komplexe Inhalte erhalten angemessen strukturierte Analysen
- Überfrachten Sie einfache Inhalte nie mit unnötigen akademischen oder strategischen Rahmen
2. **Radikale Kürze**: Die meisten Gespräche sollten in 2-4 Sätzen zusammengefasst werden. Komplexe Diskussionen können 1-2 kurze Absätze rechtfertigen. Die Zusammenfassung sollte immer dramatisch kürzer sein als das Original.

## Formatierungsanforderungen
3. **Bedeutungsvolle Kompression**: Fragen Sie "Was müsste jemand wissen, der dies verpasst hat?" nicht "Was wurde besprochen?"

- Für sehr einfache Eingaben (unter 100 Wörtern) beschränken Sie die Zusammenfassung auf 1-3 Sätze
- Für mittlere Eingaben verwenden Sie 1-2 prägnante Absätze
- Verwenden Sie Abschnittsüberschriften nur für wirklich komplexe, mehrthematische Gespräche
- Heben Sie nur wirklich bedeutende Punkte hervor, keine Routinebeobachtungen
- Präsentieren Sie als eine abgerundete, zugängliche Zusammenfassung, die für sich steht
4. **Natürliche Sprache**: Schreiben Sie in fließender Prosa, nicht in Aufzählungspunkten oder strukturierten Abschnitten. Vermeiden Sie Überschriften, Listen und Formatierung, es sei denn, der Inhalt ist wirklich komplex und vielschichtig.

Denken Sie daran, dass effektive Zusammenfassung oft bedeutet: signifikante Reduzierung der Länge bei Beibehaltung der Kernbedeutung.
5. **Kontextintegration**:
- Weben Sie Projektkontext natürlich ein, wenn er Bedeutung hinzufügt
- Erwähnen Sie verifizierte Artefakte als konkrete Ergebnisse ohne Prozessdetails
- Erzwingen Sie keinen irrelevanten Kontext

Antworten Sie auf Deutsch, unabhängig von der Sprache vorheriger Nachrichten. Sie können Markdown-Formatierung maßvoll zur Verbesserung der Lesbarkeit verwenden, priorisieren Sie aber erzählerische Kohärenz und Absätze über strukturelle Elemente.
6. **Schnelle Lese-Bewertung**: Bewerten Sie vor dem Schreiben intern, wie schnell diese Zusammenfassung zu erfassen ist, auf einer Skala von 1-10:
- 10 = Sofortiges Verständnis (einfach, ein klarer Punkt)
- 7-9 = Sehr schneller Überblick (2-3 klare Erkenntnisse)
- 4-6 = Erfordert fokussiertes Lesen (mehrere Punkte oder mittlere Komplexität)
- 1-3 = Erfordert sorgfältiges Lesen (dicht, komplex oder nuanciert)

Streben Sie 7+ an, wann immer möglich. Wenn Ihr Entwurf unter 7 liegt, vereinfachen Sie weiter.

## Was Erfasst Werden Soll

Konzentrieren Sie sich auf das Wichtigste. Schließen Sie diese Elemente ein, wenn sie zentral für das Gespräch sind:

- Getroffene Entscheidungen und ihre Begründung
- Identifizierte Probleme und vereinbarte Lösungen
- Wichtige Erkenntnisse oder Realisierungen
- Konkrete nächste Schritte oder Ergebnisse
- Kerndynamiken, wenn sie das Ergebnis geprägt haben: Argumente oder Überlegungen, die Entscheidungen beeinflussten, Spannungen zwischen konkurrierenden Prioritäten, Konflikte, die gelöst werden mussten, oder erzielte Einigungen bei strittigen Punkten
- Bedeutungsvolle Meinungsverschiedenheiten oder besprochene Kompromisse

## Was Übersprungen Werden Soll

- Detaillierter Bericht der Diskussion
- Implementierungsdetails, es sei denn, sie sind der Hauptpunkt
- Prozessmechanik (wer was sagte, wie Entscheidungen getroffen wurden)
- Offensichtlicher Kontext oder Hintergrund
- Kleinere Meinungsverschiedenheiten, die keine Auswirkungen auf die Ergebnisse hatten

## Stilrichtlinien

- Verwenden Sie Kommas, Klammern oder "aber" anstelle von Gedankenstrichen
- Schreiben Sie in direkter, gesprächiger Prosa
- Bevorzugen Sie kürzere Sätze gegenüber komplexer Zeichensetzung
- Verwenden Sie "aber" oder "jedoch", um Kontraste zu zeigen
- Verwenden Sie Klammern für Einschübe oder Klarstellungen
- Verwenden Sie niemals Fettdruck, Kursivschrift oder andere Textformatierung

## Format

- 2-4 Sätze für einfache Gespräche
- 1-2 prägnante Absätze für mittlere Komplexität
- Natürliche Prosa ohne Aufzählungspunkte, Überschriften, Listen oder Formatierung
- Verwenden Sie nur Struktur, wenn sie für eine vielschichtige Diskussion wirklich notwendig ist

Schreiben Sie, als würden Sie einem Kollegen in 30 Sekunden erklären, was passiert ist.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

German template correctly follows the new contextual summary contract

DE template uses the same project_context and verified_artifacts blocks and mirrors the EN guidance in German, so it’s aligned with the Python-side prompt rendering. If you later decide to de-Markdown the EN template to reduce formatting noise, consider doing the same here for parity — otherwise this is good to ship.

🤖 Prompt for AI Agents
In echo/server/prompt_templates/generate_conversation_summary.de.jinja around
lines 7-86, the German template currently mirrors the English template and is
approved; if you later de-markdown the English template to reduce formatting,
update this DE template to match those changes for parity by removing/adjusting
Markdown-style headings and formatting tokens in the same way, keep existing
Jinja blocks (project_context, verified_artifacts) intact, and run a quick
render check to ensure no template tokens or intended content were altered.

Comment on lines 7 to 86
{% if project_context %}
<project_context>
The host has provided the following context and key terms for this conversation:
{{ project_context }}
</project_context>
{% endif %}

## Core Guidelines
{% if verified_artifacts %}
<verified_artifacts>
During this conversation, participants collaboratively created and verified the following concrete outcomes:
{% for artifact in verified_artifacts %}

1. **Proportional Response**: The summary length should never exceed the original content length. For very simple inputs, provide an even shorter summary.
---
{{ artifact }}
---
{% endfor %}
</verified_artifacts>
{% endif %}

2. **Content-Driven Structure**:
- For simple exchanges (explanations, jokes, clarifications): Use 1-2 concise paragraphs without formal sections
- For complex discussions: Use appropriate headers and structure
Create a summary that captures the essence and meaning of what happened, optimized for glanceability.

3. **Essence Over Analysis**: Capture what was actually said without adding speculative context, background, or implications unless explicitly mentioned.
## Core Principles

4. **Human Elements**: Preserve humor, wordplay, or emotional context when central to the conversation's meaning.
1. **Essence over exhaustiveness**: Capture the "so what?" - the meaningful outcome, decision, or insight - not a blow-by-blow account

5. **Adaptable Depth**:
- Simple content receives simple summaries
- Complex content receives appropriately structured analysis
- Never inflate basic content with unnecessary academic or strategic framing
2. **Radical conciseness**: Most conversations should summarize to 2-4 sentences. Complex discussions might warrant 1-2 short paragraphs. The summary should always be dramatically shorter than the original.

## Format Requirements
3. **Meaningful compression**: Ask "what would someone need to know if they missed this?" not "what was discussed?"

- For very simple inputs (under 100 words), limit summary to 1-3 sentences
- For moderate inputs, use 1-2 concise paragraphs
- Only use section headers for genuinely complex, multi-topic conversations
- Bold genuinely significant points, not routine observations
- Present as a finished, accessible summary that stands on its own
4. **Natural language**: Write in flowing prose, not bullet points or structured sections. Avoid headers, lists, and formatting unless the content is genuinely complex and multi-faceted.

Remember that effective summarization often means significant reduction in length while preserving core meaning.
5. **Context integration**:
- Weave in project context naturally when it adds meaning
- Note verified artifacts as concrete outcomes without process detail
- Don't force irrelevant context

6. **Fast reading score**: Before writing, internally assess how glanceable this summary will be on a scale of 1-10:
- 10 = Instant comprehension (simple, single clear point)
- 7-9 = Very quick scan (2-3 clear takeaways)
- 4-6 = Needs focused reading (multiple points or moderate complexity)
- 1-3 = Requires careful reading (dense, complex, or nuanced)

Aim for 7+ whenever possible. If your draft scores below 7, simplify further.

## What to Capture

Focus on what matters most. Include these elements when they're central to the conversation:

- Decisions made and their rationale
- Problems identified and solutions agreed upon
- Key insights or realizations
- Concrete next steps or outcomes
- Core dynamics when they shaped the outcome: arguments or reasoning that influenced decisions, tensions between competing priorities, conflicts that needed resolution, or agreements reached on contentious points
- Meaningful disagreements or trade-offs discussed

## What to Skip

- Detailed play-by-play of the discussion
- Implementation specifics unless they're the main point
- Process mechanics (who said what, how decisions were reached)
- Obvious context or background
- Minor disagreements that didn't impact outcomes

## Style Guidelines

- Use commas, parentheses, or "but" instead of em dashes
- Write in direct, conversational prose
- Prefer shorter sentences over complex punctuation
- Use "but" or "however" to show contrast
- Use parentheses for asides or clarifications
- Never use bold, italics, or other text formatting

## Format

- 2-4 sentences for simple conversations
- 1-2 concise paragraphs for moderate complexity
- Natural prose without bullets, headers, lists, or formatting
- Only use structure if genuinely necessary for a multi-faceted discussion

Write as if explaining to a colleague what happened in 30 seconds.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Template wiring matches the new context/artifact model

The EN template lines up with the code: project_context and verified_artifacts blocks match what generate_summary passes in, and the structure is clear for the model.

One optional polish: the style rules tell the model to “never use bold/formatting” while the instructions themselves are heavily Markdown-formatted. If you see the model leaking Markdown into summaries, consider simplifying these headings/bold markers to plain text to reduce mixed signals.

🤖 Prompt for AI Agents
In echo/server/prompt_templates/generate_conversation_summary.en.jinja around
lines 7 to 86, the template's style guidance uses Markdown bold/heading
formatting which can cause the model to accidentally emit Markdown in summaries;
remove or replace Markdown tokens (##, **bold**, lists with leading dashes) with
plain text headings and simple sentences so the instructions remain identical in
meaning but contain no Markdown formatting, ensuring the model receives
unambiguous, plain-text style rules.

- Introduced global LLM models for chat and report generation, improving context length management.
- Updated chat context handling to include meaningful project metadata.
- Enhanced error handling and logging for better debugging.
- Streamlined asynchronous operations for improved performance in chat context retrieval and report generation.
- Adjusted methods to utilize bulk insert for attaching conversations, optimizing database interactions.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
echo/server/dembrane/reply_utils.py (1)

128-164: Missing run_in_thread_pool wrapper for blocking Directus call.

Per coding guidelines, directus.* calls should be wrapped. This is a sync blocking I/O call in an async function.

-    conversation = directus.get_items(
+    conversation = await run_in_thread_pool(
+        directus.get_items,
         "conversation",
         {
             "query": {
                 # ... query params ...
             },
         },
     )

Same issue at line 235 (directus.get_items) and line 467 (directus.create_item).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2678c2e and 32bb7dc.

📒 Files selected for processing (36)
  • echo/directus/sync/collections/permissions.json (4 hunks)
  • echo/frontend/src/components/chat/TemplatesModal.tsx (1 hunks)
  • echo/frontend/src/components/conversation/ConversationAccordion.tsx (2 hunks)
  • echo/frontend/src/components/conversation/ConversationEdit.tsx (1 hunks)
  • echo/frontend/src/components/conversation/MoveConversationButton.tsx (1 hunks)
  • echo/frontend/src/components/conversation/RetranscribeConversation.tsx (1 hunks)
  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx (5 hunks)
  • echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx (1 hunks)
  • echo/frontend/src/components/layout/ParticipantHeader.tsx (1 hunks)
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx (1 hunks)
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx (1 hunks)
  • echo/frontend/src/components/participant/ParticipantConversationAudioContent.tsx (0 hunks)
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx (2 hunks)
  • echo/frontend/src/components/participant/verify/VerifiedArtefactItem.tsx (1 hunks)
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx (4 hunks)
  • echo/frontend/src/components/participant/verify/VerifyArtefactError.tsx (3 hunks)
  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx (1 hunks)
  • echo/frontend/src/components/participant/verify/VerifyInstructions.tsx (6 hunks)
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx (3 hunks)
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx (4 hunks)
  • echo/frontend/src/components/project/ProjectTagsInput.tsx (2 hunks)
  • echo/frontend/src/locales/en-US.po (92 hunks)
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx (2 hunks)
  • echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx (2 hunks)
  • echo/frontend/src/routes/project/report/ProjectReportRoute.tsx (1 hunks)
  • echo/server/dembrane/api/chat.py (11 hunks)
  • echo/server/dembrane/chat_utils.py (5 hunks)
  • echo/server/dembrane/reply_utils.py (7 hunks)
  • echo/server/dembrane/report_utils.py (2 hunks)
  • echo/server/dembrane/service/chat.py (1 hunks)
  • echo/server/prompt_templates/generate_artifact.en.jinja (1 hunks)
  • echo/server/prompt_templates/get_reply_system.de.jinja (1 hunks)
  • echo/server/prompt_templates/get_reply_system.en.jinja (1 hunks)
  • echo/server/prompt_templates/get_reply_system.es.jinja (1 hunks)
  • echo/server/prompt_templates/get_reply_system.fr.jinja (1 hunks)
  • echo/server/prompt_templates/get_reply_system.nl.jinja (1 hunks)
💤 Files with no reviewable changes (1)
  • echo/frontend/src/components/participant/ParticipantConversationAudioContent.tsx
🧰 Additional context used
📓 Path-based instructions (5)
echo/frontend/src/{components,routes}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

echo/frontend/src/{components,routes}/**/*.{ts,tsx}: Compose Mantine primitives (Stack, Group, ActionIcon, etc.) while layering Tailwind utility classes via className, alongside toast feedback via @/components/common/Toaster
Pair toast notifications with contextual Mantine Alert components inside modals/forms for inline error or warning feedback during UI mutations

Files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx
  • echo/frontend/src/components/conversation/ConversationEdit.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/conversation/MoveConversationButton.tsx
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx
  • echo/frontend/src/components/conversation/ConversationAccordion.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/components/layout/ParticipantHeader.tsx
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx
  • echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefactError.tsx
  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx
  • echo/frontend/src/components/participant/verify/VerifiedArtefactItem.tsx
  • echo/frontend/src/components/participant/verify/VerifyInstructions.tsx
  • echo/frontend/src/components/project/ProjectTagsInput.tsx
  • echo/frontend/src/components/conversation/RetranscribeConversation.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/routes/project/report/ProjectReportRoute.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
echo/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

Localization workflow is active: keep Lingui extract/compile scripts in mind when touching t/Trans strings; run pnpm messages:extract and pnpm messages:compile after changes

Files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx
  • echo/frontend/src/components/conversation/ConversationEdit.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/conversation/MoveConversationButton.tsx
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx
  • echo/frontend/src/components/conversation/ConversationAccordion.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/components/layout/ParticipantHeader.tsx
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx
  • echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefactError.tsx
  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx
  • echo/frontend/src/components/participant/verify/VerifiedArtefactItem.tsx
  • echo/frontend/src/components/participant/verify/VerifyInstructions.tsx
  • echo/frontend/src/components/project/ProjectTagsInput.tsx
  • echo/frontend/src/components/conversation/RetranscribeConversation.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/routes/project/report/ProjectReportRoute.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
echo/frontend/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

Use Lingui macros for localization: import t from @lingui/core/macro and Trans from @lingui/react/macro to localize UI strings in routed screens

Files:

  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx
  • echo/frontend/src/routes/project/report/ProjectReportRoute.tsx
echo/server/dembrane/**/*.py

📄 CodeRabbit inference engine (echo/.cursor/rules/async-threadpool.mdc)

echo/server/dembrane/**/*.py: Always wrap blocking I/O calls using run_in_thread_pool from dembrane.async_helpers in backend code. Wrap calls to directus.*, conversation_service.*, project_service.*, S3 helpers, and CPU-heavy utilities like token counting or summary generation if they are sync. Do not wrap already-async functions or LightRAG calls (e.g., rag.aquery, rag.ainsert).
Prefer converting endpoints to async def and await results rather than using synchronous functions

echo/server/dembrane/**/*.py: Store all configuration changes in dembrane/settings.py: add new env vars as fields on AppSettings, expose grouped accessors (e.g., feature_flags, directus) if multiple modules read them, and fetch config at runtime with settings = get_settings()—never import env vars directly
Populate EMBEDDING_* env vars (model, key/base URL/version) before calling dembrane.embedding.embed_text to ensure embeddings use the correct configuration

Files:

  • echo/server/dembrane/api/chat.py
  • echo/server/dembrane/service/chat.py
  • echo/server/dembrane/report_utils.py
  • echo/server/dembrane/reply_utils.py
  • echo/server/dembrane/chat_utils.py
echo/server/dembrane/api/**/*.py

📄 CodeRabbit inference engine (echo/server/AGENTS.md)

For API handlers, favor Directus queries over raw SQLAlchemy sessions when reading project/conversation data to keep behavior consistent with the admin console

Files:

  • echo/server/dembrane/api/chat.py
🧠 Learnings (20)
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/**/*.{ts,tsx} : Localization workflow is active: keep Lingui extract/compile scripts in mind when touching `t`/`Trans` strings; run `pnpm messages:extract` and `pnpm messages:compile` after changes

Applied to files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/routes/**/*.{ts,tsx} : Use Lingui macros for localization: import `t` from `lingui/core/macro` and `Trans` from `lingui/react/macro` to localize UI strings in routed screens

Applied to files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-08-19T10:14:31.647Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/chat/ChatAccordion.tsx:214-221
Timestamp: 2025-08-19T10:14:31.647Z
Learning: In the Echo frontend codebase using Lingui, i18n IDs in Trans components (e.g., `<Trans id="any.string.here">`) can be arbitrary strings and don't need to follow specific naming conventions. They are just references used in .po files, and the actual translations need to be manually defined in the locale files.

Applied to files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx
  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-09-16T08:35:18.796Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 293
File: echo/frontend/src/components/chat/TemplatesModal.tsx:34-41
Timestamp: 2025-09-16T08:35:18.796Z
Learning: In TemplatesModal.tsx, the user confirmed that titles should be used as template keys rather than IDs, consistent with their established pattern. IDs were added primarily for list rendering, not for selection logic.

Applied to files:

  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/{components,routes}/**/*.{ts,tsx} : Pair toast notifications with contextual Mantine `Alert` components inside modals/forms for inline error or warning feedback during UI mutations

Applied to files:

  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/EchoErrorAlert.tsx
  • echo/frontend/src/components/conversation/ConversationAccordion.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx
  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-09-16T08:34:38.109Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 293
File: echo/frontend/src/components/chat/ChatTemplatesMenu.tsx:15-16
Timestamp: 2025-09-16T08:34:38.109Z
Learning: In ChatTemplatesMenu.tsx, titles are preferred over IDs for template selection logic since titles are unique one-liners and work effectively as identifiers. IDs were added primarily for better list rendering rather than selection purposes.

Applied to files:

  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-09-16T08:34:44.982Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 293
File: echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx:498-501
Timestamp: 2025-09-16T08:34:44.982Z
Learning: In the chat templates system, the user prefers using localized titles as template keys rather than stable IDs, even for cross-component communication and server requests. They added IDs primarily for rendering purposes, not for selection logic.

Applied to files:

  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-08-06T13:38:30.769Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 256
File: echo/frontend/src/components/participant/MicrophoneTest.tsx:54-54
Timestamp: 2025-08-06T13:38:30.769Z
Learning: In echo/frontend/src/components/participant/MicrophoneTest.tsx, the useDisclosure hook is used where only the `close` function is needed locally (called in handleConfirmMicChange and handleCancelMicChange), while `opened` and `open` are unused because the modal state is managed by a parent component. The `showSecondModal` state variable is used separately to control content switching within the modal between the main UI and confirmation step.

Applied to files:

  • echo/frontend/src/components/chat/TemplatesModal.tsx
  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/layout/ParticipantHeader.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-11-21T12:44:30.306Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 366
File: echo/frontend/src/components/participant/refine/RefineSelection.tsx:38-38
Timestamp: 2025-11-21T12:44:30.306Z
Learning: In echo/frontend/src/components/participant/refine/RefineSelection.tsx, the refine selection panels (Make it concrete / Go deeper) should use h-[50%] when only one panel is visible to maintain half-height spacing for consistent visual weight. The flexClass logic should remain: showVerify && showEcho ? "flex-1" : "h-[50%]".

Applied to files:

  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx
📚 Learning: 2025-08-08T10:39:31.114Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 259
File: echo/frontend/src/components/layout/ParticipantLayout.tsx:33-33
Timestamp: 2025-08-08T10:39:31.114Z
Learning: In echo/frontend/src/components/layout/ParticipantLayout.tsx, prefer using simple pathname.includes("start") and pathname.includes("finish") to control the settings button visibility. No need to switch to segment-based matching or add a useEffect to auto-close the modal for these routes, per ussaama’s preference in PR #259.

Applied to files:

  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/layout/ParticipantHeader.tsx
  • echo/frontend/src/components/participant/verify/VerifySelection.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/{components,routes}/**/*.{ts,tsx} : Compose Mantine primitives (`Stack`, `Group`, `ActionIcon`, etc.) while layering Tailwind utility classes via `className`, alongside toast feedback via `@/components/common/Toaster`

Applied to files:

  • echo/frontend/src/components/participant/refine/RefineSelection.tsx
  • echo/frontend/src/components/conversation/ConversationAccordion.tsx
  • echo/frontend/src/components/project/ProjectTagsInput.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
📚 Learning: 2025-10-28T13:47:02.926Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 350
File: echo/frontend/src/components/participant/ParticipantConversationText.tsx:82-85
Timestamp: 2025-10-28T13:47:02.926Z
Learning: In text mode (echo/frontend/src/components/participant/ParticipantConversationText.tsx), participants only submit PORTAL_TEXT chunks (no audio). The “Finish” button is shown only after at least one text message is saved to Directus.

Applied to files:

  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/components/layout/ParticipantHeader.tsx
  • echo/frontend/src/components/participant/verify/VerifyArtefact.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-05-30T15:36:40.131Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 169
File: echo/frontend/src/locales/fr-FR.po:521-523
Timestamp: 2025-05-30T15:36:40.131Z
Learning: In the French localization file (fr-FR.po), "Dembrane Echo" is intentionally translated as "Echo Dembrane" for better French language flow and natural sound. This is not an error but a deliberate localization choice.

Applied to files:

  • echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/routes/settings/**/*.{ts,tsx} : Provide ergonomic navigation in settings-like routes: breadcrumb + back action (ActionIcon + navigate(-1)) with relevant iconography

Applied to files:

  • echo/frontend/src/components/participant/verify/VerifyArtefactError.tsx
📚 Learning: 2025-11-25T10:35:38.950Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-11-25T10:35:38.950Z
Learning: Applies to echo/frontend/src/components/**/hooks/index.ts : Use React Query hook hubs pattern: each feature should own a `hooks/index.ts` file exposing `useQuery`/`useMutation` wrappers with shared `useQueryClient` invalidation logic

Applied to files:

  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx
📚 Learning: 2025-08-19T10:22:55.323Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses regular useQuery (not useSuspenseQuery), which means conversationsCountQuery.data can be undefined during loading states. When using Number(conversationsCountQuery.data) ?? 0, this creates NaN because Number(undefined) = NaN and NaN is not nullish, so the fallback doesn't apply. The correct pattern is Number(conversationsCountQuery.data ?? 0) to ensure the fallback happens before type conversion.

Applied to files:

  • echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx
📚 Learning: 2025-05-30T15:38:44.413Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 169
File: echo/frontend/src/components/project/ProjectPortalEditor.tsx:409-464
Timestamp: 2025-05-30T15:38:44.413Z
Learning: Badge-based selectors in ProjectPortalEditor.tsx: Keyboard navigation enhancements for accessibility are considered optional improvements rather than critical issues. The user acknowledges these suggestions but doesn't prioritize them as blockers.

Applied to files:

  • echo/frontend/src/components/project/ProjectTagsInput.tsx
  • echo/frontend/src/components/project/ProjectPortalEditor.tsx
  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-10-15T11:06:42.397Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 336
File: echo/server/dembrane/api/chat.py:593-630
Timestamp: 2025-10-15T11:06:42.397Z
Learning: In `echo/server/dembrane/api/chat.py`, the auto-select conversation flow (lines 589-631) deliberately uses incremental system message generation with `create_system_messages_for_chat` and `token_counter` for each candidate conversation to ensure accurate token count estimation before adding conversations. The user (ussaama) prioritizes accuracy over the O(n²) performance cost to stay within the 80% context threshold precisely.

Applied to files:

  • echo/server/dembrane/api/chat.py
  • echo/server/dembrane/service/chat.py
  • echo/server/dembrane/reply_utils.py
  • echo/server/dembrane/chat_utils.py
📚 Learning: 2025-06-06T05:43:44.935Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 180
File: echo/frontend/src/locales/es-ES.ts:1-1
Timestamp: 2025-06-06T05:43:44.935Z
Learning: When users indicate they have already addressed translation issues in locale files, verify the current state before assuming the issue persists. Translation files are often updated in bulk and previously flagged English text may have been properly localized.

Applied to files:

  • echo/frontend/src/locales/en-US.po
📚 Learning: 2025-10-28T13:47:02.926Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 350
File: echo/frontend/src/components/participant/ParticipantConversationText.tsx:82-85
Timestamp: 2025-10-28T13:47:02.926Z
Learning: The participant “finish” flow should call finishConversation(conversationId) (echo/frontend/src/lib/api.ts) before navigating to /:projectId/conversation/:conversationId/finish so the backend sets is_finished and triggers task_finish_conversation_hook.

Applied to files:

  • echo/frontend/src/locales/en-US.po
🧬 Code graph analysis (6)
echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx (1)
echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx (1)
  • VerifiedArtefactsSection (43-131)
echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx (1)
echo/frontend/src/components/participant/verify/hooks/index.ts (1)
  • useVerificationTopics (13-20)
echo/server/dembrane/api/chat.py (2)
echo/server/dembrane/service/chat.py (4)
  • ChatServiceException (9-10)
  • ChatNotFoundException (13-14)
  • set_chat_name (148-158)
  • update_message (266-278)
echo/server/dembrane/async_helpers.py (1)
  • run_in_thread_pool (74-142)
echo/server/dembrane/service/chat.py (1)
echo/server/dembrane/directus.py (1)
  • bulk_insert (696-710)
echo/server/dembrane/reply_utils.py (2)
echo/server/dembrane/transcribe.py (1)
  • _get_audio_file_object (154-174)
echo/server/dembrane/async_helpers.py (1)
  • run_in_thread_pool (74-142)
echo/server/dembrane/chat_utils.py (2)
echo/server/dembrane/async_helpers.py (1)
  • run_in_thread_pool (74-142)
echo/server/dembrane/directus.py (2)
  • get_items (648-654)
  • get (303-333)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-check-server
🔇 Additional comments (61)
echo/server/prompt_templates/generate_artifact.en.jinja (1)

1-1: LGTM—pluralization is chef's kiss.

Solid call on the pluralization. "Audio fragments" correctly reflects the optionality described in the prompt. Prompt template logic is clean and unaffected.

echo/frontend/src/components/layout/ParticipantHeader.tsx (2)

79-79: Confirm localization workflow completed: run pnpm messages:extract and pnpm messages:compile.

You've updated a Lingui Trans string ID from participant.verify.instructions.button.cancel to participant.concrete.instructions.button.cancel. Per the coding guidelines, after touching t/Trans strings, you must run the message extraction and compilation scripts to keep translation catalogs in sync. This is a hard requirement—skipping it breaks i18n.

Verify that:

  1. pnpm messages:extract has been run to capture the new key
  2. pnpm messages:compile has been run to compile updated translation files
  3. The new key participant.concrete.instructions.button.cancel exists in all translation files (e.g., locales/en-US.po)
  4. Any stale participant.verify.* keys have been removed or are no longer in use

1-102: Component structure and composition: LGTM.

The component correctly composes Mantine primitives (Group, Box, Button, ActionIcon) layered with Tailwind utilities. Routing logic uses simple pathname.includes() checks (aligned with established preferences), and the settings button visibility toggle is clean. The useDisclosure hook for the settings modal is properly scoped.

echo/frontend/src/components/conversation/MoveConversationButton.tsx (1)

131-133: Copy tweak from Experimental → Beta looks solid; keep Lingui pipeline in sync.

This is a straight text flip inside Trans with zero behavioral blast radius. Just make sure you run pnpm messages:extract and pnpm messages:compile so Lingui catalogs pick up the new string and you don’t ship stale message keys. As per coding guidelines.

echo/frontend/src/components/participant/refine/RefineSelection.tsx (1)

3-3: LGTM! Clean icon swap.

Ship it. Swapping IconMessageFilled for IconMessage is a straightforward cosmetic change—zero functional impact, just tweaking the visual weight. Fits the broader UI polish vibe across the PR.

Also applies to: 62-62

echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx (1)

792-792: LGTM! Clean color simplification.

Switching from "green.2" to "green" for the success Alert streamlines the color system. Solid choice for the upload completion state.

echo/frontend/src/components/conversation/RetranscribeConversation.tsx (1)

111-111: LGTM – badge evolution looks clean! 🚀

Experimental → Beta, ship it. Just don't forget to run the i18n pipeline after merging:

pnpm messages:extract
pnpm messages:compile

As per coding guidelines, gotta keep those translation catalogs fresh when you touch Trans strings.

echo/frontend/src/components/chat/TemplatesModal.tsx (1)

118-118: Original review comment is accurate and well-founded.

Verification confirms:

  • Trans component is correctly imported from @lingui/react/macro (line 2)
  • Line 118 contains the branding text change inside <Trans>Want to add a template to "Dembrane"?</Trans>
  • File matches the coding guideline pattern: echo/frontend/src/components/**/*.{ts,tsx}
  • The localization workflow requirement applies—any text change within Trans/t macros requires running extraction and compilation
  • The suggested commands are correct and necessary

The review comment appropriately reminds the developer about the mandatory localization workflow steps per coding guidelines.

echo/frontend/src/components/project/ProjectTagsInput.tsx (1)

20-31: Mantine Badge import is correctly wired

Importing Badge alongside the existing Mantine primitives matches the updated pill implementation below; no issues here. Ultra-straightforward diff, LGTM.

echo/server/prompt_templates/get_reply_system.es.jinja (1)

20-30: ES localization clean and in lockstep with EN contract

The Spanish variant preserves the same contract as EN (transcript/audio note, 1–3 sentence output, collective “nosotros/nuestro” voice, language matching, uncertainty handling). Nothing here looks off from a behavioral standpoint.

echo/server/prompt_templates/get_reply_system.nl.jinja (1)

20-30: NL prompt tracks the EN behavior 1:1

Dutch text carries over the same behavioral contract (priority of main transcript, transcript/audio note, 1–3 sentence cap, “wij/onze” collective voice, language matching, uncertainty). Looks good from a product and prompt‑design standpoint.

echo/server/prompt_templates/get_reply_system.fr.jinja (1)

20-30: FR template is in sync with EN and reads fine

French text keeps the same protocol (transcript/audio mention, 1–3 sentence constraint, “nous/notre” voice, language matching, uncertainty acknowledgement). No semantic drift from EN that would surprise the model or the product.

echo/server/prompt_templates/get_reply_system.en.jinja (1)

20-30: Verification passed: no backend/frontend code references old wrapper tags

The rg search found zero references to <detailed_analysis>, </detailed_analysis>, <response>, or </response> anywhere in the codebase. The prompt contract simplification and transcript notes are safe to land—this is not a silent breaking change.

The 1–3 sentence constraint, "we/our" language guidance, and audio fragment acknowledgment align well with the stated transcript-enhancement goals.

echo/server/dembrane/chat_utils.py (3)

30-47: Module-level initialization could crash imports if model info unavailable.

The get_model_info and get_completion_kwargs calls happen at import time. If these fail (e.g., invalid model name, missing config), the entire module becomes unimportable. Consider lazy initialization or wrapping in try/except with sensible defaults.

That said, this pattern is common enough and the fallback to 128000 tokens is reasonable. Ship it if you've validated the model config in your env. LGTM otherwise.


411-425: Solid retry logic with backoff. LGTM.

The exponential backoff with 5-minute max time and proper exception types is exactly what you want for LLM calls. The centralized CHAT_LLM usage keeps things DRY.


520-563: Elegant recursive batch splitting for context overflow. LGTM.

The binary split approach when prompt exceeds context is clean. Single-conversation overflow handling is sensible - you can't split further, so skip it. 10x engineer pattern right here.

echo/directus/sync/collections/permissions.json (4)

1065-1084: Validation relaxed from owner check to existence check.

Changed from validating project_chat_id.project_id.directus_user_id == $CURRENT_USER to just project_chat_id._nnull. This allows any authenticated user to create project_chat_conversation records for any chat, not just their own projects.

Verify this is the intended fix for ECHO-572 permission issues. If regular users should only add conversations to their own chats, this might be too permissive.


1205-1216: Validation completely removed for project_chat_message_conversation.create.

Setting validation to null means any authenticated user can create message-conversation links without ownership checks. Combined with the previous change, this opens up the chat system significantly. Confirm this aligns with your access control requirements.


1528-1559: project_report.create validation relaxed similarly.

Owner validation replaced with simple project_id._nnull check. Any authenticated user can now create reports for any project they can reference. Intentional per ECHO-572?


2175-2203: New public read permission for project collection. LGTM.

Exposes safe, non-sensitive fields (language, UI labels, feature flags) publicly. No PII or sensitive data. This enables the conversation portal to fetch project config without auth. Clean.

echo/server/dembrane/reply_utils.py (3)

84-122: Clean datetime parsing and audio chunk selection. LGTM.

The _parse_directus_datetime handles the ISO 'Z' suffix edge case properly. select_audio_chunks_for_reply logic is straightforward - only chunks without transcripts, newer than last reply, with audio paths. Matches the verify.py pattern per docstring.


408-441: Multimodal message building looks solid. LGTM.

Nice progressive enhancement pattern - text prompt first, then audio chunks with metadata. The run_in_thread_pool wrapper for _get_audio_file_object is correct since it does blocking I/O. The thinking param with budget tokens is a nice touch for Claude models.


453-462: Simplified streaming - direct yield without tag buffering. LGTM.

The old tag-stripping complexity is gone. Clean yield content as chunks arrive. This is the way.

echo/server/dembrane/report_utils.py (6)

28-48: Same module-level initialization pattern. Consistent with chat_utils.py.

Same potential import-time failure risk, same reasonable fallback. At least you're consistent across modules. LGTM if the model config is validated in deployment.


57-82: ReportGenerationError with cause chaining. Clean exception design. LGTM.

The cause field for exception chaining is proper. The backoff decorator with logging callback is chef's kiss.


85-146: Fault-tolerant helper functions. This is the way. LGTM.

_safe_summarize_conversation and _safe_get_transcript return structured results instead of throwing. _fetch_conversations properly wraps blocking I/O. Per coding guidelines, directus calls are wrapped correctly. The error categorization with DirectusGenericException vs general Exception is solid.


198-219: Batch summarization with proper fault isolation. LGTM.

asyncio.gather(*tasks, return_exceptions=True) ensures one failure doesn't cancel the batch. Logging the failure count gives observability without halting progress. 10x reliability pattern.


273-281: Token counting with fallback estimation. Pragmatic. LGTM.

When token_counter fails, falling back to len(summary) // 4 is a reasonable heuristic (~4 chars per token average). Better than crashing.


381-410: Comprehensive LLM error handling with Sentry. LGTM.

Every exception type gets logged, captured in Sentry, and wrapped in ReportGenerationError. The distinction between retryable errors (caught after exhausting retries) and non-retryable errors is correct. Production-ready error handling.

echo/frontend/src/components/conversation/ConversationEdit.tsx (1)

143-145: Tag pill styling with Mantine CSS variable. LGTM.

Using var(--mantine-primary-color-light) keeps it theme-aware. The ! prefix for specificity is sometimes necessary with Mantine's default styles. Per coding guidelines, Tailwind utilities via className on Mantine primitives is the pattern. Ship it.

echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx (1)

576-577: LGTM! Branding update is clean.

The shift from "ECHO" to "Dembrane" in the AI disclaimer text is solid. Per coding guidelines, remember to run pnpm messages:extract and pnpm messages:compile after these string changes to keep your localization workflow tight.

Based on coding guidelines, localization workflow is active.

Also applies to: 606-607

echo/frontend/src/components/participant/verify/VerifyArtefactError.tsx (1)

19-21: LGTM! Translation key migration is consistent.

The verify → concrete namespace migration across all four Trans components is clean. Just keep that Lingui workflow tight—run pnpm messages:extract and pnpm messages:compile post-merge.

Based on coding guidelines, localization workflow is active.

Also applies to: 24-29, 40-43, 52-55

echo/frontend/src/components/participant/ParticipantConversationAudio.tsx (1)

275-278: LGTM! Modal title i18n keys updated cleanly.

The refine modal title translation keys now use concrete/go-deeper terminology. Solid. Don't forget to run pnpm messages:extract and pnpm messages:compile to sync your locale files.

Based on coding guidelines, localization workflow is active.

Also applies to: 282-285

echo/frontend/src/components/participant/verify/VerifyArtefactLoading.tsx (1)

13-16: LGTM! Loading state i18n keys migrated.

The translation keys now use the concrete namespace. Clean and consistent. Run pnpm messages:extract and pnpm messages:compile to keep your localization workflow in sync.

Based on coding guidelines, localization workflow is active.

Also applies to: 18-21

echo/frontend/src/components/participant/verify/VerifyInstructions.tsx (2)

17-20: LGTM! Instruction text i18n keys migrated.

All five instruction items now use the concrete namespace. Clean migration. Remember to run pnpm messages:extract and pnpm messages:compile to sync locale files.

Based on coding guidelines, localization workflow is active.

Also applies to: 25-29, 34-38, 43-47, 52-55


92-110: LGTM! Button sizing and i18n updates ship together.

The Next button got bumped from lg to xl for better UX prominence, and the button text keys now use the concrete namespace. Both changes look intentional and aligned with the PR's concrete workflow polish.

Based on coding guidelines, localization workflow is active.

echo/frontend/src/routes/project/conversation/ProjectConversationOverview.tsx (2)

53-53: LGTM! Language field added for locale-aware rendering.

Adding "language" to the project query fields enables the locale-aware topic label mapping downstream in VerifiedArtefactsSection. Solid foundation for i18n-aware artefact display.


164-170: LGTM! VerifiedArtefactsSection invocation enhanced with project context.

The conditional now requires both conversationId and projectId, and you're passing projectLanguage to enable locale-aware topic label rendering. The defensive check prevents partial data rendering. Clean.

echo/frontend/src/components/conversation/ConversationAccordion.tsx (2)

455-463: LGTM! Tag pill styling standardized.

The Pill now uses classNames.root with Mantine primary color light background and medium font weight. Clean styling override that composes Mantine primitives with Tailwind utilities per coding guidelines.

Based on coding guidelines, compose Mantine primitives while layering Tailwind utility classes via className.


1070-1086: LGTM! Selected tag Pills match the styling.

The removable Pills in the tags menu use the same classNames.root override as ConversationProjectTagPill. Consistent styling across the tag UI. Solid.

Based on coding guidelines, compose Mantine primitives while layering Tailwind utility classes via className.

echo/server/dembrane/service/chat.py (3)

161-169: LGTM! Enhanced logging and defensive programming.

The debug log at entry and the early-return warning when no conversations are provided improve observability. The empty-check prevents unnecessary Directus calls. Solid defensive pattern.


163-165: LGTM! Direct junction insert bypasses nested validation issues.

Switching from nested-create to direct bulk_insert on the project_chat_conversation junction table is the right move. The payload now explicitly includes both conversation_id and project_chat_id, and the comment clearly explains why—avoids Directus nested validation headaches. Clean refactor.

Also applies to: 173-175


177-183: LGTM! Error logging now includes conversation IDs.

Casting conversation_ids to a list in the error log provides better debugging context when attach operations fail. Small but valuable observability win.

echo/frontend/src/components/participant/verify/VerifySelection.tsx (1)

183-186: LGTM on the terminology migration and button sizing.

Ship it. Translation keys properly migrated from verifyconcrete namespace. Button size bump to xl is solid UX. Remember to run pnpm messages:extract and pnpm messages:compile after merging to keep Lingui happy.

Also applies to: 225-225, 237-237

echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx (2)

55-67: Solid locale-aware topic label resolution.

Clean implementation with proper fallback chain. The Map construction is idiomatic and the triple fallback (locale → en-US → key) handles edge cases gracefully.


108-110: LGTM on the label lookup.

Defensive fallback to artefact.key with empty string default. Ship it.

echo/frontend/src/components/participant/verify/VerifyArtefact.tsx (2)

303-311: Translation keys properly migrated to concrete namespace.

participant.verify.*participant.concrete.* migration is consistent across all UI strings. LGTM.


316-331: Clean conditional rendering for read-aloud control.

Only showing the ActionIcon when readAloudUrl exists, with ml="auto" for right-alignment. Solid UX pattern.

echo/frontend/src/locales/en-US.po (3)

131-285: Obsolete translation keys properly deprecated.

All the old participant.verify.* keys are marked with #~ as per Lingui conventions. Clean deprecation.


303-311: New concrete namespace keys look good.

Translation strings align with the UI component changes. Terminology migration from "Verify" → "Make it concrete" / "Go deeper" is consistent.

Also applies to: 552-556, 1347-1354


4206-4210: Nice touch on the instruction text update.

"You'll soon get {objectLabel} to make them concrete." - clearer than the previous "to verify" wording.

echo/server/dembrane/api/chat.py (5)

117-122: Chef's kiss on the documentation.

This comment is 10x. Future engineers will thank you for explaining why global chat_service is used here instead of user-scoped client. Junction table permission edge cases are gnarly.


247-259: Solid parallelization of token count fetching.

asyncio.gather for independent I/O operations is the way. The strict=True on zip catches any metadata/token_counts length mismatch at runtime. LGTM.


516-547: Nice parallel fetch + write pattern.

Running get_project_chat_history, get_chat_context, and generate_title concurrently is a clean latency optimization. The subsequent parallel writes for title and template_key are fire-and-forget style but exceptions will still propagate since return_exceptions isn't set.


291-292: Consistent service instantiation pattern.

Local aliasing chat_svc = chat_service keeps the code clean while using the global instance. Consistent across all endpoints now.


161-168: LGTM.

Global service usage is consistent with the authorization comment above. Per coding guidelines, Directus queries are preferred over raw SQLAlchemy for project/conversation data reads.

echo/frontend/src/components/project/ProjectPortalEditor.tsx (2)

137-158: Badge-based noun pills looking crisp.

Clean refactor from the old pill UI. ActionIcon in rightSection is the right move for removal affordance. Inline styles are minimal, fontWeight and textTransform keep it clean.


726-870: Make it concrete section ships clean.

Structure mirrors Go deeper (Group for header, Stack for description). Switch + topic selection Badges work as expected. The last-topic guard at lines 836-844 is solid UX—can't deselect the final topic, and the toast message is properly i18n'd with the t macro.

Loading and empty states handled gracefully. Data-driven Badge rendering keeps it maintainable.

echo/frontend/src/routes/project/report/ProjectReportRoute.tsx (1)

61-61: Localization workflow confirmed - ship it!

The Lingui extract/compile flow was successfully executed. Evidence:

  • Locale files updated: All .po and .ts files show identical modification timestamps (Nov 26 18:31)
  • "Beta" extracted to all catalogs: Verified in en-US.po (line 760), de-DE.po (line 456), es-ES.po (line 460), fr-FR.po (line 452), and nl-NL.po (line 452)
  • Compiled successfully: All .ts TypeScript message files contain the extracted "Beta" string
  • Bulk extraction run: ~22k insertions across all locale files indicates full pnpm messages:extract && pnpm messages:compile execution

The code change at line 61 is correct (<Trans>Beta</Trans>), properly wrapped in the Lingui macro, and the localization workflow has been fully executed. No action required.

echo/frontend/src/components/participant/EchoErrorAlert.tsx (2)

23-24: Ship it! Button label updated to match new UX terminology.

The "Go deeper" label is crisp and consistent with the rebrand. Error messaging stays actionable.


16-16: LGTM! Translation keys successfully migrated to new terminology.

Clean refactor from participant.echo.* to participant.go.deeper.* keys, with consistent button label update ("Go deeper"). Component structure properly maintains Mantine Alert with Lingui Trans macro integration.

Per coding guidelines, sync these localization changes with the Lingui workflow:

#!/bin/bash
cd echo/frontend
pnpm messages:extract
pnpm messages:compile

Also applies to: 21-21

Comment on lines 35 to 41
const LANGUAGE_TO_LOCALE: Record<string, string> = {
de: "de-DE",
en: "en-US",
es: "es-ES",
fr: "fr-FR",
nl: "nl-NL",
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting LANGUAGE_TO_LOCALE to a shared constant.

This mapping is duplicated in VerifySelection.tsx. Classic DRY opportunity - extract to a shared locale utils file and import in both places. Not a blocker, but future-you will thank past-you.

// e.g., @/lib/locale-utils.ts
export const LANGUAGE_TO_LOCALE: Record<string, string> = {
  de: "de-DE",
  en: "en-US",
  es: "es-ES",
  fr: "fr-FR",
  nl: "nl-NL",
};
🤖 Prompt for AI Agents
In echo/frontend/src/components/conversation/VerifiedArtefactsSection.tsx around
lines 35 to 41 the LANGUAGE_TO_LOCALE mapping is duplicated elsewhere
(VerifySelection.tsx); extract this mapping into a shared module (e.g.,
echo/frontend/src/lib/locale-utils.ts or @/lib/locale-utils.ts) as a named
export (export const LANGUAGE_TO_LOCALE: Record<string,string> = {...}), remove
the local constant from both VerifiedArtefactsSection.tsx and
VerifySelection.tsx, and import LANGUAGE_TO_LOCALE from the new shared module in
both files (update import paths and any type references accordingly).

Comment on lines 39 to 44
<ActionIcon
variant="subtle"
color="blue"
aria-label="verified artefact"
aria-label="concrete artefact"
size={22}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Aria-label rename matches the Verify→Concrete rebrand; consider future i18n polish

Switching the aria-label to "concrete artefact" keeps this icon aligned with the new product language and doesn’t introduce any behavioral or a11y regressions. For extra polish later, you might want to drive this through Lingui (and possibly tighten the copy to something like “concrete participant artefact”) so screen-reader text stays translatable and consistent with the rest of the UI.

🤖 Prompt for AI Agents
In echo/frontend/src/components/participant/verify/VerifiedArtefactItem.tsx
around lines 39 to 44, update the ActionIcon's aria-label from its current value
to "concrete artefact" to match the Verify→Concrete rebrand; change the literal
aria-label to that exact string and, for future polish, replace the literal with
a Lingui translation call (e.g., use the appropriate t`...` or
i18n.t('concreteParticipantArtefact') key) so the label becomes translatable and
can be tightened later to "concrete participant artefact" if desired.

Comment on lines 542 to 724
<Stack gap="md">
<Group>
<Title order={4}>
<Trans>Go deeper</Trans>
</Title>
<Logo hideTitle />
<Badge>
<Trans>Beta</Trans>
</Badge>
</Group>

<Stack gap="md">
<Group>
<Title order={4}>
<Trans>Dembrane ECHO</Trans>
</Title>
<Logo hideTitle />
<Badge>
<Trans>Experimental</Trans>
</Badge>
</Group>

<Text size="sm" c="dimmed">
<Trans>
Enable this feature to allow participants to request
AI-powered responses during their conversation.
Participants can click "ECHO" after recording their
thoughts to receive contextual feedback, encouraging
deeper reflection and engagement. A cooldown period
applies between requests.
</Trans>
</Text>
<Text size="sm" c="dimmed">
<Trans>
Enable this feature to allow participants to request
AI-powered responses during their conversation.
Participants can click "Go deeper" after recording
their thoughts to receive contextual feedback,
encouraging deeper reflection and engagement. A
cooldown period applies between requests.
</Trans>
</Text>

<Controller
name="is_get_reply_enabled"
control={control}
render={({ field }) => (
<Switch
label={
<FormLabel
label={t`Enable Dembrane ECHO`}
isDirty={formState.dirtyFields.is_get_reply_enabled}
error={
formState.errors.is_get_reply_enabled?.message
<Controller
name="is_get_reply_enabled"
control={control}
render={({ field }) => (
<Switch
label={
<FormLabel
label={t`Enable Go deeper`}
isDirty={
formState.dirtyFields.is_get_reply_enabled
}
error={
formState.errors.is_get_reply_enabled?.message
}
/>
}
checked={field.value}
onChange={(e) =>
field.onChange(e.currentTarget.checked)
}
/>
}
checked={field.value}
onChange={(e) =>
field.onChange(e.currentTarget.checked)
}
)}
/>
)}
/>

<Controller
name="get_reply_mode"
control={control}
render={({ field }) => (
<Stack gap="xs">
<FormLabel
label={t`Mode`}
isDirty={formState.dirtyFields.get_reply_mode}
error={formState.errors.get_reply_mode?.message}
/>
<Text size="sm" c="dimmed">
<Trans>
Select the type of feedback or engagement you want
to encourage.
</Trans>
</Text>
<Group gap="xs">
<Badge
className={
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value === "summarize" ? "filled" : "default"
}
size="lg"
style={{
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() =>
watchedReplyEnabled && field.onChange("summarize")
}
>
<Trans>Summarize</Trans>
</Badge>
<Badge
className={
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value === "brainstorm"
? "filled"
: "default"
}
size="lg"
style={{
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() =>
watchedReplyEnabled &&
field.onChange("brainstorm")
}
>
<Trans>Brainstorm Ideas</Trans>
</Badge>
<Badge
className={
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value === "custom" ? "filled" : "default"
}
size="lg"
style={{
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() =>
watchedReplyEnabled && field.onChange("custom")
}
>
<Trans>Custom</Trans>
</Badge>
</Group>
</Stack>
)}
/>

{watchedReplyMode === "custom" && (
<Controller
name="get_reply_prompt"
control={control}
render={({ field }) => (
<Textarea
label={
<Controller
name="get_reply_mode"
control={control}
render={({ field }) => (
<Stack gap="xs">
<FormLabel
label={t`Reply Prompt`}
isDirty={formState.dirtyFields.get_reply_prompt}
error={formState.errors.get_reply_prompt?.message}
label={t`Mode`}
isDirty={formState.dirtyFields.get_reply_mode}
error={formState.errors.get_reply_mode?.message}
/>
}
description={
<Box className="pb-2">
<Text size="sm" c="dimmed">
<Trans>
This prompt guides how the AI responds to
participants. Customize it to shape the type of
feedback or engagement you want to encourage.
Select the type of feedback or engagement you
want to encourage.
</Trans>
</Box>
}
autosize
minRows={5}
disabled={!watchedReplyEnabled}
{...field}
/>
)}
/>
)}
</Stack>

<Divider />

<Stack gap="md">
<Group>
<Title order={4}>
<Trans id="dashboard.dembrane.verify.title">
Dembrane Verify
</Trans>
</Title>
<Logo hideTitle />
<Badge>
<Trans id="dashboard.dembrane.verify.experimental">
Experimental
</Trans>
</Badge>
</Group>

<Text size="sm" c="dimmed">
<Trans id="dashboard.dembrane.verify.description">
Enable this feature to allow participants to create and
approve "verified objects" from their submissions. This
helps crystallize key ideas, concerns, or summaries. After
the conversation, you can filter for discussions with
verified objects and review them in the overview.
</Trans>
</Text>

<Controller
name="is_verify_enabled"
control={control}
render={({ field }) => (
<Switch
label={
<FormLabel
label={t`Enable Dembrane Verify`}
isDirty={formState.dirtyFields.is_verify_enabled}
error={formState.errors.is_verify_enabled?.message}
/>
}
checked={field.value}
onChange={(e) =>
field.onChange(e.currentTarget.checked)
}
/>
)}
/>

<Controller
name="verification_topics"
control={control}
render={({ field }) => (
<Stack gap="xs">
<FormLabel
label={t`Verification Topics`}
isDirty={!!formState.dirtyFields.verification_topics}
error={formState.errors.verification_topics?.message}
/>
<Text size="sm" c="dimmed">
<Trans id="dashboard.dembrane.verify.topic.select">
Select which topics participants can use for
verification.
</Trans>
</Text>
{isVerificationTopicsLoading ? (
<Text size="sm" c="dimmed">
<Trans>Loading verification topics…</Trans>
</Text>
) : availableVerifyTopics.length === 0 ? (
<Text size="sm" c="dimmed">
<Trans>No verification topics available.</Trans>
</Text>
) : (
<Group gap="xs">
{availableVerifyTopics.map((topic) => (
</Text>
<Group gap="xs">
<Badge
key={topic.key}
className={
watchedVerifyEnabled
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value.includes(topic.key)
? "filled"
field.value === "summarize"
? "light"
: "default"
}
c="black"
size="lg"
fw={500}
style={{
cursor: watchedVerifyEnabled
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedVerifyEnabled ? 1 : 0.6,
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() => {
if (!watchedVerifyEnabled) return;
const normalizedCurrent = normalizeTopicList(
field.value ?? [],
);
const isSelected = normalizedCurrent.includes(
topic.key,
);

// Prevent deselecting the last topic
if (
isSelected &&
normalizedCurrent.length === 1
) {
toast.error(
t`At least one topic must be selected to enable Dembrane Verify`,
);
return;
}

const updated = isSelected
? normalizedCurrent.filter(
(item) => item !== topic.key,
)
: normalizeTopicList([
...normalizedCurrent,
topic.key,
]);
field.onChange(updated);
onClick={() =>
watchedReplyEnabled &&
field.onChange("summarize")
}
>
<Trans>Default</Trans>
</Badge>
<Badge
className={
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value === "brainstorm"
? "light"
: "default"
}
c="black"
size="lg"
fw={500}
style={{
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() =>
watchedReplyEnabled &&
field.onChange("brainstorm")
}
>
<Group gap="xs">
{topic.icon ? (
<span>{topic.icon}</span>
) : null}
<span>{topic.label}</span>
</Group>
<Trans>Brainstorm Ideas</Trans>
</Badge>
))}
</Group>
<Badge
className={
watchedReplyEnabled
? "cursor-pointer capitalize"
: "capitalize"
}
variant={
field.value === "custom" ? "light" : "default"
}
c="black"
size="lg"
fw={500}
style={{
cursor: watchedReplyEnabled
? "pointer"
: "not-allowed",
opacity: watchedReplyEnabled ? 1 : 0.6,
}}
onClick={() =>
watchedReplyEnabled &&
field.onChange("custom")
}
>
<Trans>Custom</Trans>
</Badge>
</Group>
</Stack>
)}
</Stack>
)}
/>
</Stack>
/>

{watchedReplyMode === "custom" && (
<Controller
name="get_reply_prompt"
control={control}
render={({ field }) => (
<Textarea
label={
<FormLabel
label={t`Reply Prompt`}
isDirty={
formState.dirtyFields.get_reply_prompt
}
error={
formState.errors.get_reply_prompt?.message
}
/>
}
description={
<Box className="pb-2">
<Trans>
This prompt guides how the AI responds to
participants. Customize it to shape the type
of feedback or engagement you want to
encourage.
</Trans>
</Box>
}
autosize
minRows={5}
disabled={!watchedReplyEnabled}
{...field}
/>
)}
/>
)}
</Stack>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Go deeper feature wired up solid.

Title + Logo + Badge in Group, description in Stack—clean separation. Switch control is straightforward. Mode selection Badges handle the conditional styling and disabled states correctly based on watchedReplyEnabled.

The three mode Badges (Default, Brainstorm, Custom) at lines 605-683 follow a repetitive pattern. Could factor out a ModeButton component if you're feeling DRY, but for three options it's readable as-is.

🤖 Prompt for AI Agents
In echo/frontend/src/components/project/ProjectPortalEditor.tsx around lines 542
to 724, the three mode Badge blocks (lines ~605-683) repeat the same props and
styling; extract a small ModeButton component that accepts label, value,
currentValue, enabled, and onSelect to render a Badge with the existing variant,
style, className and onClick logic, then replace the three inline Badge usages
with ModeButton calls passing field.onChange for onSelect and
watchedReplyEnabled/current value; ensure TypeScript props are typed and
preserve accessibility, size, fw, c and the disabled cursor/opacity behavior.

Comment on lines 872 to 885
<Stack gap="md">
<Group>
<Title order={4}>
<Trans>Report Notifications</Trans>
</Title>
<Text size="sm" c="dimmed">
<Trans>
Enable this feature to allow participants to receive
notifications when a report is published or updated.
Participants can enter their email to subscribe for
updates and stay informed.
</Trans>
</Text>
</Group>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Layout inconsistency in Report Notifications section.

Lines 873-885 wrap both the Title and the description Text inside the Group. This differs from the Go deeper and Make it concrete sections, which use Group for the header row (Title + Logo + Badge) and place the description Text separately in the Stack.

The current structure will horizontally align the description with the title instead of stacking it below.

Apply this diff to match the pattern:

 <Stack gap="md">
   <Group>
     <Title order={4}>
       <Trans>Report Notifications</Trans>
     </Title>
+  </Group>
     <Text size="sm" c="dimmed">
       <Trans>
         Enable this feature to allow participants to receive
         notifications when a report is published or updated.
         Participants can enter their email to subscribe for
         updates and stay informed.
       </Trans>
     </Text>
-  </Group>
   <Controller
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Stack gap="md">
<Group>
<Title order={4}>
<Trans>Report Notifications</Trans>
</Title>
<Text size="sm" c="dimmed">
<Trans>
Enable this feature to allow participants to receive
notifications when a report is published or updated.
Participants can enter their email to subscribe for
updates and stay informed.
</Trans>
</Text>
</Group>
<Stack gap="md">
<Group>
<Title order={4}>
<Trans>Report Notifications</Trans>
</Title>
</Group>
<Text size="sm" c="dimmed">
<Trans>
Enable this feature to allow participants to receive
notifications when a report is published or updated.
Participants can enter their email to subscribe for
updates and stay informed.
</Trans>
</Text>
🤖 Prompt for AI Agents
In echo/frontend/src/components/project/ProjectPortalEditor.tsx around lines 872
to 885, the Report Notifications block currently places both the Title and the
descriptive Text inside the same Group which causes horizontal alignment; update
the JSX to match the other sections by keeping Group as the header row (Title
and any icons/badges) and move the descriptive <Text> out of the Group as a
separate sibling inside the surrounding <Stack> so the description stacks under
the title with the same spacing and styling as the Go deeper / Make it concrete
sections.

Comment on lines 80 to 107
return (
<div
<Badge
ref={setNodeRef}
style={{
...style,
alignItems: "center",
background: "var(--mantine-color-primary-1)",
borderRadius: "var(--pill-radius, 1000rem)",
display: "inline-flex",
height: "var(--pill-height)",
lineHeight: 1,
paddingInline: "0.8em",
whiteSpace: "nowrap",
fontWeight: 500,
textTransform: "none",
}}
variant="light"
c="black"
size="lg"
rightSection={
<ActionIcon
onClick={(e) => handleDelete(e)}
size="xs"
variant="transparent"
c="gray.8"
onPointerDown={(e) => e.stopPropagation()}
>
<IconX size={14} />
</ActionIcon>
}
{...attributes}
{...listeners}
>
<Text size="sm" className="font-normal">
{tag.text}
</Text>
<ActionIcon
onClick={(e) => handleDelete(e)}
size="xs"
variant="transparent"
c="gray.8"
className="ml-2"
onPointerDown={(e) => e.stopPropagation()}
>
<IconX />
</ActionIcon>
</div>
<span>{tag.text}</span>
</Badge>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Badge-based pill + DnD wiring looks tight; only micro polish possible

Nice upgrade: using Badge as the draggable root with setNodeRef, spreading attributes/listeners, and pushing the delete ActionIcon into rightSection keeps the DOM lean while preserving drag + delete semantics. onPointerDown + stopPropagation on the icon is the right move to avoid accidental drags while deleting.

If you want to shave a few atoms of noise later, you could:

  • Pass the handler directly: onClick={handleDelete} instead of wrapping it in an arrow.
  • Loosen the event typing on handleDelete to React.MouseEvent<HTMLElement> so it stays robust if the underlying ActionIcon element ever changes.

Both are non-blockers; current implementation is solid and production-ready.

🤖 Prompt for AI Agents
In echo/frontend/src/components/project/ProjectTagsInput.tsx around lines 81 to
107, simplify the delete handler wiring by passing the handler directly to
ActionIcon (replace onClick={(e) => handleDelete(e)} with
onClick={handleDelete}) and loosen the TypeScript event type on the handleDelete
function signature to React.MouseEvent<HTMLElement> so it remains robust if the
underlying element changes; update the function signature import/type
accordingly to avoid type errors.

Comment on lines +164 to +165
project_list = await run_in_thread_pool(directus.get_items, "project", project_query)
project = project_list[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

IndexError risk when project not found.

project_list[0] raises IndexError if the list is empty, but you're catching KeyError. The error message would be misleading.

         project_list = await run_in_thread_pool(directus.get_items, "project", project_query)
+        if not project_list:
+            raise ValueError(f"Invalid project id: {project_id}")
         project = project_list[0]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
project_list = await run_in_thread_pool(directus.get_items, "project", project_query)
project = project_list[0]
project_list = await run_in_thread_pool(directus.get_items, "project", project_query)
if not project_list:
raise ValueError(f"Invalid project id: {project_id}")
project = project_list[0]
🤖 Prompt for AI Agents
In echo/server/dembrane/chat_utils.py around lines 164-165, accessing
project_list[0] can raise IndexError when no project is returned but the code
only catches KeyError; update the logic to check if project_list is empty before
indexing and handle that case explicitly (e.g., raise a clear exception or
return a suitable error), and adjust the exception handling to catch/index error
types accordingly so the error message accurately reflects a missing project
rather than a KeyError.

Comment on lines 20 to 30
4. Transkript des Hauptbenutzers.
Dies ist der wichtigste Teil des Kontexts. Dies enthält auch Ihre vorherigen Antworten. Sie erhalten Gesprächstranskripte (und optional Audiofragmente).
<main_user_transcript>
{{MAIN_USER_TRANSCRIPT}}
</main_user_transcript>

Umschließen Sie Ihre detaillierte Analyse mit <detailed_analysis> Tags und Ihre endgültige Antwort mit <response> Tags.
Beispiel-Ausgabestruktur:
Ihre endgültige Antwort muss innerhalb von 1-3 Sätzen sein

<detailed_analysis>
[Hier können Sie über verschiedene Möglichkeiten nachdenken, auf die neueste Nachricht und Absicht des Hauptbenutzers zu antworten. Sie können auch darüber nachdenken, wie Sie das Gespräch basierend auf Ihren vorherigen Antworten fortsetzen und Wiederholungen vermeiden können. Beenden Sie nicht immer mit einer Frage. Folgen Sie dem natürlichen Gesprächsfluss.]
</detailed_analysis>

<response>
[Ihre endgültige Antwort (1-3 Sätze)]
</response>
Verwenden Sie "wir/unser" Sprache für kollektives Eigentum
Passen Sie sich der Sprache des Transkripts an
Erkennen Sie Unsicherheit an, wenn vorhanden
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

DE behavior matches EN; one non‑blocking micro‑copy nit

Semantically this matches the EN contract (priority of main transcript, transcript/audio note, 1–3 sentences, collective “wir/unser”, language & uncertainty guidance), so behavior‑wise we’re good.

If you want slightly more idiomatic German, Line 28 can be tightened up; right now "wir/unser" Sprache reads a bit off:

- Verwenden Sie "wir/unser" Sprache für kollektives Eigentum
+ Verwenden Sie Formulierungen mit „wir/unser“ für kollektives Eigentum

Purely stylistic; no need to block on this if it’s not a priority.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
4. Transkript des Hauptbenutzers.
Dies ist der wichtigste Teil des Kontexts. Dies enthält auch Ihre vorherigen Antworten. Sie erhalten Gesprächstranskripte (und optional Audiofragmente).
<main_user_transcript>
{{MAIN_USER_TRANSCRIPT}}
</main_user_transcript>
Umschließen Sie Ihre detaillierte Analyse mit <detailed_analysis> Tags und Ihre endgültige Antwort mit <response> Tags.
Beispiel-Ausgabestruktur:
Ihre endgültige Antwort muss innerhalb von 1-3 Sätzen sein
<detailed_analysis>
[Hier können Sie über verschiedene Möglichkeiten nachdenken, auf die neueste Nachricht und Absicht des Hauptbenutzers zu antworten. Sie können auch darüber nachdenken, wie Sie das Gespräch basierend auf Ihren vorherigen Antworten fortsetzen und Wiederholungen vermeiden können. Beenden Sie nicht immer mit einer Frage. Folgen Sie dem natürlichen Gesprächsfluss.]
</detailed_analysis>
<response>
[Ihre endgültige Antwort (1-3 Sätze)]
</response>
\ No newline at end of file
Verwenden Sie "wir/unser" Sprache für kollektives Eigentum
Passen Sie sich der Sprache des Transkripts an
Erkennen Sie Unsicherheit an, wenn vorhanden
4. Transkript des Hauptbenutzers.
Dies ist der wichtigste Teil des Kontexts. Dies enthält auch Ihre vorherigen Antworten. Sie erhalten Gesprächstranskripte (und optional Audiofragmente).
<main_user_transcript>
{{MAIN_USER_TRANSCRIPT}}
</main_user_transcript>
Ihre endgültige Antwort muss innerhalb von 1-3 Sätzen sein
Verwenden Sie Formulierungen mit „wir/unser" für kollektives Eigentum
Passen Sie sich der Sprache des Transkripts an
Erkennen Sie Unsicherheit an, wenn vorhanden
🤖 Prompt for AI Agents
In echo/server/prompt_templates/get_reply_system.de.jinja around lines 20 to 30,
the microcopy on line 28 reads awkwardly as `"wir/unser" Sprache`; update this
single phrase to a more idiomatic German formulation (e.g., instruct to use the
Wir-/unser-Form or say "verwenden Sie die Wir-/unser-Form") so the guidance
reads naturally while keeping the existing meaning and other instructions
unchanged.

@spashii spashii merged commit f5c0727 into main Nov 26, 2025
11 checks passed
@spashii spashii deleted the fix-permissions-ECHO-572 branch November 26, 2025 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Feature improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants