@wpic/ui 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/components/CollapseToggle.vue.d.ts +10 -0
  2. package/dist/components/ConfirmModal.vue.d.ts +16 -0
  3. package/dist/components/Modal.vue.d.ts +27 -0
  4. package/dist/components/SearchSelect.vue.d.ts +19 -0
  5. package/dist/components/ToastContainer.vue.d.ts +2 -0
  6. package/dist/components/TreeSelect.vue.d.ts +32 -0
  7. package/dist/components/WActionMenu.vue.d.ts +12 -0
  8. package/dist/components/WActionMenuItem.vue.d.ts +20 -0
  9. package/dist/components/WActionPanel.vue.d.ts +22 -0
  10. package/dist/components/WAlert.vue.d.ts +24 -0
  11. package/dist/components/WAvatar.vue.d.ts +7 -0
  12. package/dist/components/WBadge.vue.d.ts +21 -0
  13. package/dist/components/WBanner.vue.d.ts +17 -0
  14. package/dist/components/WBatchActionBar.vue.d.ts +22 -0
  15. package/dist/components/WButton.vue.d.ts +25 -0
  16. package/dist/components/WButtonGroup.vue.d.ts +12 -0
  17. package/dist/components/WCard.vue.d.ts +44 -0
  18. package/dist/components/WChatPanel.vue.d.ts +278 -0
  19. package/dist/components/WCheckbox.vue.d.ts +22 -0
  20. package/dist/components/WDatePicker.vue.d.ts +31 -0
  21. package/dist/components/WDateRangePicker.vue.d.ts +26 -0
  22. package/dist/components/WEmptyState.vue.d.ts +27 -0
  23. package/dist/components/WEmptyValue.vue.d.ts +17 -0
  24. package/dist/components/WFileList.vue.d.ts +36 -0
  25. package/dist/components/WFilter.vue.d.ts +21 -0
  26. package/dist/components/WFilterBar.vue.d.ts +69 -0
  27. package/dist/components/WFormActions.vue.d.ts +14 -0
  28. package/dist/components/WFormLabel.vue.d.ts +15 -0
  29. package/dist/components/WImageTile.vue.d.ts +11 -0
  30. package/dist/components/WInput.vue.d.ts +35 -0
  31. package/dist/components/WLoader.vue.d.ts +12 -0
  32. package/dist/components/WModal.vue.d.ts +28 -0
  33. package/dist/components/WMonthPicker.vue.d.ts +23 -0
  34. package/dist/components/WOptionPopover.vue.d.ts +19 -0
  35. package/dist/components/WPagination.vue.d.ts +16 -0
  36. package/dist/components/WPillGroup.vue.d.ts +35 -0
  37. package/dist/components/WPopover.vue.d.ts +44 -0
  38. package/dist/components/WProgress.vue.d.ts +10 -0
  39. package/dist/components/WSearchSelect.vue.d.ts +25 -0
  40. package/dist/components/WSegmentedControl.vue.d.ts +22 -0
  41. package/dist/components/WSelect.vue.d.ts +67 -0
  42. package/dist/components/WSelectOption.vue.d.ts +22 -0
  43. package/dist/components/WSidePanel.vue.d.ts +29 -0
  44. package/dist/components/WSkeleton.vue.d.ts +7 -0
  45. package/dist/components/WSlidePanel.vue.d.ts +25 -0
  46. package/dist/components/WSlider.vue.d.ts +22 -0
  47. package/dist/components/WSpinner.vue.d.ts +15 -0
  48. package/dist/components/WSpreadsheet.vue.d.ts +99 -0
  49. package/dist/components/WStatCard.vue.d.ts +21 -0
  50. package/dist/components/WTable.vue.d.ts +87 -0
  51. package/dist/components/WTabs.vue.d.ts +28 -0
  52. package/dist/components/WTag.vue.d.ts +27 -0
  53. package/dist/components/WTagInput.vue.d.ts +71 -0
  54. package/dist/components/WTailwindColorPicker.vue.d.ts +21 -0
  55. package/dist/components/WTextarea.vue.d.ts +15 -0
  56. package/dist/components/WToggle.vue.d.ts +22 -0
  57. package/dist/components/WTooltip.vue.d.ts +19 -0
  58. package/dist/components/WTwoColumnFormSection.vue.d.ts +19 -0
  59. package/dist/components/WWorkflowNav.vue.d.ts +20 -0
  60. package/dist/components/form/FormDate.vue.d.ts +23 -0
  61. package/dist/components/form/FormInput.vue.d.ts +15 -0
  62. package/dist/components/form/FormLabel.vue.d.ts +12 -0
  63. package/dist/components/form/FormSelect.vue.d.ts +18 -0
  64. package/dist/components/form/FormTextarea.vue.d.ts +14 -0
  65. package/dist/components/icons/MarketplaceIcon.vue.d.ts +9 -0
  66. package/dist/components/icons/ProviderLogo.vue.d.ts +11 -0
  67. package/dist/components/table/Table.vue.d.ts +12 -0
  68. package/dist/components/table/TableBody.vue.d.ts +12 -0
  69. package/dist/components/table/TableCell.vue.d.ts +17 -0
  70. package/dist/components/table/TableHead.vue.d.ts +12 -0
  71. package/dist/components/table/TableRow.vue.d.ts +15 -0
  72. package/dist/composables/useRecentItems.d.ts +9 -0
  73. package/dist/index.d.ts +82 -0
  74. package/dist/stores/toast.d.ts +148 -0
  75. package/dist/wpic-ui.cjs +9318 -0
  76. package/dist/wpic-ui.css +284 -0
  77. package/dist/wpic-ui.js +17570 -0
  78. package/package.json +51 -0
  79. package/src/components/chat/DB_SCHEMA.md +440 -0
  80. package/src/components/chat/LLM_STREAMING.md +390 -0
  81. package/src/components/chat/UI_INTEGRATION.md +273 -0
  82. package/src/style.css +282 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@wpic/ui",
