@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.
- package/dist/components/CollapseToggle.vue.d.ts +10 -0
- package/dist/components/ConfirmModal.vue.d.ts +16 -0
- package/dist/components/Modal.vue.d.ts +27 -0
- package/dist/components/SearchSelect.vue.d.ts +19 -0
- package/dist/components/ToastContainer.vue.d.ts +2 -0
- package/dist/components/TreeSelect.vue.d.ts +32 -0
- package/dist/components/WActionMenu.vue.d.ts +12 -0
- package/dist/components/WActionMenuItem.vue.d.ts +20 -0
- package/dist/components/WActionPanel.vue.d.ts +22 -0
- package/dist/components/WAlert.vue.d.ts +24 -0
- package/dist/components/WAvatar.vue.d.ts +7 -0
- package/dist/components/WBadge.vue.d.ts +21 -0
- package/dist/components/WBanner.vue.d.ts +17 -0
- package/dist/components/WBatchActionBar.vue.d.ts +22 -0
- package/dist/components/WButton.vue.d.ts +25 -0
- package/dist/components/WButtonGroup.vue.d.ts +12 -0
- package/dist/components/WCard.vue.d.ts +44 -0
- package/dist/components/WChatPanel.vue.d.ts +278 -0
- package/dist/components/WCheckbox.vue.d.ts +22 -0
- package/dist/components/WDatePicker.vue.d.ts +31 -0
- package/dist/components/WDateRangePicker.vue.d.ts +26 -0
- package/dist/components/WEmptyState.vue.d.ts +27 -0
- package/dist/components/WEmptyValue.vue.d.ts +17 -0
- package/dist/components/WFileList.vue.d.ts +36 -0
- package/dist/components/WFilter.vue.d.ts +21 -0
- package/dist/components/WFilterBar.vue.d.ts +69 -0
- package/dist/components/WFormActions.vue.d.ts +14 -0
- package/dist/components/WFormLabel.vue.d.ts +15 -0
- package/dist/components/WImageTile.vue.d.ts +11 -0
- package/dist/components/WInput.vue.d.ts +35 -0
- package/dist/components/WLoader.vue.d.ts +12 -0
- package/dist/components/WModal.vue.d.ts +28 -0
- package/dist/components/WMonthPicker.vue.d.ts +23 -0
- package/dist/components/WOptionPopover.vue.d.ts +19 -0
- package/dist/components/WPagination.vue.d.ts +16 -0
- package/dist/components/WPillGroup.vue.d.ts +35 -0
- package/dist/components/WPopover.vue.d.ts +44 -0
- package/dist/components/WProgress.vue.d.ts +10 -0
- package/dist/components/WSearchSelect.vue.d.ts +25 -0
- package/dist/components/WSegmentedControl.vue.d.ts +22 -0
- package/dist/components/WSelect.vue.d.ts +67 -0
- package/dist/components/WSelectOption.vue.d.ts +22 -0
- package/dist/components/WSidePanel.vue.d.ts +29 -0
- package/dist/components/WSkeleton.vue.d.ts +7 -0
- package/dist/components/WSlidePanel.vue.d.ts +25 -0
- package/dist/components/WSlider.vue.d.ts +22 -0
- package/dist/components/WSpinner.vue.d.ts +15 -0
- package/dist/components/WSpreadsheet.vue.d.ts +99 -0
- package/dist/components/WStatCard.vue.d.ts +21 -0
- package/dist/components/WTable.vue.d.ts +87 -0
- package/dist/components/WTabs.vue.d.ts +28 -0
- package/dist/components/WTag.vue.d.ts +27 -0
- package/dist/components/WTagInput.vue.d.ts +71 -0
- package/dist/components/WTailwindColorPicker.vue.d.ts +21 -0
- package/dist/components/WTextarea.vue.d.ts +15 -0
- package/dist/components/WToggle.vue.d.ts +22 -0
- package/dist/components/WTooltip.vue.d.ts +19 -0
- package/dist/components/WTwoColumnFormSection.vue.d.ts +19 -0
- package/dist/components/WWorkflowNav.vue.d.ts +20 -0
- package/dist/components/form/FormDate.vue.d.ts +23 -0
- package/dist/components/form/FormInput.vue.d.ts +15 -0
- package/dist/components/form/FormLabel.vue.d.ts +12 -0
- package/dist/components/form/FormSelect.vue.d.ts +18 -0
- package/dist/components/form/FormTextarea.vue.d.ts +14 -0
- package/dist/components/icons/MarketplaceIcon.vue.d.ts +9 -0
- package/dist/components/icons/ProviderLogo.vue.d.ts +11 -0
- package/dist/components/table/Table.vue.d.ts +12 -0
- package/dist/components/table/TableBody.vue.d.ts +12 -0
- package/dist/components/table/TableCell.vue.d.ts +17 -0
- package/dist/components/table/TableHead.vue.d.ts +12 -0
- package/dist/components/table/TableRow.vue.d.ts +15 -0
- package/dist/composables/useRecentItems.d.ts +9 -0
- package/dist/index.d.ts +82 -0
- package/dist/stores/toast.d.ts +148 -0
- package/dist/wpic-ui.cjs +9318 -0
- package/dist/wpic-ui.css +284 -0
- package/dist/wpic-ui.js +17570 -0
- package/package.json +51 -0
- package/src/components/chat/DB_SCHEMA.md +440 -0
- package/src/components/chat/LLM_STREAMING.md +390 -0
- package/src/components/chat/UI_INTEGRATION.md +273 -0
- 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`.
|