docs(chat): record shared access and control behavior
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
## Backend Patterns
|
||||
- **Views**: DRF `ModelViewSet` subclasses; `get_queryset()` filters by `user=self.request.user`
|
||||
- **Shared-access queries**: Use `Q(user=user) | Q(shared_with=user)).distinct()` for collection lookups that should include shared members (e.g. chat agent tools). Always `.distinct()` to avoid `MultipleObjectsReturned` when owner is also in `shared_with`.
|
||||
- **Money**: `djmoney` MoneyField
|
||||
- **Geo**: PostGIS via `django-geojson`
|
||||
- **Chat providers**: Dynamic catalog from `GET /api/chat/providers/`; configured in `CHAT_PROVIDER_CONFIG`
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
- On unshare/leave, departing user's locations are removed from collection (not deleted)
|
||||
- `duplicate` action creates a private copy with no `shared_with` transfer
|
||||
|
||||
### Chat Agent Tool Access
|
||||
- `get_trip_details` and `add_to_itinerary` tools authorize using `Q(user=user) | Q(shared_with=user)` — shared members can use AI chat tools on shared collections.
|
||||
- `list_trips` remains owner-only (shared collections not listed).
|
||||
- `add_to_itinerary` assigns `Location.user = shared_user` (shared users own their contributed locations), consistent with REST API behavior.
|
||||
- See [patterns/chat-and-llm.md](../patterns/chat-and-llm.md#shared-trip-tool-access).
|
||||
|
||||
## Itinerary Architecture
|
||||
|
||||
### Primary Component
|
||||
|
||||
@@ -10,10 +10,11 @@ Frontend never calls Django directly. All API calls go through `src/routes/api/[
|
||||
- Chat conversations stream via SSE through `/api/chat/conversations/`.
|
||||
- `ChatViewSet.send_message()` accepts optional context fields (`collection_id`, `collection_name`, `start_date`, `end_date`, `destination`) and appends a `## Trip Context` section to the system prompt when provided. When a `collection_id` is present, also injects `Itinerary stops:` from `collection.locations` (up to 8 unique stops) and the collection UUID with explicit `get_trip_details`/`add_to_itinerary` grounding. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#trip-context-uuid-grounding) and [patterns/chat-and-llm.md](patterns/chat-and-llm.md#multi-stop-context-derivation).
|
||||
- Chat composer supports per-provider model override (persisted in browser `localStorage` key `voyage_chat_model_prefs`). DB-saved default provider/model (`UserAISettings`) is authoritative on initialization; localStorage is write-only sync target. Backend `send_message` accepts optional `model` param; falls back to DB defaults → instance defaults → `"openai"`.
|
||||
- Chat agent tools (`get_trip_details`, `add_to_itinerary`) authorize using `Q(user=user) | Q(shared_with=user)` — both owners and shared members can use them. `list_trips` remains owner-only. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#shared-trip-tool-access).
|
||||
- Invalid required-argument tool calls are detected and short-circuited: stream terminates with `tool_validation_error` SSE event + `[DONE]` and invalid tool results are not replayed into conversation history. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#tool-call-error-handling-chat-loop-hardening).
|
||||
- LiteLLM errors mapped to sanitized user-safe messages via `_safe_error_payload()` (never exposes raw exception text). See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#sanitized-llm-error-mapping).
|
||||
- Tool outputs display as concise summaries (not raw JSON) via `getToolSummary()`. Persisted `role=tool` messages are hidden from display; on conversation reload, `rebuildConversationMessages()` reconstructs `tool_results` on assistant messages. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#tool-output-rendering).
|
||||
- Embedded chat uses compact header (provider/model selectors in settings dropdown), bounded height, sidebar-closed-by-default, and visible streaming indicator. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#embedded-chat-ux).
|
||||
- Embedded chat uses compact header (provider/model selectors in settings dropdown with outside-click/Escape close), bounded height, sidebar-closed-by-default, visible streaming indicator, and i18n aria-labels. See [patterns/chat-and-llm.md](patterns/chat-and-llm.md#embedded-chat-ux).
|
||||
- Frontend type: `ChatProviderCatalogEntry` in `src/lib/types.ts`.
|
||||
- Reference: [Plan: AI travel agent](../plans/ai-travel-agent-collections-integration.md), [Plan: AI travel agent redesign — WS4](../plans/ai-travel-agent-redesign.md#ws4-collection-level-chat-improvements)
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@
|
||||
- UUID injection only occurs when collection lookup succeeds AND user is owner or `shared_with` member (authorization gate).
|
||||
- System prompt includes two-phase confirmation guidance: confirm only before the **first** `add_to_itinerary` action; after explicit user approval phrases ("yes", "go ahead", "add them"), proceed directly without re-confirming.
|
||||
- `get_trip_details` DoesNotExist returns `"collection_id is required and must reference a trip you can access"` (does NOT match short-circuit regex due to `fullmatch` — correct, this is an invalid-value error, not missing-param).
|
||||
- Known pre-existing: `get_trip_details` filters `user=user` only — shared-collection members get UUID context but tool returns DoesNotExist. Low severity.
|
||||
|
||||
### Shared-Trip Tool Access
|
||||
- `get_trip_details` and `add_to_itinerary` authorize collections using `Q(user=user) | Q(shared_with=user)` with `.distinct()` — both owners and shared members can access.
|
||||
- `list_trips` remains owner-only by design.
|
||||
- `.distinct()` prevents `MultipleObjectsReturned` when the owner is also present in `shared_with`.
|
||||
- Non-members receive `DoesNotExist` errors through existing error paths.
|
||||
|
||||
## Tool Output Rendering
|
||||
- Frontend `AITravelChat.svelte` hides raw `role=tool` messages via `visibleMessages` filter (`messages.filter(msg => msg.role !== 'tool')`).
|
||||
@@ -62,7 +67,8 @@
|
||||
- Sidebar defaults to closed in embedded mode (`let sidebarOpen = !embedded;`); `lg:flex` ensures always-visible on desktop.
|
||||
- Quick-action chips use `btn-xs` + `overflow-x-auto` for compact embedded fit.
|
||||
- Streaming indicator visible inside last assistant bubble throughout entire generation (conditioned on `isStreaming && msg.id === lastVisibleMessageId`).
|
||||
- Known low-priority: `aria-label` values on sidebar toggle and settings button are hardcoded English (should use `$t()`). `<details>` dropdown does not auto-close on outside click.
|
||||
- Aria-label values on sidebar toggle and settings button use i18n keys (`chat_a11y.show_conversations_aria`, `chat_a11y.hide_conversations_aria`, `chat_a11y.ai_settings_aria`); key parity across all 20 locale files.
|
||||
- Settings dropdown closes on outside click (`pointerdown`/`mousedown`/`touchstart` listeners) and `Escape` keypress, with mount-time listener cleanup.
|
||||
|
||||
## OpenCode Zen Provider
|
||||
- Provider ID: `opencode_zen`
|
||||
|
||||
Reference in New Issue
Block a user