3
+ "version": "0.3.5",
4
+ "type": "module",
5
+ "description": "WPIC shared Vue 3 component library",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "src/style.css",
12
+ "src/components/chat/*.md"
13
+ ],
14
+ "main": "./dist/wpic-ui.cjs",
15
+ "module": "./dist/wpic-ui.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/wpic-ui.js",
21
+ "require": "./dist/wpic-ui.cjs"
22
+ },
23
+ "./style.css": "./dist/wpic-ui.css",
24
+ "./dist/wpic-ui.css": "./dist/wpic-ui.css"
25
+ },
26
+ "scripts": {
27
+ "build": "vue-tsc --noEmit && vite build && node scripts/postbuild.js",
28
+ "dev": "vite build --watch",
29
+ "test:ui": "vite --config test-ui/vite.config.ts",
30
+ "postbuild": "node scripts/postbuild.js"
31
+ },
32
+ "peerDependencies": {
33
+ "@floating-ui/vue": ">=1",
34
+ "lucide-vue-next": ">=0.4",
35
+ "pinia": ">=2",
36
+ "vue": ">=3.4"
37
+ },
38
+ "devDependencies": {
39
+ "@floating-ui/vue": "^1.1.11",
40
+ "@tailwindcss/vite": "^4.2.1",
41
+ "@vitejs/plugin-vue": "^6.0.0",
42
+ "lucide-vue-next": "^0.577.0",
43
+ "pinia": "^3.0.4",
44
+ "tailwindcss": "^4.2.1",
45
+ "typescript": "^5.0.0",
46
+ "vite": "^7.3.1",
47
+ "vite-plugin-dts": "^5.0.0",
48
+ "vue": "^3.5.0",
49
+ "vue-tsc": "^2.0.0"
50
+ }
51
+ }
@@ -0,0 +1,440 @@
1
+ # Chat database schema guide
2
+
3
+ Use this when an app needs DB-backed chat history, sessions, projects, memory, or usage. `WChatPanel` works with local-only state, but production assistants usually need persistent tables.
4
+
5
+ This guide is based on the working WPIC Perform migrations:
6
+
7
+ - `migrations/20260507000000_agent.sql`
8
+ - `migrations/20260507000001_chat_projects.sql`
9
+ - `routes/chat.py`
10
+
11
+ ## When to use DB persistence
12
+
13
+ Use DB-backed chat when any of these are true:
14
+
15
+ - conversations must survive logout, browser changes, or device changes
16
+ - users need conversation history and session switching
17
+ - conversations can be grouped by project/client/workspace
18
+ - admins need audit/debug visibility
19
+ - token usage/cost must be tracked
20
+ - the assistant needs durable user memory
21
+ - the app uses tool calls that mutate business objects
22
+
23
+ Use localStorage only for a first MVP where history is personal, disposable, and not needed across devices.
24
+
25
+ ## Core concepts
26
+
27
+ Recommended model:
28
+
29
+ - `wpic_chat_projects`: optional folders or workspaces inside a user/org
30
+ - `wpic_chat_sessions`: one conversation thread
31
+ - `wpic_chat_messages`: ordered messages in a session
32
+ - `wpic_chat_provider_runs`: one provider/API call for observability and debugging
33
+ - `wpic_chat_memory`: durable user facts/preferences
34
+ - `wpic_chat_usage`: daily token/message/cost counters
35
+ - `wpic_chat_attachments`: uploaded files, assets, or parsed text attached to messages
36
+ - `wpic_chat_tool_events`: structured tool calls/results for audit and replay
37
+
38
+ Names can vary by product. The important part is the relationship:
39
+
40
+ ```text
41
+ user/org
42
+ -> wpic_chat_projects
43
+ -> wpic_chat_sessions
44
+ -> wpic_chat_messages
45
+ ```
46
+
47
+ ## Perform schema
48
+
49
+ Perform uses `agent_conversations`, `agent_memory`, `agent_usage`, `chat_projects`, and `chat_sessions`.
50
+
51
+ ```sql
52
+ CREATE TABLE IF NOT EXISTS agent_conversations (
53
+ id BIGSERIAL PRIMARY KEY,
54
+ user_id UUID NOT NULL,
55
+ role VARCHAR(20) NOT NULL,
56
+ content TEXT NOT NULL,
57
+ tokens INT DEFAULT 0,
58
+ created_at TIMESTAMPTZ DEFAULT NOW()
59
+ );
60
+ CREATE INDEX IF NOT EXISTS agent_conv_user_idx
61
+ ON agent_conversations (user_id, created_at DESC);
62
+
63
+ CREATE TABLE IF NOT EXISTS agent_memory (
64
+ user_id UUID NOT NULL,
65
+ key VARCHAR(100) NOT NULL,
66
+ value TEXT NOT NULL,
67
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
68
+ PRIMARY KEY (user_id, key)
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS agent_usage (
72
+ user_id UUID NOT NULL,
73
+ date DATE NOT NULL DEFAULT CURRENT_DATE,
74
+ input_tokens INT DEFAULT 0,
75
+ output_tokens INT DEFAULT 0,
76
+ messages INT DEFAULT 0,
77
+ PRIMARY KEY (user_id, date)
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS chat_projects (
81
+ id BIGSERIAL PRIMARY KEY,
82
+ user_id UUID NOT NULL,
83
+ name VARCHAR(200) NOT NULL,
84
+ color VARCHAR(20) DEFAULT '#5E6AD2',
85
+ created_at TIMESTAMPTZ DEFAULT NOW(),
86
+ updated_at TIMESTAMPTZ DEFAULT NOW()
87
+ );
88
+ CREATE INDEX IF NOT EXISTS chat_projects_user_idx
89
+ ON chat_projects (user_id, updated_at DESC);
90
+
91
+ CREATE TABLE IF NOT EXISTS chat_sessions (
92
+ id BIGSERIAL PRIMARY KEY,
93
+ user_id UUID NOT NULL,
94
+ project_id BIGINT REFERENCES chat_projects(id) ON DELETE SET NULL,
95
+ title VARCHAR(200) NOT NULL DEFAULT 'New conversation',
96
+ created_at TIMESTAMPTZ DEFAULT NOW(),
97
+ updated_at TIMESTAMPTZ DEFAULT NOW()
98
+ );
99
+ CREATE INDEX IF NOT EXISTS chat_sessions_user_idx
100
+ ON chat_sessions (user_id, updated_at DESC);
101
+ CREATE INDEX IF NOT EXISTS chat_sessions_project_idx
102
+ ON chat_sessions (project_id);
103
+
104
+ ALTER TABLE agent_conversations
105
+ ADD COLUMN IF NOT EXISTS session_id BIGINT REFERENCES chat_sessions(id) ON DELETE CASCADE;
106
+ CREATE INDEX IF NOT EXISTS agent_conv_session_idx
107
+ ON agent_conversations (session_id, created_at ASC);
108
+ ```
109
+
110
+ Perform saves user and assistant rows only after the final assistant response is known, then updates `chat_sessions.updated_at` and upserts daily `agent_usage`.
111
+
112
+ ## Recommended `wpic_chat_*` schema
113
+
114
+ For new apps, use the `wpic_chat_*` prefix to avoid collisions with existing app tables. Include `app` so Studio, Perform, admin, support, or future apps can share the same table family without logical collisions.
115
+
116
+ ```sql
117
+ CREATE TABLE wpic_chat_projects (
118
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
119
+ app TEXT NOT NULL DEFAULT 'studio',
120
+ organization_id UUID NOT NULL,
121
+ created_by UUID NOT NULL,
122
+ name TEXT NOT NULL,
123
+ color TEXT NOT NULL DEFAULT '#5E6AD2',
124
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
125
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
126
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
127
+ deleted_at TIMESTAMPTZ
128
+ );
129
+ CREATE INDEX idx_wpic_chat_projects_owner
130
+ ON wpic_chat_projects (app, organization_id, created_by, updated_at DESC);
131
+
132
+ CREATE TABLE wpic_chat_sessions (
133
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
134
+ app TEXT NOT NULL DEFAULT 'studio',
135
+ organization_id UUID NOT NULL,
136
+ created_by UUID NOT NULL,
137
+ project_id UUID REFERENCES wpic_chat_projects(id) ON DELETE SET NULL,
138
+ title TEXT NOT NULL DEFAULT 'New conversation',
139
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
140
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
141
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
142
+ deleted_at TIMESTAMPTZ
143
+ );
144
+ CREATE INDEX idx_wpic_chat_sessions_owner
145
+ ON wpic_chat_sessions (app, organization_id, created_by, updated_at DESC);
146
+ CREATE INDEX idx_wpic_chat_sessions_project
147
+ ON wpic_chat_sessions (project_id, updated_at DESC);
148
+
149
+ CREATE TABLE wpic_chat_provider_runs (
150
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
151
+ app TEXT NOT NULL DEFAULT 'studio',
152
+ organization_id UUID NOT NULL,
153
+ user_id UUID NOT NULL,
154
+ session_id UUID REFERENCES wpic_chat_sessions(id) ON DELETE SET NULL,
155
+ provider TEXT NOT NULL,
156
+ model TEXT NOT NULL,
157
+ status TEXT NOT NULL DEFAULT 'running'
158
+ CHECK (status IN ('running','succeeded','failed','cancelled')),
159
+ request_id TEXT,
160
+ input_tokens INT NOT NULL DEFAULT 0,
161
+ output_tokens INT NOT NULL DEFAULT 0,
162
+ cost_usd_micros BIGINT NOT NULL DEFAULT 0,
163
+ latency_ms INT,
164
+ error TEXT,
165
+ request JSONB NOT NULL DEFAULT '{}'::jsonb,
166
+ response JSONB NOT NULL DEFAULT '{}'::jsonb,
167
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
168
+ started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
169
+ completed_at TIMESTAMPTZ
170
+ );
171
+ CREATE INDEX idx_wpic_chat_provider_runs_owner
172
+ ON wpic_chat_provider_runs (app, organization_id, user_id, started_at DESC);
173
+
174
+ CREATE TABLE wpic_chat_messages (
175
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
176
+ app TEXT NOT NULL DEFAULT 'studio',
177
+ organization_id UUID NOT NULL,
178
+ session_id UUID NOT NULL REFERENCES wpic_chat_sessions(id) ON DELETE CASCADE,
179
+ created_by UUID NOT NULL,
180
+ provider_run_id UUID REFERENCES wpic_chat_provider_runs(id) ON DELETE SET NULL,
181
+ role TEXT NOT NULL CHECK (role IN ('system', 'user', 'assistant', 'tool')),
182
+ content TEXT NOT NULL,
183
+ provider TEXT,
184
+ model TEXT,
185
+ input_tokens INT NOT NULL DEFAULT 0,
186
+ output_tokens INT NOT NULL DEFAULT 0,
187
+ cost_usd_micros BIGINT NOT NULL DEFAULT 0,
188
+ tool_name TEXT,
189
+ tool_call_id TEXT,
190
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
191
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
192
+ deleted_at TIMESTAMPTZ
193
+ );
194
+ CREATE INDEX idx_wpic_chat_messages_session
195
+ ON wpic_chat_messages (session_id, created_at ASC);
196
+ CREATE INDEX idx_wpic_chat_messages_owner
197
+ ON wpic_chat_messages (app, organization_id, created_by, created_at DESC);
198
+
199
+ CREATE TABLE wpic_chat_usage (
200
+ app TEXT NOT NULL DEFAULT 'studio',
201
+ organization_id UUID NOT NULL,
202
+ user_id UUID NOT NULL,
203
+ date DATE NOT NULL DEFAULT CURRENT_DATE,
204
+ input_tokens INT NOT NULL DEFAULT 0,
205
+ output_tokens INT NOT NULL DEFAULT 0,
206
+ messages INT NOT NULL DEFAULT 0,
207
+ cost_usd_micros BIGINT NOT NULL DEFAULT 0,
208
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
209
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
210
+ PRIMARY KEY (app, organization_id, user_id, date)
211
+ );
212
+
213
+ CREATE TABLE wpic_chat_memory (
214
+ app TEXT NOT NULL DEFAULT 'studio',
215
+ organization_id UUID NOT NULL,
216
+ user_id UUID NOT NULL,
217
+ key TEXT NOT NULL,
218
+ value TEXT NOT NULL,
219
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
220
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
221
+ PRIMARY KEY (app, organization_id, user_id, key)
222
+ );
223
+ ```
224
+
225
+ If the product has no organizations, remove `organization_id` or make it nullable consistently.
226
+
227
+ ## Attachment table
228
+
229
+ Use this when uploads, assets, or media must be auditable or reused:
230
+
231
+ ```sql
232
+ CREATE TABLE wpic_chat_attachments (
233
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
234
+ app TEXT NOT NULL DEFAULT 'studio',
235
+ organization_id UUID NOT NULL,
236
+ session_id UUID REFERENCES wpic_chat_sessions(id) ON DELETE CASCADE,
237
+ message_id UUID REFERENCES wpic_chat_messages(id) ON DELETE CASCADE,
238
+ asset_id UUID,
239
+ media_item_id UUID,
240
+ created_by UUID NOT NULL,
241
+ name TEXT NOT NULL,
242
+ mime_type TEXT,
243
+ bytes BIGINT,
244
+ storage_key TEXT,
245
+ extracted_text TEXT,
246
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
247
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
248
+ deleted_at TIMESTAMPTZ
249
+ );
250
+ CREATE INDEX idx_wpic_chat_attachments_session
251
+ ON wpic_chat_attachments (session_id, created_at DESC);
252
+ ```
253
+
254
+ For small text attachments, storing extracted text in message metadata is acceptable. For files, store the file in object storage and keep `storage_key`.
255
+
256
+ ## Tool-event table
257
+
258
+ Use this when tool calls mutate app data and you need audit/debug trails:
259
+
260
+ ```sql
261
+ CREATE TABLE wpic_chat_tool_events (
262
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
263
+ app TEXT NOT NULL DEFAULT 'studio',
264
+ organization_id UUID NOT NULL,
265
+ session_id UUID NOT NULL REFERENCES wpic_chat_sessions(id) ON DELETE CASCADE,
266
+ message_id UUID REFERENCES wpic_chat_messages(id) ON DELETE SET NULL,
267
+ provider_run_id UUID REFERENCES wpic_chat_provider_runs(id) ON DELETE SET NULL,
268
+ created_by UUID NOT NULL,
269
+ tool_name TEXT NOT NULL,
270
+ tool_call_id TEXT,
271
+ status TEXT NOT NULL DEFAULT 'succeeded'
272
+ CHECK (status IN ('succeeded','failed','cancelled','skipped')),
273
+ arguments JSONB NOT NULL DEFAULT '{}'::jsonb,
274
+ result JSONB NOT NULL DEFAULT '{}'::jsonb,
275
+ error TEXT,
276
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
277
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
278
+ );
279
+ CREATE INDEX idx_wpic_chat_tool_events_session
280
+ ON wpic_chat_tool_events (session_id, created_at ASC);
281
+ ```
282
+
283
+ ## Required API endpoints
284
+
285
+ Minimum DB-backed session API:
286
+
287
+ ```text
288
+ GET /api/chat/projects
289
+ POST /api/chat/projects
290
+ PATCH /api/chat/projects/:id
291
+ DELETE /api/chat/projects/:id
292
+
293
+ GET /api/chat/sessions?project_id=...
294
+ POST /api/chat/sessions
295
+ PATCH /api/chat/sessions/:id
296
+ DELETE /api/chat/sessions/:id
297
+ GET /api/chat/sessions/:id/messages
298
+
299
+ POST /api/chat
300
+ ```
301
+
302
+ For admin/debug:
303
+
304
+ ```text
305
+ GET /api/admin/chat/usage
306
+ DELETE /api/admin/chat/conversations/:user_id
307
+ ```
308
+
309
+ ## Save flow
310
+
311
+ Use this flow for a streamed response:
312
+
313
+ 1. Authenticate user and resolve org/workspace.
314
+ 2. Validate session belongs to user/org.
315
+ 3. Build messages:
316
+ - system prompt
317
+ - DB history for session, latest N messages
318
+ - current user message
319
+ 4. Stream provider response to client.
320
+ 5. Execute tool calls if needed.
321
+ 6. Collect final assistant text and usage.
322
+ 7. If not cancelled and assistant text is non-empty, insert user message and assistant message.
323
+ 8. Update `wpic_chat_sessions.updated_at`.
324
+ 9. Upsert `wpic_chat_usage`.
325
+ 10. Return `[DONE]`.
326
+
327
+ Do not write the final assistant row before the stream completes unless the product explicitly wants partial messages.
328
+
329
+ ## History loading
330
+
331
+ Perform uses:
332
+
333
+ ```sql
334
+ SELECT role, content
335
+ FROM agent_conversations
336
+ WHERE user_id = $1
337
+ AND session_id = $2
338
+ AND role IN ('user', 'assistant')
339
+ AND content IS NOT NULL
340
+ AND content != ''
341
+ ORDER BY created_at DESC
342
+ LIMIT $3;
343
+ ```
344
+
345
+ Then it reverses rows before sending to the model. This keeps provider context small and ordered.
346
+
347
+ Recommended defaults:
348
+
349
+ - history limit: 20 messages
350
+ - memory max chars: 2000
351
+ - frontend local fallback: last 10-12 messages
352
+ - hard max request messages: 20
353
+ - hard max content length per message: 12000 chars
354
+
355
+ ## Usage upsert
356
+
357
+ Pattern:
358
+
359
+ ```sql
360
+ INSERT INTO wpic_chat_usage (app, organization_id, user_id, date, input_tokens, output_tokens, messages, cost_usd_micros)
361
+ VALUES ($1, $2, $3, CURRENT_DATE, $4, $5, 1, $6)
362
+ ON CONFLICT (app, organization_id, user_id, date) DO UPDATE SET
363
+ input_tokens = wpic_chat_usage.input_tokens + EXCLUDED.input_tokens,
364
+ output_tokens = wpic_chat_usage.output_tokens + EXCLUDED.output_tokens,
365
+ messages = wpic_chat_usage.messages + 1,
366
+ cost_usd_micros = wpic_chat_usage.cost_usd_micros + EXCLUDED.cost_usd_micros,
367
+ updated_at = NOW();
368
+ ```
369
+
370
+ If the app is not org-scoped, make the primary key `(user_id, date)`.
371
+
372
+ ## Auth and tenancy rules
373
+
374
+ - Every project/session/message query must be scoped by user and organization/workspace.
375
+ - Never trust `session_id` alone.
376
+ - Validate `project_id` belongs to the same user/org before attaching a session.
377
+ - If admins can inspect conversations, add explicit admin endpoints and do not reuse user endpoints.
378
+ - Decide whether deleted sessions are soft-deleted or hard-deleted. Perform hard-deletes sessions and cascades messages.
379
+ - For shared org workspaces, decide whether sessions are private to the user or shared with team members.
380
+
381
+ ## Title generation
382
+
383
+ Perform uses the first user message sliced to 60 chars. That is good enough for MVP.
384
+
385
+ Options:
386
+
387
+ - first user message, truncated
388
+ - LLM-generated title after first response
389
+ - editable user title
390
+
391
+ If using LLM-generated titles, do it async and never block the main chat stream.
392
+
393
+ ## Memory
394
+
395
+ `chat_memory` / `agent_memory` should store durable facts, not transient conversation summaries. Good entries:
396
+
397
+ - user preferences
398
+ - stable client notes
399
+ - recurring workflow preferences
400
+
401
+ Bad entries:
402
+
403
+ - one-off prompt drafts
404
+ - temporary instructions
405
+ - secrets
406
+ - raw uploaded file contents
407
+
408
+ Memory should have explicit write rules. Do not let arbitrary assistant output become memory without a tool or reviewed pathway.
409
+
410
+ ## Migration checklist
411
+
412
+ - Add tables and indexes.
413
+ - Backfill `session_id` if migrating existing flat conversations.
414
+ - Add session/project API endpoints.
415
+ - Add auth scoping tests for every endpoint.
416
+ - Add stream save tests:
417
+ - normal completion inserts two messages
418
+ - cancelled stream inserts nothing or marks partial according to product decision
419
+ - provider error does not insert assistant success
420
+ - usage upsert increments
421
+ - deleting a session removes or hides messages
422
+ - Add admin usage visibility if needed.
423
+
424
+ ## Drizzle implementation notes
425
+
426
+ For Drizzle apps, model these as normal `pgTable`s. Prefer UUID primary keys for new Studio-like apps. Use `jsonb` for metadata, not ad-hoc text columns for every future extension.
427
+
428
+ Store provider-specific details in message metadata when they are not first-class query dimensions:
429
+
430
+ ```json
431
+ {
432
+ "provider": "deepseek",
433
+ "model": "deepseek-v4-flash",
434
+ "finish_reason": "stop",
435
+ "tools_used": ["navigate"],
436
+ "request_id": "..."
437
+ }
438
+ ```
439
+
440
+ Use first-class columns for fields that will be filtered, joined, or aggregated frequently: `session_id`, `user_id`, `organization_id`, `created_at`, `role`, `input_tokens`, `output_tokens`.