Spaces:
Sleeping
Sleeping
harryagasi commited on
Commit ·
eb3bd3c
1
Parent(s): f3f70dc
fix: improve analysis chat rendering, scroll, and traceability persistence
Browse files- Fix bold markdown not rendering: reorder repairMarkdownMarkers so
space-adding runs before trim, preventing trim from being undone
- Apply same fix to normalizeAssistantContent before saving to DB
- Add auto-scroll to bottom on send, scroll to top of AI answer on
stream complete
- Tighten markdown spacing (my-3→my-1.5, leading-7→leading-6, space-y-2→space-y-0.5)
- Fix traceability disappearing on page refresh by using message_id
returned from API (now provided by backend) instead of hardcoding
doneMessageId from the agentic stream
- .claude/skills/speckit-implement/SKILL.md +2 -2
- .claude/skills/speckit-specify/SKILL.md +14 -14
- .claude/skills/speckit-tasks/SKILL.md +2 -2
- .specify/templates/checklist-template.md +3 -3
- docs/API_CONTRACT_BE_GOLANG.md +29 -26
- docs/API_CONTRACT_BE_PYTHON.md +1 -1
- specs/001-unified-analysis-chat/contracts/ENDPOINTS.md +4 -4
- specs/001-unified-analysis-chat/contracts/ENDPOINTS_BY_TASK.md +23 -23
- specs/001-unified-analysis-chat/contracts/ENDPOINTS_SUMMARY.md +27 -27
- specs/001-unified-analysis-chat/contracts/ENDPOINT_COMPARISON.md +126 -126
- specs/001-unified-analysis-chat/contracts/EXISTING_ENDPOINTS_ANALYSIS.md +82 -82
- src/app/components/KnowledgeManagement.tsx +22 -22
- src/app/components/analysis/AnalysisShell.tsx +12 -5
- src/app/components/analysis/MarkdownContent.tsx +9 -10
- src/app/components/analysis/MessageList.tsx +29 -4
.claude/skills/speckit-implement/SKILL.md
CHANGED
|
@@ -49,7 +49,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
| 49 |
**Automatic Pre-Hook**: {extension}
|
| 50 |
Executing: `/{command}`
|
| 51 |
EXECUTE_COMMAND: {command}
|
| 52 |
-
|
| 53 |
Wait for the result of the hook command before proceeding to the Outline.
|
| 54 |
```
|
| 55 |
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
|
@@ -150,7 +150,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
| 150 |
|
| 151 |
6. Execute implementation following the task plan:
|
| 152 |
- **Phase-by-phase execution**: Complete each phase before moving to the next
|
| 153 |
-
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
|
| 154 |
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
|
| 155 |
- **File-based coordination**: Tasks affecting the same files must run sequentially
|
| 156 |
- **Validation checkpoints**: Verify each phase completion before proceeding
|
|
|
|
| 49 |
**Automatic Pre-Hook**: {extension}
|
| 50 |
Executing: `/{command}`
|
| 51 |
EXECUTE_COMMAND: {command}
|
| 52 |
+
|
| 53 |
Wait for the result of the hook command before proceeding to the Outline.
|
| 54 |
```
|
| 55 |
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
|
|
|
| 150 |
|
| 151 |
6. Execute implementation following the task plan:
|
| 152 |
- **Phase-by-phase execution**: Complete each phase before moving to the next
|
| 153 |
+
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
|
| 154 |
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
|
| 155 |
- **File-based coordination**: Tasks affecting the same files must run sequentially
|
| 156 |
- **Validation checkpoints**: Verify each phase completion before proceeding
|
.claude/skills/speckit-specify/SKILL.md
CHANGED
|
@@ -144,20 +144,20 @@ Given that feature description, do this:
|
|
| 144 |
|
| 145 |
```markdown
|
| 146 |
# Specification Quality Checklist: [FEATURE NAME]
|
| 147 |
-
|
| 148 |
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 149 |
**Created**: [DATE]
|
| 150 |
**Feature**: [Link to spec.md]
|
| 151 |
-
|
| 152 |
## Content Quality
|
| 153 |
-
|
| 154 |
- [ ] No implementation details (languages, frameworks, APIs)
|
| 155 |
- [ ] Focused on user value and business needs
|
| 156 |
- [ ] Written for non-technical stakeholders
|
| 157 |
- [ ] All mandatory sections completed
|
| 158 |
-
|
| 159 |
## Requirement Completeness
|
| 160 |
-
|
| 161 |
- [ ] No [NEEDS CLARIFICATION] markers remain
|
| 162 |
- [ ] Requirements are testable and unambiguous
|
| 163 |
- [ ] Success criteria are measurable
|
|
@@ -166,16 +166,16 @@ Given that feature description, do this:
|
|
| 166 |
- [ ] Edge cases are identified
|
| 167 |
- [ ] Scope is clearly bounded
|
| 168 |
- [ ] Dependencies and assumptions identified
|
| 169 |
-
|
| 170 |
## Feature Readiness
|
| 171 |
-
|
| 172 |
- [ ] All functional requirements have clear acceptance criteria
|
| 173 |
- [ ] User scenarios cover primary flows
|
| 174 |
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
| 175 |
- [ ] No implementation details leak into specification
|
| 176 |
-
|
| 177 |
## Notes
|
| 178 |
-
|
| 179 |
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
|
| 180 |
```
|
| 181 |
|
|
@@ -200,20 +200,20 @@ Given that feature description, do this:
|
|
| 200 |
|
| 201 |
```markdown
|
| 202 |
## Question [N]: [Topic]
|
| 203 |
-
|
| 204 |
**Context**: [Quote relevant spec section]
|
| 205 |
-
|
| 206 |
**What we need to know**: [Specific question from NEEDS CLARIFICATION marker]
|
| 207 |
-
|
| 208 |
**Suggested Answers**:
|
| 209 |
-
|
| 210 |
| Option | Answer | Implications |
|
| 211 |
|--------|--------|--------------|
|
| 212 |
| A | [First suggested answer] | [What this means for the feature] |
|
| 213 |
| B | [Second suggested answer] | [What this means for the feature] |
|
| 214 |
| C | [Third suggested answer] | [What this means for the feature] |
|
| 215 |
| Custom | Provide your own answer | [Explain how to provide custom input] |
|
| 216 |
-
|
| 217 |
**Your choice**: _[Wait for user response]_
|
| 218 |
```
|
| 219 |
|
|
|
|
| 144 |
|
| 145 |
```markdown
|
| 146 |
# Specification Quality Checklist: [FEATURE NAME]
|
| 147 |
+
|
| 148 |
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 149 |
**Created**: [DATE]
|
| 150 |
**Feature**: [Link to spec.md]
|
| 151 |
+
|
| 152 |
## Content Quality
|
| 153 |
+
|
| 154 |
- [ ] No implementation details (languages, frameworks, APIs)
|
| 155 |
- [ ] Focused on user value and business needs
|
| 156 |
- [ ] Written for non-technical stakeholders
|
| 157 |
- [ ] All mandatory sections completed
|
| 158 |
+
|
| 159 |
## Requirement Completeness
|
| 160 |
+
|
| 161 |
- [ ] No [NEEDS CLARIFICATION] markers remain
|
| 162 |
- [ ] Requirements are testable and unambiguous
|
| 163 |
- [ ] Success criteria are measurable
|
|
|
|
| 166 |
- [ ] Edge cases are identified
|
| 167 |
- [ ] Scope is clearly bounded
|
| 168 |
- [ ] Dependencies and assumptions identified
|
| 169 |
+
|
| 170 |
## Feature Readiness
|
| 171 |
+
|
| 172 |
- [ ] All functional requirements have clear acceptance criteria
|
| 173 |
- [ ] User scenarios cover primary flows
|
| 174 |
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
| 175 |
- [ ] No implementation details leak into specification
|
| 176 |
+
|
| 177 |
## Notes
|
| 178 |
+
|
| 179 |
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
|
| 180 |
```
|
| 181 |
|
|
|
|
| 200 |
|
| 201 |
```markdown
|
| 202 |
## Question [N]: [Topic]
|
| 203 |
+
|
| 204 |
**Context**: [Quote relevant spec section]
|
| 205 |
+
|
| 206 |
**What we need to know**: [Specific question from NEEDS CLARIFICATION marker]
|
| 207 |
+
|
| 208 |
**Suggested Answers**:
|
| 209 |
+
|
| 210 |
| Option | Answer | Implications |
|
| 211 |
|--------|--------|--------------|
|
| 212 |
| A | [First suggested answer] | [What this means for the feature] |
|
| 213 |
| B | [Second suggested answer] | [What this means for the feature] |
|
| 214 |
| C | [Third suggested answer] | [What this means for the feature] |
|
| 215 |
| Custom | Provide your own answer | [Explain how to provide custom input] |
|
| 216 |
+
|
| 217 |
**Your choice**: _[Wait for user response]_
|
| 218 |
```
|
| 219 |
|
.claude/skills/speckit-tasks/SKILL.md
CHANGED
|
@@ -49,7 +49,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|
| 49 |
**Automatic Pre-Hook**: {extension}
|
| 50 |
Executing: `/{command}`
|
| 51 |
EXECUTE_COMMAND: {command}
|
| 52 |
-
|
| 53 |
Wait for the result of the hook command before proceeding to the Outline.
|
| 54 |
```
|
| 55 |
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
|
@@ -151,7 +151,7 @@ Every task MUST strictly follow this format:
|
|
| 151 |
4. **[Story] label**: REQUIRED for user story phase tasks only
|
| 152 |
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
| 153 |
- Setup phase: NO story label
|
| 154 |
-
- Foundational phase: NO story label
|
| 155 |
- User Story phases: MUST have story label
|
| 156 |
- Polish phase: NO story label
|
| 157 |
5. **Description**: Clear action with exact file path
|
|
|
|
| 49 |
**Automatic Pre-Hook**: {extension}
|
| 50 |
Executing: `/{command}`
|
| 51 |
EXECUTE_COMMAND: {command}
|
| 52 |
+
|
| 53 |
Wait for the result of the hook command before proceeding to the Outline.
|
| 54 |
```
|
| 55 |
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
|
|
|
| 151 |
4. **[Story] label**: REQUIRED for user story phase tasks only
|
| 152 |
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
| 153 |
- Setup phase: NO story label
|
| 154 |
+
- Foundational phase: NO story label
|
| 155 |
- User Story phases: MUST have story label
|
| 156 |
- Polish phase: NO story label
|
| 157 |
5. **Description**: Clear action with exact file path
|
.specify/templates/checklist-template.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
| 6 |
|
| 7 |
**Note**: This checklist is generated by the `/speckit-checklist` command based on feature context and requirements.
|
| 8 |
|
| 9 |
-
<!--
|
| 10 |
============================================================================
|
| 11 |
IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
|
| 12 |
-
|
| 13 |
The /speckit-checklist command MUST replace these with actual items based on:
|
| 14 |
- User's specific checklist request
|
| 15 |
- Feature requirements from spec.md
|
| 16 |
- Technical context from plan.md
|
| 17 |
- Implementation details from tasks.md
|
| 18 |
-
|
| 19 |
DO NOT keep these sample items in the generated checklist file.
|
| 20 |
============================================================================
|
| 21 |
-->
|
|
|
|
| 6 |
|
| 7 |
**Note**: This checklist is generated by the `/speckit-checklist` command based on feature context and requirements.
|
| 8 |
|
| 9 |
+
<!--
|
| 10 |
============================================================================
|
| 11 |
IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
|
| 12 |
+
|
| 13 |
The /speckit-checklist command MUST replace these with actual items based on:
|
| 14 |
- User's specific checklist request
|
| 15 |
- Feature requirements from spec.md
|
| 16 |
- Technical context from plan.md
|
| 17 |
- Implementation details from tasks.md
|
| 18 |
+
|
| 19 |
DO NOT keep these sample items in the generated checklist file.
|
| 20 |
============================================================================
|
| 21 |
-->
|
docs/API_CONTRACT_BE_GOLANG.md
CHANGED
|
@@ -106,7 +106,7 @@ Flow yang direkomendasikan:
|
|
| 106 |
2. Ambil `data.id` dari response sebagai `analysis_id`.
|
| 107 |
3. Frontend memanggil AI Agent Service terpisah memakai context analysis, business_questions, dan catalog.
|
| 108 |
4. Saat user mulai bertanya ke AI Agent Service, rekam pertanyaan dengan `role=user` ke endpoint messages.
|
| 109 |
-
5. Setelah AI Agent Service menjawab, simpan jawaban dengan `role=ai`
|
| 110 |
|
| 111 |
### 5. Conversation Recording
|
| 112 |
|
|
@@ -128,7 +128,6 @@ Frontend bertanggung jawab melakukan dua write terpisah:
|
|
| 128 |
```json
|
| 129 |
{
|
| 130 |
"role": "ai",
|
| 131 |
-
"message_id": "msg_88f1",
|
| 132 |
"content": "Revenue Q3 turun terutama karena penurunan volume transaksi di segmen enterprise..."
|
| 133 |
}
|
| 134 |
```
|
|
@@ -141,7 +140,7 @@ Frontend bertanggung jawab melakukan dua write terpisah:
|
|
| 141 |
| `POST` | `/api/login` | Login dan issue token pair |
|
| 142 |
| `POST` | `/api/refresh` | Rotate refresh token dan issue token pair baru |
|
| 143 |
| `GET` | `/api/v1/documents/doctypes` | List tipe dokumen yang didukung |
|
| 144 |
-
| `POST` | `/api/v1/document/upload` | Upload dokumen ke
|
| 145 |
| `POST` | `/api/v1/document/upload-local` | Upload dokumen ke local filesystem untuk benchmark |
|
| 146 |
| `POST` | `/api/v1/document/process` | Proses dokumen async |
|
| 147 |
| `GET` | `/api/v1/documents/{user_id}` | List dokumen milik user |
|
|
@@ -244,6 +243,8 @@ Errors: `400`, `401`, `403`, `500`.
|
|
| 244 |
|
| 245 |
Status umum: `uploaded`, `processing`, `processed`, `failed`.
|
| 246 |
|
|
|
|
|
|
|
| 247 |
### `GET /api/v1/documents/doctypes`
|
| 248 |
|
| 249 |
Mengambil tipe dokumen yang didukung.
|
|
@@ -267,7 +268,7 @@ Success `200`:
|
|
| 267 |
|
| 268 |
### `POST /api/v1/document/upload`
|
| 269 |
|
| 270 |
-
Upload dokumen ke
|
| 271 |
|
| 272 |
Content-Type: `multipart/form-data`
|
| 273 |
|
|
@@ -284,7 +285,7 @@ Errors: `400`, `401`, `403`, `429`, `500`.
|
|
| 284 |
|
| 285 |
### `POST /api/v1/document/upload-local`
|
| 286 |
|
| 287 |
-
Upload file ke filesystem lokal untuk benchmarking. Kontrak form sama dengan upload
|
| 288 |
|
| 289 |
Success `201`:
|
| 290 |
|
|
@@ -347,7 +348,7 @@ Errors: `401`, `403`, `500`.
|
|
| 347 |
|
| 348 |
### `DELETE /api/v1/document/delete`
|
| 349 |
|
| 350 |
-
Menghapus dokumen dari storage, embedding/parquet terkait, dan record database.
|
| 351 |
|
| 352 |
Request:
|
| 353 |
|
|
@@ -548,7 +549,7 @@ Success `200`:
|
|
| 548 |
"source_id": "document-or-client-id",
|
| 549 |
"source_type": "tabular",
|
| 550 |
"name": "sales.csv",
|
| 551 |
-
"location_ref": "
|
| 552 |
"table_count": 1,
|
| 553 |
"updated_at": "2026-06-30T08:00:00Z"
|
| 554 |
}
|
|
@@ -806,7 +807,6 @@ Errors:
|
|
| 806 |
"user_id": "user-id",
|
| 807 |
"role": "user",
|
| 808 |
"content": "Apa penyebab revenue turun di Q3?",
|
| 809 |
-
"message_id": null,
|
| 810 |
"created_at": "2026-06-30T08:00:00Z"
|
| 811 |
}
|
| 812 |
```
|
|
@@ -816,8 +816,6 @@ Errors:
|
|
| 816 |
- `user`: pertanyaan atau instruksi dari user
|
| 817 |
- `ai`: jawaban dari AI Agent Service
|
| 818 |
|
| 819 |
-
`message_id` nullable untuk `role=user`, tetapi wajib untuk `role=ai`. Nilainya harus sama dengan `message_id` dari event `done` Python Agentic Service agar message history dapat dikorelasikan dengan observability.
|
| 820 |
-
|
| 821 |
### `POST /api/v1/analyses/{id}/messages`
|
| 822 |
|
| 823 |
Merekam tepat satu pesan conversation ke `analyses_messages`.
|
|
@@ -831,12 +829,11 @@ Request untuk pertanyaan user:
|
|
| 831 |
}
|
| 832 |
```
|
| 833 |
|
| 834 |
-
Request untuk jawaban AI
|
| 835 |
|
| 836 |
```json
|
| 837 |
{
|
| 838 |
"role": "ai",
|
| 839 |
-
"message_id": "msg_88f1",
|
| 840 |
"content": "Revenue turun karena penurunan transaksi enterprise dan kenaikan churn di wilayah barat."
|
| 841 |
}
|
| 842 |
```
|
|
@@ -849,21 +846,23 @@ Success `201`:
|
|
| 849 |
"message": "Message created",
|
| 850 |
"data": {
|
| 851 |
"message": {
|
| 852 |
-
"id": "
|
|
|
|
| 853 |
"analysis_id": "analysis-id",
|
| 854 |
"user_id": "user-id",
|
| 855 |
"role": "user",
|
| 856 |
"content": "Apa penyebab revenue turun di Q3?",
|
| 857 |
-
"message_id": null,
|
| 858 |
"created_at": "2026-06-30T08:00:00Z"
|
| 859 |
}
|
| 860 |
}
|
| 861 |
}
|
| 862 |
```
|
| 863 |
|
|
|
|
|
|
|
| 864 |
Errors:
|
| 865 |
|
| 866 |
-
- `400`: invalid role/content
|
| 867 |
- `401`: token invalid/missing
|
| 868 |
- `409`: inactive analysis atau message limit tercapai
|
| 869 |
|
|
@@ -884,11 +883,23 @@ Success `200`:
|
|
| 884 |
"status": "success",
|
| 885 |
"message": "Messages retrieved",
|
| 886 |
"data": {
|
| 887 |
-
"messages": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
}
|
| 889 |
}
|
| 890 |
```
|
| 891 |
|
|
|
|
|
|
|
| 892 |
Errors: `400`, `401`, `404`.
|
| 893 |
|
| 894 |
## Suggested Frontend Integration Sequence
|
|
@@ -931,7 +942,7 @@ GET /api/v1/data-catalog/{user_id}
|
|
| 931 |
POST /api/v1/analyses with business_questions
|
| 932 |
call AI Agent Service outside this service
|
| 933 |
POST /api/v1/analyses/{analysis_id}/messages with role=user and content=user_question
|
| 934 |
-
POST /api/v1/analyses/{analysis_id}/messages with role=ai
|
| 935 |
GET /api/v1/analyses/{analysis_id}/messages
|
| 936 |
```
|
| 937 |
|
|
@@ -939,16 +950,8 @@ GET /api/v1/analyses/{analysis_id}/messages
|
|
| 939 |
|
| 940 |
- Jangan mengirim pesan user ke `POST /api/v1/analyses/{id}/messages` dengan ekspektasi service ini akan menjawab. Endpoint ini hanya persistence.
|
| 941 |
- AI Agent Service adalah service terpisah. Service ini menyimpan metadata analysis, knowledge catalog, data binding, dan history conversation.
|
| 942 |
-
- Untuk pesan `role=ai`, simpan `message_id` yang dikembalikan Python pada event `done` agar history conversation dapat dikorelasikan dengan observability Python.
|
| 943 |
- User-level catalog berisi seluruh knowledge source user; analysis-level catalog hanya berisi source yang ada di `data_bind` analysis.
|
| 944 |
- Perubahan `data_bind` akan rebuild analysis-level catalog. Frontend juga dapat memanggil endpoint rebuild analysis catalog secara eksplisit jika diperlukan.
|
| 945 |
- Setelah refresh token sukses, selalu replace refresh token lama dengan refresh token baru.
|
| 946 |
- Untuk endpoint yang membutuhkan `user_id`, gunakan `data.user.id` dari login dan pastikan sama dengan token aktif.
|
| 947 |
-
- Untuk binding analysis, pakai source yang sudah berhasil diupload/diproses atau database client yang sudah diingest.
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
|
|
|
| 106 |
2. Ambil `data.id` dari response sebagai `analysis_id`.
|
| 107 |
3. Frontend memanggil AI Agent Service terpisah memakai context analysis, business_questions, dan catalog.
|
| 108 |
4. Saat user mulai bertanya ke AI Agent Service, rekam pertanyaan dengan `role=user` ke endpoint messages.
|
| 109 |
+
5. Setelah AI Agent Service menjawab, simpan jawaban dengan `role=ai` ke endpoint messages.
|
| 110 |
|
| 111 |
### 5. Conversation Recording
|
| 112 |
|
|
|
|
| 128 |
```json
|
| 129 |
{
|
| 130 |
"role": "ai",
|
|
|
|
| 131 |
"content": "Revenue Q3 turun terutama karena penurunan volume transaksi di segmen enterprise..."
|
| 132 |
}
|
| 133 |
```
|
|
|
|
| 140 |
| `POST` | `/api/login` | Login dan issue token pair |
|
| 141 |
| `POST` | `/api/refresh` | Rotate refresh token dan issue token pair baru |
|
| 142 |
| `GET` | `/api/v1/documents/doctypes` | List tipe dokumen yang didukung |
|
| 143 |
+
| `POST` | `/api/v1/document/upload` | Upload dokumen ke private object storage |
|
| 144 |
| `POST` | `/api/v1/document/upload-local` | Upload dokumen ke local filesystem untuk benchmark |
|
| 145 |
| `POST` | `/api/v1/document/process` | Proses dokumen async |
|
| 146 |
| `GET` | `/api/v1/documents/{user_id}` | List dokumen milik user |
|
|
|
|
| 243 |
|
| 244 |
Status umum: `uploaded`, `processing`, `processed`, `failed`.
|
| 245 |
|
| 246 |
+
`blob_name` dipertahankan untuk kompatibilitas response lama, tetapi secara kontrak frontend harus dipahami sebagai stored object reference di private object storage, bukan Azure-specific blob URL.
|
| 247 |
+
|
| 248 |
### `GET /api/v1/documents/doctypes`
|
| 249 |
|
| 250 |
Mengambil tipe dokumen yang didukung.
|
|
|
|
| 268 |
|
| 269 |
### `POST /api/v1/document/upload`
|
| 270 |
|
| 271 |
+
Upload dokumen ke private object storage. Maksimum 10 MB. Mendukung `pdf`, `docx`, `txt`, `csv`, dan `xlsx`.
|
| 272 |
|
| 273 |
Content-Type: `multipart/form-data`
|
| 274 |
|
|
|
|
| 285 |
|
| 286 |
### `POST /api/v1/document/upload-local`
|
| 287 |
|
| 288 |
+
Upload file ke filesystem lokal untuk benchmarking. Kontrak form sama dengan upload object storage.
|
| 289 |
|
| 290 |
Success `201`:
|
| 291 |
|
|
|
|
| 348 |
|
| 349 |
### `DELETE /api/v1/document/delete`
|
| 350 |
|
| 351 |
+
Menghapus dokumen dari private object storage, embedding/parquet terkait, dan record database.
|
| 352 |
|
| 353 |
Request:
|
| 354 |
|
|
|
|
| 549 |
"source_id": "document-or-client-id",
|
| 550 |
"source_type": "tabular",
|
| 551 |
"name": "sales.csv",
|
| 552 |
+
"location_ref": "object-storage/path/or/db-ref",
|
| 553 |
"table_count": 1,
|
| 554 |
"updated_at": "2026-06-30T08:00:00Z"
|
| 555 |
}
|
|
|
|
| 807 |
"user_id": "user-id",
|
| 808 |
"role": "user",
|
| 809 |
"content": "Apa penyebab revenue turun di Q3?",
|
|
|
|
| 810 |
"created_at": "2026-06-30T08:00:00Z"
|
| 811 |
}
|
| 812 |
```
|
|
|
|
| 816 |
- `user`: pertanyaan atau instruksi dari user
|
| 817 |
- `ai`: jawaban dari AI Agent Service
|
| 818 |
|
|
|
|
|
|
|
| 819 |
### `POST /api/v1/analyses/{id}/messages`
|
| 820 |
|
| 821 |
Merekam tepat satu pesan conversation ke `analyses_messages`.
|
|
|
|
| 829 |
}
|
| 830 |
```
|
| 831 |
|
| 832 |
+
Request untuk jawaban AI:
|
| 833 |
|
| 834 |
```json
|
| 835 |
{
|
| 836 |
"role": "ai",
|
|
|
|
| 837 |
"content": "Revenue turun karena penurunan transaksi enterprise dan kenaikan churn di wilayah barat."
|
| 838 |
}
|
| 839 |
```
|
|
|
|
| 846 |
"message": "Message created",
|
| 847 |
"data": {
|
| 848 |
"message": {
|
| 849 |
+
"id": "uuid-dari-db",
|
| 850 |
+
"message_id": "uuid-dari-db",
|
| 851 |
"analysis_id": "analysis-id",
|
| 852 |
"user_id": "user-id",
|
| 853 |
"role": "user",
|
| 854 |
"content": "Apa penyebab revenue turun di Q3?",
|
|
|
|
| 855 |
"created_at": "2026-06-30T08:00:00Z"
|
| 856 |
}
|
| 857 |
}
|
| 858 |
}
|
| 859 |
```
|
| 860 |
|
| 861 |
+
> `message_id` bernilai sama dengan `id` — disertakan agar frontend dapat langsung menggunakannya untuk memanggil endpoint observability (`GET /api/v1/observability?analysis_id=...&message_id=...`) tanpa mapping tambahan.
|
| 862 |
+
|
| 863 |
Errors:
|
| 864 |
|
| 865 |
+
- `400`: invalid role/content, invalid analysis ID
|
| 866 |
- `401`: token invalid/missing
|
| 867 |
- `409`: inactive analysis atau message limit tercapai
|
| 868 |
|
|
|
|
| 883 |
"status": "success",
|
| 884 |
"message": "Messages retrieved",
|
| 885 |
"data": {
|
| 886 |
+
"messages": [
|
| 887 |
+
{
|
| 888 |
+
"id": "uuid-dari-db",
|
| 889 |
+
"message_id": "uuid-dari-db",
|
| 890 |
+
"analysis_id": "analysis-id",
|
| 891 |
+
"user_id": "user-id",
|
| 892 |
+
"role": "ai",
|
| 893 |
+
"content": "Revenue turun karena penurunan transaksi enterprise.",
|
| 894 |
+
"created_at": "2026-06-30T08:01:00Z"
|
| 895 |
+
}
|
| 896 |
+
]
|
| 897 |
}
|
| 898 |
}
|
| 899 |
```
|
| 900 |
|
| 901 |
+
> Setiap item menyertakan `message_id` (sama dengan `id`) untuk digunakan frontend pada pemanggilan `GET /api/v1/observability?analysis_id=...&message_id=...` setelah page refresh.
|
| 902 |
+
|
| 903 |
Errors: `400`, `401`, `404`.
|
| 904 |
|
| 905 |
## Suggested Frontend Integration Sequence
|
|
|
|
| 942 |
POST /api/v1/analyses with business_questions
|
| 943 |
call AI Agent Service outside this service
|
| 944 |
POST /api/v1/analyses/{analysis_id}/messages with role=user and content=user_question
|
| 945 |
+
POST /api/v1/analyses/{analysis_id}/messages with role=ai and content=agent_answer
|
| 946 |
GET /api/v1/analyses/{analysis_id}/messages
|
| 947 |
```
|
| 948 |
|
|
|
|
| 950 |
|
| 951 |
- Jangan mengirim pesan user ke `POST /api/v1/analyses/{id}/messages` dengan ekspektasi service ini akan menjawab. Endpoint ini hanya persistence.
|
| 952 |
- AI Agent Service adalah service terpisah. Service ini menyimpan metadata analysis, knowledge catalog, data binding, dan history conversation.
|
|
|
|
| 953 |
- User-level catalog berisi seluruh knowledge source user; analysis-level catalog hanya berisi source yang ada di `data_bind` analysis.
|
| 954 |
- Perubahan `data_bind` akan rebuild analysis-level catalog. Frontend juga dapat memanggil endpoint rebuild analysis catalog secara eksplisit jika diperlukan.
|
| 955 |
- Setelah refresh token sukses, selalu replace refresh token lama dengan refresh token baru.
|
| 956 |
- Untuk endpoint yang membutuhkan `user_id`, gunakan `data.user.id` dari login dan pastikan sama dengan token aktif.
|
| 957 |
+
- Untuk binding analysis, pakai source yang sudah berhasil diupload/diproses atau database client yang sudah diingest.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/API_CONTRACT_BE_PYTHON.md
CHANGED
|
@@ -98,7 +98,7 @@ event: status
|
|
| 98 |
data: Running 3 steps...
|
| 99 |
|
| 100 |
event: chunk
|
| 101 |
-
data: Total sales by region last quarter:
|
| 102 |
|
| 103 |
event: chunk
|
| 104 |
data: Central led at $1.21M (38%), East $0.74M, West $0.55M (down 12% QoQ).
|
|
|
|
| 98 |
data: Running 3 steps...
|
| 99 |
|
| 100 |
event: chunk
|
| 101 |
+
data: Total sales by region last quarter:
|
| 102 |
|
| 103 |
event: chunk
|
| 104 |
data: Central led at $1.21M (38%), East $0.74M, West $0.55M (down 12% QoQ).
|
specs/001-unified-analysis-chat/contracts/ENDPOINTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
|
| 2 |
# Backend API Endpoints Required
|
| 3 |
|
| 4 |
-
**Feature**: Unified Analysis Chat with Skills
|
| 5 |
-
**Date**: 2026-06-15
|
| 6 |
**Status**: Required for Implementation
|
| 7 |
|
| 8 |
## Overview
|
|
@@ -779,7 +779,7 @@ Based on Success Criteria:
|
|
| 779 |
|
| 780 |
2. **Message Order**: Ensure messages are returned in chronological order (timestamp ASC) for proper conversation flow.
|
| 781 |
|
| 782 |
-
3. **Validation**:
|
| 783 |
- Validate title length (1-200 chars)
|
| 784 |
- Validate problem statement length (1-2000 chars)
|
| 785 |
- Prevent removing all data sources from an analysis
|
|
|
|
| 1 |
+
> Superseded planning note: For the 2026-06-30 dual-backend UX plan, use `frontend-dual-backend-contract.md` as the authoritative frontend contract. This file is retained as historical context from the older endpoint analysis.
|
| 2 |
# Backend API Endpoints Required
|
| 3 |
|
| 4 |
+
**Feature**: Unified Analysis Chat with Skills
|
| 5 |
+
**Date**: 2026-06-15
|
| 6 |
**Status**: Required for Implementation
|
| 7 |
|
| 8 |
## Overview
|
|
|
|
| 779 |
|
| 780 |
2. **Message Order**: Ensure messages are returned in chronological order (timestamp ASC) for proper conversation flow.
|
| 781 |
|
| 782 |
+
3. **Validation**:
|
| 783 |
- Validate title length (1-200 chars)
|
| 784 |
- Validate problem statement length (1-2000 chars)
|
| 785 |
- Prevent removing all data sources from an analysis
|
specs/001-unified-analysis-chat/contracts/ENDPOINTS_BY_TASK.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
# Backend Endpoints Needed - Organized by Task
|
| 3 |
|
| 4 |
This document maps frontend implementation tasks to the backend endpoints they depend on.
|
|
@@ -273,19 +273,19 @@ updateAnalysis(analysisId, { dataSources: [...] }); // Already needed
|
|
| 273 |
## Summary: All Required Endpoints
|
| 274 |
|
| 275 |
### MUST HAVE (MVP):
|
| 276 |
-
1.
|
| 277 |
-
2.
|
| 278 |
-
3.
|
| 279 |
-
4.
|
| 280 |
-
5.
|
| 281 |
-
6.
|
| 282 |
-
7.
|
| 283 |
-
8.
|
| 284 |
-
9.
|
| 285 |
|
| 286 |
### NICE TO HAVE (Optional):
|
| 287 |
-
10.
|
| 288 |
-
11.
|
| 289 |
|
| 290 |
---
|
| 291 |
|
|
@@ -297,7 +297,7 @@ updateAnalysis(analysisId, { dataSources: [...] }); // Already needed
|
|
| 297 |
3. **READ**: GET `/api/analyses` with pagination
|
| 298 |
4. **READ**: GET `/api/analyses/:id` with messages included
|
| 299 |
5. **UPDATE**: PUT `/api/analyses/:id` with validation (min 1 source)
|
| 300 |
-
6. **DELETE**: DELETE `/api/analyses/:id`
|
| 301 |
7. **READ**: GET `/api/skills` with status computation
|
| 302 |
8. **READ**: GET `/api/datasources`
|
| 303 |
|
|
@@ -315,16 +315,16 @@ updateAnalysis(analysisId, { dataSources: [...] }); // Already needed
|
|
| 315 |
|
| 316 |
## Backend Testing Checklist
|
| 317 |
|
| 318 |
-
- [ ] POST `/api/analyses` with valid data
|
| 319 |
-
- [ ] POST `/api/analyses` without data sources
|
| 320 |
-
- [ ] GET `/api/analyses`
|
| 321 |
-
- [ ] GET `/api/analyses/:id` for other user's analysis
|
| 322 |
-
- [ ] PUT `/api/analyses/:id` removing all sources
|
| 323 |
-
- [ ] GET `/api/skills?analysisId=X`
|
| 324 |
-
- [ ] POST `/api/analyses/:id/messages` with unmet precondition
|
| 325 |
-
- [ ] POST `/api/analyses/:id/messages` with valid skill
|
| 326 |
-
- [ ] GET `/api/datasources`
|
| 327 |
-
- [ ] All endpoints without auth
|
| 328 |
- [ ] Pagination works (limit, offset, hasMore)
|
| 329 |
- [ ] Error responses include error code, message, timestamp
|
| 330 |
|
|
|
|
| 1 |
+
> Superseded planning note: For the 2026-06-30 dual-backend UX plan, use `frontend-dual-backend-contract.md` as the authoritative frontend contract. This file is retained as historical context from the older endpoint analysis.
|
| 2 |
# Backend Endpoints Needed - Organized by Task
|
| 3 |
|
| 4 |
This document maps frontend implementation tasks to the backend endpoints they depend on.
|
|
|
|
| 273 |
## Summary: All Required Endpoints
|
| 274 |
|
| 275 |
### MUST HAVE (MVP):
|
| 276 |
+
1. [OK] POST `/api/analyses` - Create analysis
|
| 277 |
+
2. [OK] GET `/api/analyses` - List analyses
|
| 278 |
+
3. [OK] GET `/api/analyses/:id` - Get analysis with messages
|
| 279 |
+
4. [OK] PUT `/api/analyses/:id` - Update analysis
|
| 280 |
+
5. [OK] DELETE `/api/analyses/:id` - Delete analysis
|
| 281 |
+
6. [OK] POST `/api/analyses/:id/messages` - Add message/invoke skill
|
| 282 |
+
7. [OK] GET `/api/analyses/:id/messages` - Get messages (paginated)
|
| 283 |
+
8. [OK] GET `/api/skills` - Get available skills with status
|
| 284 |
+
9. [OK] GET `/api/datasources` - Get available data sources
|
| 285 |
|
| 286 |
### NICE TO HAVE (Optional):
|
| 287 |
+
10. [WARN] GET `/api/skills/:skillName` - Get single skill details
|
| 288 |
+
11. [WARN] WebSocket `/ws/analyses/:id` - Real-time skill status updates
|
| 289 |
|
| 290 |
---
|
| 291 |
|
|
|
|
| 297 |
3. **READ**: GET `/api/analyses` with pagination
|
| 298 |
4. **READ**: GET `/api/analyses/:id` with messages included
|
| 299 |
5. **UPDATE**: PUT `/api/analyses/:id` with validation (min 1 source)
|
| 300 |
+
6. **DELETE**: DELETE `/api/analyses/:id`
|
| 301 |
7. **READ**: GET `/api/skills` with status computation
|
| 302 |
8. **READ**: GET `/api/datasources`
|
| 303 |
|
|
|
|
| 315 |
|
| 316 |
## Backend Testing Checklist
|
| 317 |
|
| 318 |
+
- [ ] POST `/api/analyses` with valid data -> returns 201 with full object
|
| 319 |
+
- [ ] POST `/api/analyses` without data sources -> returns 400 "MIN_SOURCES_REQUIRED"
|
| 320 |
+
- [ ] GET `/api/analyses` -> returns user's analyses only
|
| 321 |
+
- [ ] GET `/api/analyses/:id` for other user's analysis -> returns 403 Forbidden
|
| 322 |
+
- [ ] PUT `/api/analyses/:id` removing all sources -> returns 400 "Cannot remove all sources"
|
| 323 |
+
- [ ] GET `/api/skills?analysisId=X` -> returns status based on analysis state
|
| 324 |
+
- [ ] POST `/api/analyses/:id/messages` with unmet precondition -> returns 400 "PRECONDITION_UNMET"
|
| 325 |
+
- [ ] POST `/api/analyses/:id/messages` with valid skill -> returns 201 with message + skillResponse
|
| 326 |
+
- [ ] GET `/api/datasources` -> returns documents/databases user has access to
|
| 327 |
+
- [ ] All endpoints without auth -> return 401 Unauthorized
|
| 328 |
- [ ] Pagination works (limit, offset, hasMore)
|
| 329 |
- [ ] Error responses include error code, message, timestamp
|
| 330 |
|
specs/001-unified-analysis-chat/contracts/ENDPOINTS_SUMMARY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
|
| 2 |
# Backend API Endpoints - Quick Reference
|
| 3 |
|
| 4 |
-
##
|
| 5 |
|
| 6 |
| Category | Endpoint | Method | Purpose |
|
| 7 |
|----------|----------|--------|---------|
|
|
@@ -18,7 +18,7 @@
|
|
| 18 |
|
| 19 |
---
|
| 20 |
|
| 21 |
-
##
|
| 22 |
|
| 23 |
### Phase 2 Foundational Tasks Needed:
|
| 24 |
- **T010**: `analysisService.ts` needs endpoints: POST/GET/PUT/DELETE `/api/analyses`, GET `/api/analyses/:id/messages`
|
|
@@ -35,7 +35,7 @@
|
|
| 35 |
|
| 36 |
---
|
| 37 |
|
| 38 |
-
##
|
| 39 |
|
| 40 |
If delivering MVP with just **US1 (Unified Interface)**:
|
| 41 |
|
|
@@ -48,7 +48,7 @@ These 4 endpoints are the **absolute minimum** to display a working unified chat
|
|
| 48 |
|
| 49 |
---
|
| 50 |
|
| 51 |
-
##
|
| 52 |
|
| 53 |
### Skill Status Management
|
| 54 |
```
|
|
@@ -61,7 +61,7 @@ Response includes:
|
|
| 61 |
|
| 62 |
**Frontend responsibility**: Display which skills are available and why others are disabled
|
| 63 |
|
| 64 |
-
**Backend responsibility**:
|
| 65 |
- Compute `status` based on analysis state and skill preconditions
|
| 66 |
- Examples:
|
| 67 |
- `/analyze-*` skills are `unavailable` until `/problem-statement` is called
|
|
@@ -80,7 +80,7 @@ PUT /api/analyses/:id
|
|
| 80 |
Error if: Attempting to remove all sources
|
| 81 |
```
|
| 82 |
|
| 83 |
-
**Frontend responsibility**:
|
| 84 |
- Validate form before submission (min 1 source selected)
|
| 85 |
- Show error if trying to remove last source
|
| 86 |
|
|
@@ -93,9 +93,9 @@ PUT /api/analyses/:id
|
|
| 93 |
### Skill Invocation with Preconditions
|
| 94 |
```
|
| 95 |
POST /api/analyses/:id/messages
|
| 96 |
-
Request: {
|
| 97 |
content: "/analyze-descriptive",
|
| 98 |
-
skillName: "analyze-descriptive"
|
| 99 |
}
|
| 100 |
|
| 101 |
Response if precondition unmet:
|
|
@@ -108,7 +108,7 @@ Response if precondition unmet:
|
|
| 108 |
```
|
| 109 |
|
| 110 |
**Frontend responsibility**:
|
| 111 |
-
- Display error message in chat: "
|
| 112 |
- Don't gray out skills (backend handles status via GET /api/skills)
|
| 113 |
|
| 114 |
**Backend responsibility**:
|
|
@@ -117,7 +117,7 @@ Response if precondition unmet:
|
|
| 117 |
|
| 118 |
---
|
| 119 |
|
| 120 |
-
##
|
| 121 |
|
| 122 |
Every message in the system has this structure:
|
| 123 |
|
|
@@ -142,7 +142,7 @@ Every message in the system has this structure:
|
|
| 142 |
|
| 143 |
---
|
| 144 |
|
| 145 |
-
##
|
| 146 |
|
| 147 |
Per Success Criteria from spec.md:
|
| 148 |
|
|
@@ -156,7 +156,7 @@ Per Success Criteria from spec.md:
|
|
| 156 |
|
| 157 |
---
|
| 158 |
|
| 159 |
-
##
|
| 160 |
|
| 161 |
- **All endpoints** require authenticated user (JWT token / session)
|
| 162 |
- **User isolation**: Only return/allow access to user's own analyses
|
|
@@ -164,7 +164,7 @@ Per Success Criteria from spec.md:
|
|
| 164 |
|
| 165 |
---
|
| 166 |
|
| 167 |
-
##
|
| 168 |
|
| 169 |
All errors follow this format:
|
| 170 |
|
|
@@ -188,36 +188,36 @@ All errors follow this format:
|
|
| 188 |
|
| 189 |
---
|
| 190 |
|
| 191 |
-
##
|
| 192 |
|
| 193 |
```
|
| 194 |
User types: "/analyze-descriptive" in chat
|
| 195 |
-
|
| 196 |
Frontend: POST /api/analyses/:id/messages
|
| 197 |
{
|
| 198 |
content: "/analyze-descriptive",
|
| 199 |
skillName: "analyze-descriptive"
|
| 200 |
}
|
| 201 |
-
|
| 202 |
Backend checks:
|
| 203 |
-
1. Is user authenticated?
|
| 204 |
-
2. Does user own analysis?
|
| 205 |
-
3. Are preconditions met?
|
| 206 |
-
- Is /problem-statement called?
|
| 207 |
-
- Any other preconditions?
|
| 208 |
4. Execute skill with analysis context
|
| 209 |
5. Return result
|
| 210 |
-
|
| 211 |
Backend response (201 Created):
|
| 212 |
{
|
| 213 |
-
"message": {
|
| 214 |
user message with skill metadata
|
| 215 |
},
|
| 216 |
"skillResponse": {
|
| 217 |
system message with skill output
|
| 218 |
}
|
| 219 |
}
|
| 220 |
-
|
| 221 |
Frontend displays:
|
| 222 |
- User message: "/analyze-descriptive" (styled with skill icon)
|
| 223 |
- System message: "[skill output]" (styled as system response)
|
|
@@ -225,7 +225,7 @@ Frontend displays:
|
|
| 225 |
|
| 226 |
---
|
| 227 |
|
| 228 |
-
##
|
| 229 |
|
| 230 |
- [ ] Create Analysis table/collection with fields: id, title, problemStatement, dataSources, createdAt, updatedAt, ownerId
|
| 231 |
- [ ] Create Message table/collection with fields: id, analysisId, content, author, timestamp, isSkillInvocation, skillName, skillStatus
|
|
@@ -242,7 +242,7 @@ Frontend displays:
|
|
| 242 |
|
| 243 |
---
|
| 244 |
|
| 245 |
-
##
|
| 246 |
|
| 247 |
1. **Skill Execution**: How long do skills take to execute? Should we make skill invocation async with polling/websocket for updates?
|
| 248 |
|
|
|
|
| 1 |
+
> Superseded planning note: For the 2026-06-30 dual-backend UX plan, use `frontend-dual-backend-contract.md` as the authoritative frontend contract. This file is retained as historical context from the older endpoint analysis.
|
| 2 |
# Backend API Endpoints - Quick Reference
|
| 3 |
|
| 4 |
+
## Endpoint Summary
|
| 5 |
|
| 6 |
| Category | Endpoint | Method | Purpose |
|
| 7 |
|----------|----------|--------|---------|
|
|
|
|
| 18 |
|
| 19 |
---
|
| 20 |
|
| 21 |
+
## Frontend Dependency by Task
|
| 22 |
|
| 23 |
### Phase 2 Foundational Tasks Needed:
|
| 24 |
- **T010**: `analysisService.ts` needs endpoints: POST/GET/PUT/DELETE `/api/analyses`, GET `/api/analyses/:id/messages`
|
|
|
|
| 35 |
|
| 36 |
---
|
| 37 |
|
| 38 |
+
## Critical Endpoints for MVP (User Story 1 Only)
|
| 39 |
|
| 40 |
If delivering MVP with just **US1 (Unified Interface)**:
|
| 41 |
|
|
|
|
| 48 |
|
| 49 |
---
|
| 50 |
|
| 51 |
+
## Key Features of Each Endpoint
|
| 52 |
|
| 53 |
### Skill Status Management
|
| 54 |
```
|
|
|
|
| 61 |
|
| 62 |
**Frontend responsibility**: Display which skills are available and why others are disabled
|
| 63 |
|
| 64 |
+
**Backend responsibility**:
|
| 65 |
- Compute `status` based on analysis state and skill preconditions
|
| 66 |
- Examples:
|
| 67 |
- `/analyze-*` skills are `unavailable` until `/problem-statement` is called
|
|
|
|
| 80 |
Error if: Attempting to remove all sources
|
| 81 |
```
|
| 82 |
|
| 83 |
+
**Frontend responsibility**:
|
| 84 |
- Validate form before submission (min 1 source selected)
|
| 85 |
- Show error if trying to remove last source
|
| 86 |
|
|
|
|
| 93 |
### Skill Invocation with Preconditions
|
| 94 |
```
|
| 95 |
POST /api/analyses/:id/messages
|
| 96 |
+
Request: {
|
| 97 |
content: "/analyze-descriptive",
|
| 98 |
+
skillName: "analyze-descriptive"
|
| 99 |
}
|
| 100 |
|
| 101 |
Response if precondition unmet:
|
|
|
|
| 108 |
```
|
| 109 |
|
| 110 |
**Frontend responsibility**:
|
| 111 |
+
- Display error message in chat: "[NO] /analyze-descriptive requires /problem-statement to be completed first"
|
| 112 |
- Don't gray out skills (backend handles status via GET /api/skills)
|
| 113 |
|
| 114 |
**Backend responsibility**:
|
|
|
|
| 117 |
|
| 118 |
---
|
| 119 |
|
| 120 |
+
## Message Structure
|
| 121 |
|
| 122 |
Every message in the system has this structure:
|
| 123 |
|
|
|
|
| 142 |
|
| 143 |
---
|
| 144 |
|
| 145 |
+
## Performance Performance Targets
|
| 146 |
|
| 147 |
Per Success Criteria from spec.md:
|
| 148 |
|
|
|
|
| 156 |
|
| 157 |
---
|
| 158 |
|
| 159 |
+
## Authentication & Authorization
|
| 160 |
|
| 161 |
- **All endpoints** require authenticated user (JWT token / session)
|
| 162 |
- **User isolation**: Only return/allow access to user's own analyses
|
|
|
|
| 164 |
|
| 165 |
---
|
| 166 |
|
| 167 |
+
## Error Response Format
|
| 168 |
|
| 169 |
All errors follow this format:
|
| 170 |
|
|
|
|
| 188 |
|
| 189 |
---
|
| 190 |
|
| 191 |
+
## Refresh Data Flow Example: Skill Invocation
|
| 192 |
|
| 193 |
```
|
| 194 |
User types: "/analyze-descriptive" in chat
|
| 195 |
+
v
|
| 196 |
Frontend: POST /api/analyses/:id/messages
|
| 197 |
{
|
| 198 |
content: "/analyze-descriptive",
|
| 199 |
skillName: "analyze-descriptive"
|
| 200 |
}
|
| 201 |
+
v
|
| 202 |
Backend checks:
|
| 203 |
+
1. Is user authenticated? [OK]
|
| 204 |
+
2. Does user own analysis? [OK]
|
| 205 |
+
3. Are preconditions met?
|
| 206 |
+
- Is /problem-statement called? [OK]
|
| 207 |
+
- Any other preconditions? [OK]
|
| 208 |
4. Execute skill with analysis context
|
| 209 |
5. Return result
|
| 210 |
+
v
|
| 211 |
Backend response (201 Created):
|
| 212 |
{
|
| 213 |
+
"message": {
|
| 214 |
user message with skill metadata
|
| 215 |
},
|
| 216 |
"skillResponse": {
|
| 217 |
system message with skill output
|
| 218 |
}
|
| 219 |
}
|
| 220 |
+
v
|
| 221 |
Frontend displays:
|
| 222 |
- User message: "/analyze-descriptive" (styled with skill icon)
|
| 223 |
- System message: "[skill output]" (styled as system response)
|
|
|
|
| 225 |
|
| 226 |
---
|
| 227 |
|
| 228 |
+
## Checklist for Backend Implementation
|
| 229 |
|
| 230 |
- [ ] Create Analysis table/collection with fields: id, title, problemStatement, dataSources, createdAt, updatedAt, ownerId
|
| 231 |
- [ ] Create Message table/collection with fields: id, analysisId, content, author, timestamp, isSkillInvocation, skillName, skillStatus
|
|
|
|
| 242 |
|
| 243 |
---
|
| 244 |
|
| 245 |
+
## Questions for Backend Team
|
| 246 |
|
| 247 |
1. **Skill Execution**: How long do skills take to execute? Should we make skill invocation async with polling/websocket for updates?
|
| 248 |
|
specs/001-unified-analysis-chat/contracts/ENDPOINT_COMPARISON.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
| 1 |
-
|
| 2 |
# Endpoint Comparison: Existing vs Needed
|
| 3 |
|
| 4 |
---
|
| 5 |
|
| 6 |
-
##
|
| 7 |
|
| 8 |
### EXISTING STRUCTURE (Current State)
|
| 9 |
|
| 10 |
```
|
| 11 |
Frontend Pages:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
```
|
| 38 |
|
| 39 |
### NEW STRUCTURE (After Unified Analysis Chat)
|
| 40 |
|
| 41 |
```
|
| 42 |
Frontend:
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
```
|
| 64 |
|
| 65 |
---
|
| 66 |
|
| 67 |
-
##
|
| 68 |
|
| 69 |
### Which Endpoints Can Be Reused/Extended?
|
| 70 |
|
| 71 |
-
####
|
| 72 |
|
| 73 |
```
|
| 74 |
-
Chat Rooms
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
Same: Delete conversation
|
| 89 |
Diff: Same logic
|
| 90 |
```
|
| 91 |
|
| 92 |
```
|
| 93 |
-
Chat Message
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
Needed for: Paginated message history when switching analyses
|
| 101 |
Similar to: GET /api/v1/chat-rooms/:id (but paginated messages)
|
| 102 |
```
|
| 103 |
|
| 104 |
```
|
| 105 |
-
Knowledge Base
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
Same: List available data sources
|
| 112 |
Diff: Aggregate with documents, unified response
|
| 113 |
```
|
| 114 |
|
| 115 |
-
####
|
| 116 |
|
| 117 |
```
|
| 118 |
NEW: PUT /api/analyses/:id
|
|
@@ -133,12 +133,12 @@ NEW: POST /api/analyses/:id/messages with skill precondition validation
|
|
| 133 |
|
| 134 |
---
|
| 135 |
|
| 136 |
-
##
|
| 137 |
|
| 138 |
### Current: Room vs New: Analysis
|
| 139 |
|
| 140 |
```typescript
|
| 141 |
-
//
|
| 142 |
interface Room {
|
| 143 |
id: string;
|
| 144 |
title: string;
|
|
@@ -156,7 +156,7 @@ interface RoomMessage {
|
|
| 156 |
sources?: ChatSource[]; // References to documents used
|
| 157 |
}
|
| 158 |
|
| 159 |
-
//
|
| 160 |
interface Analysis {
|
| 161 |
id: string;
|
| 162 |
title: string;
|
|
@@ -194,7 +194,7 @@ interface DataSource {
|
|
| 194 |
|
| 195 |
---
|
| 196 |
|
| 197 |
-
##
|
| 198 |
|
| 199 |
### How Frontend Will Call New Endpoints
|
| 200 |
|
|
@@ -240,8 +240,8 @@ const analyses = await api.getAnalyses(limit=50); // NEW
|
|
| 240 |
|
| 241 |
// On sidebar item click:
|
| 242 |
const messages = await api.getAnalysisMessages( // NEW
|
| 243 |
-
analysisId,
|
| 244 |
-
limit=50,
|
| 245 |
offset=0
|
| 246 |
);
|
| 247 |
// Load paginated message history
|
|
@@ -249,29 +249,29 @@ const messages = await api.getAnalysisMessages( // NEW
|
|
| 249 |
|
| 250 |
---
|
| 251 |
|
| 252 |
-
##
|
| 253 |
|
| 254 |
### Phase 1: Extend Existing Endpoints (2 days)
|
| 255 |
|
| 256 |
```
|
| 257 |
-
|
| 258 |
- Add: problem_statement TEXT
|
| 259 |
- Add: data_sources JSONB
|
| 260 |
-
|
| 261 |
-
|
| 262 |
- Add: problem_statement, dataSources to request
|
| 263 |
- Validate: dataSources.length >= 1
|
| 264 |
- Test: Create analysis with data sources
|
| 265 |
-
|
| 266 |
-
|
| 267 |
- Include: problem_statement, dataSources in response
|
| 268 |
- Test: Verify data sources in response
|
| 269 |
-
|
| 270 |
-
|
| 271 |
- Include: problem_statement, dataSources
|
| 272 |
- Test: Verify full analysis structure
|
| 273 |
-
|
| 274 |
-
|
| 275 |
- New endpoint: Update analysis
|
| 276 |
- Allow update: title, problem_statement, data_sources
|
| 277 |
- Validate: dataSources.length >= 1
|
|
@@ -281,37 +281,37 @@ const messages = await api.getAnalysisMessages( // NEW
|
|
| 281 |
### Phase 2: New Endpoints (2 days)
|
| 282 |
|
| 283 |
```
|
| 284 |
-
|
| 285 |
- Aggregate: documents + database clients
|
| 286 |
- Format: { id, name, type: "document" | "database" }
|
| 287 |
- Test: Get datasources, verify both types included
|
| 288 |
-
|
| 289 |
-
|
| 290 |
- Query param: analysisId (required)
|
| 291 |
- Return: Array of 10 skills with status
|
| 292 |
-
- Status logic:
|
| 293 |
-
- /analyze-* skills
|
| 294 |
-
- /help, /check-*, /retrieve-*
|
| 295 |
- Test: Verify status changes based on analysis messages
|
| 296 |
-
|
| 297 |
-
|
| 298 |
- Add: is_skill_invocation BOOLEAN
|
| 299 |
- Add: skill_name VARCHAR(255)
|
| 300 |
- Add: skill_status VARCHAR(50)
|
| 301 |
-
|
| 302 |
-
|
| 303 |
- New endpoint or extend existing
|
| 304 |
- Input: { content, skillName?, skillParams? }
|
| 305 |
- Validate: Check skill preconditions
|
| 306 |
- Return error if precondition unmet:
|
| 307 |
{ error: "PRECONDITION_UNMET", precondition: "problem-statement" }
|
| 308 |
-
- Execute skill
|
| 309 |
-
- Test:
|
| 310 |
-
- Send regular message
|
| 311 |
-
- Send skill with met preconditions
|
| 312 |
-
- Send skill with unmet preconditions
|
| 313 |
-
|
| 314 |
-
|
| 315 |
- Query: limit=50, offset=0
|
| 316 |
- Return: { messages, pagination }
|
| 317 |
- Test: Large message history, pagination works
|
|
@@ -320,26 +320,26 @@ const messages = await api.getAnalysisMessages( // NEW
|
|
| 320 |
### Phase 3: Testing & Integration (1 day)
|
| 321 |
|
| 322 |
```
|
| 323 |
-
|
| 324 |
-
- Create analysis
|
| 325 |
-
- Send message
|
| 326 |
-
- Invoke skill
|
| 327 |
-
- Update datasources
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
- Create analysis without dataSources
|
| 331 |
-
- Invoke skill without precondition
|
| 332 |
-
- Remove all datasources
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
- Frontend calls new endpoints
|
| 336 |
-
- Skill invocation
|
| 337 |
-
- Datasource selection
|
| 338 |
```
|
| 339 |
|
| 340 |
---
|
| 341 |
|
| 342 |
-
##
|
| 343 |
|
| 344 |
| Endpoint | Existing | Status | Effort |
|
| 345 |
|----------|----------|--------|--------|
|
|
|
|
| 1 |
+
> Superseded planning note: For the 2026-06-30 dual-backend UX plan, use `frontend-dual-backend-contract.md` as the authoritative frontend contract. This file is retained as historical context from the older endpoint analysis.
|
| 2 |
# Endpoint Comparison: Existing vs Needed
|
| 3 |
|
| 4 |
---
|
| 5 |
|
| 6 |
+
## Visual Comparison
|
| 7 |
|
| 8 |
### EXISTING STRUCTURE (Current State)
|
| 9 |
|
| 10 |
```
|
| 11 |
Frontend Pages:
|
| 12 |
+
+-- Analytics Page (Chat Rooms)
|
| 13 |
+
| +-- GET /api/v1/chat-rooms (List rooms)
|
| 14 |
+
| +-- POST /api/v1/chat-rooms (Create room)
|
| 15 |
+
| +-- GET /api/v1/chat-rooms/:roomId (Get room + messages)
|
| 16 |
+
| +-- DELETE /api/v1/chat-rooms/:roomId (Delete room)
|
| 17 |
+
| +-- POST /api/v1/chat/stream (Send message, get streaming response)
|
| 18 |
+
|
|
| 19 |
+
+-- Knowledge Management Page (Documents + Databases)
|
| 20 |
+
| +-- GET /api/v1/documents/:userId (List documents)
|
| 21 |
+
| +-- POST /api/v1/document/upload (Upload doc)
|
| 22 |
+
| +-- POST /api/v1/document/process (Process doc)
|
| 23 |
+
| +-- DELETE /api/v1/document/delete (Delete doc)
|
| 24 |
+
| +-- GET /api/v1/database-clients/dbtypes
|
| 25 |
+
| +-- POST /api/v1/database-clients (Connect DB)
|
| 26 |
+
| +-- GET /api/v1/database-clients/:userId
|
| 27 |
+
| +-- DELETE /api/v1/database-clients/:id
|
| 28 |
+
| +-- POST /api/v1/database-clients/:id/ingest
|
| 29 |
+
|
|
| 30 |
+
+-- Interview Page (Separate)
|
| 31 |
+
+-- GET /frameworks
|
| 32 |
+
+-- POST /api/v1/interviews/sessions (Create session)
|
| 33 |
+
+-- POST /api/v1/interviews/sessions/:id/message
|
| 34 |
+
+-- POST /api/v1/interviews/sessions/:id/finish
|
| 35 |
+
+-- GET /api/v1/interviews/:roomId/result
|
| 36 |
+
+-- POST /api/v1/interviews/sessions/:id/stream-message
|
| 37 |
```
|
| 38 |
|
| 39 |
### NEW STRUCTURE (After Unified Analysis Chat)
|
| 40 |
|
| 41 |
```
|
| 42 |
Frontend:
|
| 43 |
+
+-- Unified Analysis Chat Page
|
| 44 |
+
| +-- POST /api/analyses (Create analysis) [NEW or EXTEND chat-rooms]
|
| 45 |
+
| +-- GET /api/analyses (List analyses) [NEW or EXTEND]
|
| 46 |
+
| +-- GET /api/analyses/:id (Get analysis + messages) [NEW or EXTEND]
|
| 47 |
+
| +-- PUT /api/analyses/:id (Update analysis) [NEW - untuk update datasources]
|
| 48 |
+
| +-- DELETE /api/analyses/:id (Delete analysis) [NEW or EXTEND]
|
| 49 |
+
| |
|
| 50 |
+
| +-- POST /api/analyses/:id/messages (Add message/invoke skill) [NEW - extend chat/stream]
|
| 51 |
+
| +-- GET /api/analyses/:id/messages (List messages paginated) [NEW]
|
| 52 |
+
| |
|
| 53 |
+
| +-- GET /api/skills?analysisId=X (Get available skills) [NEW - skill management]
|
| 54 |
+
| +-- GET /api/skills/:skillName (Get skill detail) [NEW - optional]
|
| 55 |
+
| |
|
| 56 |
+
| +-- GET /api/datasources (Get documents + databases) [NEW - aggregate]
|
| 57 |
+
|
|
| 58 |
+
+-- Knowledge Management (TETAP ADA)
|
| 59 |
+
| +-- Same as before
|
| 60 |
+
|
|
| 61 |
+
+-- Interview (TETAP ADA atau DIGABUNG)
|
| 62 |
+
+-- Same as before or merged
|
| 63 |
```
|
| 64 |
|
| 65 |
---
|
| 66 |
|
| 67 |
+
## Refresh Endpoint Reuse Strategy
|
| 68 |
|
| 69 |
### Which Endpoints Can Be Reused/Extended?
|
| 70 |
|
| 71 |
+
#### [OK] REUSABLE (Logic mirip):
|
| 72 |
|
| 73 |
```
|
| 74 |
+
Chat Rooms -> Analysis CRUD
|
| 75 |
+
+-- GET /api/v1/chat-rooms -> GET /api/analyses
|
| 76 |
+
| Same: List user's conversations
|
| 77 |
+
| Diff: Response includes dataSources + problem_statement
|
| 78 |
+
|
|
| 79 |
+
+-- POST /api/v1/chat-rooms -> POST /api/analyses
|
| 80 |
+
| Same: Create new conversation
|
| 81 |
+
| Diff: Require dataSources, problem_statement; no longer optional title
|
| 82 |
+
|
|
| 83 |
+
+-- GET /api/v1/chat-rooms/:id -> GET /api/analyses/:id
|
| 84 |
+
| Same: Get full conversation with messages
|
| 85 |
+
| Diff: Include dataSources, problem_statement, skill metadata in messages
|
| 86 |
+
|
|
| 87 |
+
+-- DELETE /api/v1/chat-rooms/:id -> DELETE /api/analyses/:id
|
| 88 |
Same: Delete conversation
|
| 89 |
Diff: Same logic
|
| 90 |
```
|
| 91 |
|
| 92 |
```
|
| 93 |
+
Chat Message -> Analysis Message + Skill Metadata
|
| 94 |
+
+-- POST /api/v1/chat/stream -> POST /api/analyses/:id/messages
|
| 95 |
+
| Same: Send message to conversation, get response
|
| 96 |
+
| Diff: Add skillName, skillParams; add skill invocation metadata
|
| 97 |
+
| Response includes message + optional skillResponse
|
| 98 |
+
|
|
| 99 |
+
+-- NEW REQUIREMENT: GET /api/analyses/:id/messages
|
| 100 |
Needed for: Paginated message history when switching analyses
|
| 101 |
Similar to: GET /api/v1/chat-rooms/:id (but paginated messages)
|
| 102 |
```
|
| 103 |
|
| 104 |
```
|
| 105 |
+
Knowledge Base -> Data Sources
|
| 106 |
+
+-- GET /api/v1/documents -> GET /api/datasources (documents type)
|
| 107 |
+
| Same: List available data sources
|
| 108 |
+
| Diff: Filter by type, more metadata
|
| 109 |
+
|
|
| 110 |
+
+-- GET /api/v1/database-clients -> GET /api/datasources (database type)
|
| 111 |
Same: List available data sources
|
| 112 |
Diff: Aggregate with documents, unified response
|
| 113 |
```
|
| 114 |
|
| 115 |
+
#### [NO] TIDAK BISA REUSE (Logic baru):
|
| 116 |
|
| 117 |
```
|
| 118 |
NEW: PUT /api/analyses/:id
|
|
|
|
| 133 |
|
| 134 |
---
|
| 135 |
|
| 136 |
+
## Data Structure Comparison
|
| 137 |
|
| 138 |
### Current: Room vs New: Analysis
|
| 139 |
|
| 140 |
```typescript
|
| 141 |
+
// [NO] EXISTING: Room (from api.ts)
|
| 142 |
interface Room {
|
| 143 |
id: string;
|
| 144 |
title: string;
|
|
|
|
| 156 |
sources?: ChatSource[]; // References to documents used
|
| 157 |
}
|
| 158 |
|
| 159 |
+
// [OK] NEW: Analysis (needed for new feature)
|
| 160 |
interface Analysis {
|
| 161 |
id: string;
|
| 162 |
title: string;
|
|
|
|
| 194 |
|
| 195 |
---
|
| 196 |
|
| 197 |
+
## Integration Points
|
| 198 |
|
| 199 |
### How Frontend Will Call New Endpoints
|
| 200 |
|
|
|
|
| 240 |
|
| 241 |
// On sidebar item click:
|
| 242 |
const messages = await api.getAnalysisMessages( // NEW
|
| 243 |
+
analysisId,
|
| 244 |
+
limit=50,
|
| 245 |
offset=0
|
| 246 |
);
|
| 247 |
// Load paginated message history
|
|
|
|
| 249 |
|
| 250 |
---
|
| 251 |
|
| 252 |
+
## Implementation Checklist for Backend
|
| 253 |
|
| 254 |
### Phase 1: Extend Existing Endpoints (2 days)
|
| 255 |
|
| 256 |
```
|
| 257 |
+
[ ] Extend Room table schema:
|
| 258 |
- Add: problem_statement TEXT
|
| 259 |
- Add: data_sources JSONB
|
| 260 |
+
|
| 261 |
+
[ ] Extend POST /api/v1/chat-rooms:
|
| 262 |
- Add: problem_statement, dataSources to request
|
| 263 |
- Validate: dataSources.length >= 1
|
| 264 |
- Test: Create analysis with data sources
|
| 265 |
+
|
| 266 |
+
[ ] Extend GET /api/v1/chat-rooms:
|
| 267 |
- Include: problem_statement, dataSources in response
|
| 268 |
- Test: Verify data sources in response
|
| 269 |
+
|
| 270 |
+
[ ] Extend GET /api/v1/chat-rooms/:id:
|
| 271 |
- Include: problem_statement, dataSources
|
| 272 |
- Test: Verify full analysis structure
|
| 273 |
+
|
| 274 |
+
[ ] Extend PUT /api/v1/chat-rooms/:id -> PUT /api/analyses/:id:
|
| 275 |
- New endpoint: Update analysis
|
| 276 |
- Allow update: title, problem_statement, data_sources
|
| 277 |
- Validate: dataSources.length >= 1
|
|
|
|
| 281 |
### Phase 2: New Endpoints (2 days)
|
| 282 |
|
| 283 |
```
|
| 284 |
+
[ ] Implement GET /api/datasources:
|
| 285 |
- Aggregate: documents + database clients
|
| 286 |
- Format: { id, name, type: "document" | "database" }
|
| 287 |
- Test: Get datasources, verify both types included
|
| 288 |
+
|
| 289 |
+
[ ] Implement GET /api/skills:
|
| 290 |
- Query param: analysisId (required)
|
| 291 |
- Return: Array of 10 skills with status
|
| 292 |
+
- Status logic:
|
| 293 |
+
- /analyze-* skills -> unavailable until /problem-statement called
|
| 294 |
+
- /help, /check-*, /retrieve-* -> always available
|
| 295 |
- Test: Verify status changes based on analysis messages
|
| 296 |
+
|
| 297 |
+
[ ] Extend RoomMessage schema:
|
| 298 |
- Add: is_skill_invocation BOOLEAN
|
| 299 |
- Add: skill_name VARCHAR(255)
|
| 300 |
- Add: skill_status VARCHAR(50)
|
| 301 |
+
|
| 302 |
+
[ ] Extend POST /api/v1/chat/stream -> POST /api/analyses/:id/messages:
|
| 303 |
- New endpoint or extend existing
|
| 304 |
- Input: { content, skillName?, skillParams? }
|
| 305 |
- Validate: Check skill preconditions
|
| 306 |
- Return error if precondition unmet:
|
| 307 |
{ error: "PRECONDITION_UNMET", precondition: "problem-statement" }
|
| 308 |
+
- Execute skill -> return both user message + skill response
|
| 309 |
+
- Test:
|
| 310 |
+
- Send regular message -> works
|
| 311 |
+
- Send skill with met preconditions -> works
|
| 312 |
+
- Send skill with unmet preconditions -> error 400
|
| 313 |
+
|
| 314 |
+
[ ] Implement GET /api/analyses/:id/messages (PAGINATED):
|
| 315 |
- Query: limit=50, offset=0
|
| 316 |
- Return: { messages, pagination }
|
| 317 |
- Test: Large message history, pagination works
|
|
|
|
| 320 |
### Phase 3: Testing & Integration (1 day)
|
| 321 |
|
| 322 |
```
|
| 323 |
+
[ ] Integration tests:
|
| 324 |
+
- Create analysis -> Get analysis -> Verify structure
|
| 325 |
+
- Send message -> Get messages -> Verify in list
|
| 326 |
+
- Invoke skill -> Verify response + skill status
|
| 327 |
+
- Update datasources -> Verify changes persisted
|
| 328 |
+
|
| 329 |
+
[ ] Error handling tests:
|
| 330 |
+
- Create analysis without dataSources -> 400
|
| 331 |
+
- Invoke skill without precondition -> 400 PRECONDITION_UNMET
|
| 332 |
+
- Remove all datasources -> 400
|
| 333 |
+
|
| 334 |
+
[ ] Frontend integration:
|
| 335 |
+
- Frontend calls new endpoints -> works
|
| 336 |
+
- Skill invocation -> displays status correctly
|
| 337 |
+
- Datasource selection -> lists both documents & databases
|
| 338 |
```
|
| 339 |
|
| 340 |
---
|
| 341 |
|
| 342 |
+
## Summary Table
|
| 343 |
|
| 344 |
| Endpoint | Existing | Status | Effort |
|
| 345 |
|----------|----------|--------|--------|
|
specs/001-unified-analysis-chat/contracts/EXISTING_ENDPOINTS_ANALYSIS.md
CHANGED
|
@@ -1,39 +1,39 @@
|
|
| 1 |
-
|
| 2 |
# Analisis Endpoint API Existing di Frontend
|
| 3 |
|
| 4 |
-
**Tanggal**: 2026-06-15
|
| 5 |
**Purpose**: Memahami endpoint mana yang sudah ada vs yang perlu ditambah untuk fitur Unified Analysis Chat
|
| 6 |
|
| 7 |
---
|
| 8 |
|
| 9 |
-
##
|
| 10 |
|
| 11 |
```
|
| 12 |
src/services/
|
| 13 |
-
|
| 14 |
-
|
| 15 |
```
|
| 16 |
|
| 17 |
---
|
| 18 |
|
| 19 |
-
##
|
| 20 |
|
| 21 |
```typescript
|
| 22 |
// Dari src/env atau environment variables:
|
| 23 |
|
| 24 |
ORCHESTRATION_API_BASE_URL = "https://api-orchestration.example.com"
|
| 25 |
-
|
| 26 |
|
| 27 |
-
AGENTIC_API_BASE_URL = "https://api-agentic.example.com"
|
| 28 |
-
|
| 29 |
|
| 30 |
INTERVIEW_BASE_URL = "https://api-interview.example.com"
|
| 31 |
-
|
| 32 |
```
|
| 33 |
|
| 34 |
---
|
| 35 |
|
| 36 |
-
##
|
| 37 |
|
| 38 |
### 1. AUTHENTICATION (src/services/api.ts)
|
| 39 |
|
|
@@ -68,13 +68,13 @@ Ini adalah fitur **MIRIP** dengan "Analysis" yang kita butuh, tapi nama/struktur
|
|
| 68 |
// Location: src/services/api.ts:140-166
|
| 69 |
export const getRooms = (userId: string): Promise<Room[]> =>
|
| 70 |
request<{ status: string; message: string; data: Room[] | null }>(
|
| 71 |
-
ORCHESTRATION_BASE_URL,
|
| 72 |
`/api/v1/chat-rooms?user_id=${userId}`
|
| 73 |
).then(res => res.data ?? []);
|
| 74 |
|
| 75 |
export const getRoom = (roomId: string): Promise<RoomDetail> =>
|
| 76 |
request<{ status: string; message: string; data: RoomDetail }>(
|
| 77 |
-
ORCHESTRATION_BASE_URL,
|
| 78 |
`/api/v1/chat-rooms/${roomId}`
|
| 79 |
).then(res => res.data);
|
| 80 |
|
|
@@ -92,7 +92,7 @@ export const deleteRoom = (roomId: string, userId: string) =>
|
|
| 92 |
);
|
| 93 |
```
|
| 94 |
|
| 95 |
-
**
|
| 96 |
- Room = Analysis (conversation)
|
| 97 |
- RoomMessage = Message (tapi belum punya skill metadata)
|
| 98 |
|
|
@@ -112,7 +112,7 @@ export const deleteRoom = (roomId: string, userId: string) =>
|
|
| 112 |
// Location: src/services/api.ts:170-212
|
| 113 |
export const getDocuments = (userId: string): Promise<ApiDocument[]> =>
|
| 114 |
request<{ status: string; message: string; data: ApiDocument[] | null }>(
|
| 115 |
-
ORCHESTRATION_BASE_URL,
|
| 116 |
`/api/v1/documents/${userId}`
|
| 117 |
).then(res => res.data ?? []);
|
| 118 |
|
|
@@ -120,7 +120,7 @@ export const uploadDocument = async (userId: string, file: File) => {
|
|
| 120 |
const form = new FormData();
|
| 121 |
form.append("user_id", userId);
|
| 122 |
form.append("file", file);
|
| 123 |
-
|
| 124 |
const res = await fetch(`${ORCHESTRATION_BASE_URL}/api/v1/document/upload`, {
|
| 125 |
method: "POST",
|
| 126 |
body: form,
|
|
@@ -136,7 +136,7 @@ export const deleteDocument = (userId: string, documentId: string) =>
|
|
| 136 |
);
|
| 137 |
```
|
| 138 |
|
| 139 |
-
**
|
| 140 |
- Documents = Tipe data source (document type)
|
| 141 |
- Kita perlu: `GET /api/datasources` untuk menampilkan opsi pilihan data source dalam dialog
|
| 142 |
|
|
@@ -156,7 +156,7 @@ export const deleteDocument = (userId: string, documentId: string) =>
|
|
| 156 |
// Location: src/services/api.ts:264-295
|
| 157 |
export const getDatabaseClientTypes = (): Promise<DbTypeInfo[]> =>
|
| 158 |
request<{ status: string; message: string; data: DbTypeInfo[] | null }>(
|
| 159 |
-
ORCHESTRATION_BASE_URL,
|
| 160 |
"/api/v1/database-clients/dbtypes"
|
| 161 |
).then(res => res.data ?? []);
|
| 162 |
|
|
@@ -167,7 +167,7 @@ export const connectDatabase = (
|
|
| 167 |
credentials: Record<string, string | number | boolean>
|
| 168 |
): Promise<DatabaseClient> =>
|
| 169 |
request<{ status: string; message: string; data: DatabaseClient }>(
|
| 170 |
-
ORCHESTRATION_BASE_URL,
|
| 171 |
`/api/v1/database-clients`, {
|
| 172 |
method: "POST",
|
| 173 |
body: JSON.stringify({ user_id: userId, name, db_type: dbType, credentials }),
|
|
@@ -175,12 +175,12 @@ export const connectDatabase = (
|
|
| 175 |
|
| 176 |
export const getDatabaseClients = (userId: string): Promise<DatabaseClient[]> =>
|
| 177 |
request<{ status: string; message: string; data: DatabaseClient[] | null }>(
|
| 178 |
-
ORCHESTRATION_BASE_URL,
|
| 179 |
`/api/v1/database-clients/${userId}`
|
| 180 |
).then(res => res.data ?? []);
|
| 181 |
|
| 182 |
export const ingestDatabaseClient = (
|
| 183 |
-
clientId: string,
|
| 184 |
userId: string
|
| 185 |
): Promise<IngestResponse> =>
|
| 186 |
request<{ status: string; message: string; data: IngestResponse }>(
|
|
@@ -190,7 +190,7 @@ export const ingestDatabaseClient = (
|
|
| 190 |
).then(res => res.data);
|
| 191 |
```
|
| 192 |
|
| 193 |
-
**
|
| 194 |
- Database Clients = Type data source (database type)
|
| 195 |
- Ingest = Process untuk membuat data tersedia
|
| 196 |
|
|
@@ -241,7 +241,7 @@ export const streamChat = (
|
|
| 241 |
});
|
| 242 |
```
|
| 243 |
|
| 244 |
-
**
|
| 245 |
- Mirip dengan: `POST /api/analyses/:id/messages` yang kita butuh
|
| 246 |
- Tapi ini hanya untuk plain message, belum ada skill invocation metadata
|
| 247 |
|
|
@@ -295,13 +295,13 @@ export const streamMessage = (
|
|
| 295 |
});
|
| 296 |
```
|
| 297 |
|
| 298 |
-
**
|
| 299 |
- Kemungkinan: Kita akan **MERGER** interview + analytics menjadi satu Analysis Chat
|
| 300 |
- Tapi interview API tetap terpisah untuk structured interview flow
|
| 301 |
|
| 302 |
---
|
| 303 |
|
| 304 |
-
##
|
| 305 |
|
| 306 |
### Total: 22 Endpoint
|
| 307 |
|
|
@@ -309,65 +309,65 @@ export const streamMessage = (
|
|
| 309 |
|
| 310 |
| Feature | Count | Status |
|
| 311 |
|---------|-------|--------|
|
| 312 |
-
| Authentication | 1 |
|
| 313 |
-
| Chat Rooms (Analysis equivalents) | 5 |
|
| 314 |
-
| Documents (Data Sources) | 5 |
|
| 315 |
-
| Database Clients (Data Sources) | 4 |
|
| 316 |
-
| Data Catalog | 2 |
|
| 317 |
-
| Chat Streaming | 1 |
|
| 318 |
-
| Interview | 6 |
|
| 319 |
|
| 320 |
---
|
| 321 |
|
| 322 |
-
##
|
| 323 |
|
| 324 |
Dari dokumentasi yang sudah saya buat (`ENDPOINTS.md`), inilah endpoint BARU yang perlu ditambah:
|
| 325 |
|
| 326 |
| Endpoint | Status | Catatan |
|
| 327 |
|----------|--------|---------|
|
| 328 |
-
| `POST /api/analyses` |
|
| 329 |
-
| `GET /api/analyses` |
|
| 330 |
-
| `GET /api/analyses/:id` |
|
| 331 |
-
| `PUT /api/analyses/:id` |
|
| 332 |
-
| `DELETE /api/analyses/:id` |
|
| 333 |
-
| `POST /api/analyses/:id/messages` |
|
| 334 |
-
| `GET /api/analyses/:id/messages` |
|
| 335 |
-
| `GET /api/skills` |
|
| 336 |
-
| `GET /api/datasources` |
|
| 337 |
-
| `GET /api/skills/:skillName` |
|
| 338 |
|
| 339 |
---
|
| 340 |
|
| 341 |
-
##
|
| 342 |
|
| 343 |
### BISA LANGSUNG REUSE:
|
| 344 |
|
| 345 |
```typescript
|
| 346 |
-
// Chat Room CRUD
|
| 347 |
// Struktur sama, tinggal ubah nama endpoint
|
| 348 |
|
| 349 |
-
|
| 350 |
-
|
| 351 |
|
| 352 |
-
|
| 353 |
-
|
| 354 |
|
| 355 |
-
|
| 356 |
-
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
```
|
| 361 |
|
| 362 |
### PERLU DIMODIFIKASI:
|
| 363 |
|
| 364 |
```typescript
|
| 365 |
-
// Chat Message
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
content,
|
| 369 |
-
skillName?,
|
| 370 |
-
skillParams?
|
| 371 |
}
|
| 372 |
|
| 373 |
Perbedaan:
|
|
@@ -378,51 +378,51 @@ Perbedaan:
|
|
| 378 |
### PERLU DIBIKIN BARU:
|
| 379 |
|
| 380 |
```typescript
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
```
|
| 386 |
|
| 387 |
---
|
| 388 |
|
| 389 |
-
##
|
| 390 |
|
| 391 |
### Option 1: Reuse Chat Room (Recommended)
|
| 392 |
|
| 393 |
```
|
| 394 |
Rename/Extend existing endpoints:
|
| 395 |
-
/api/v1/chat-rooms
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
```
|
| 400 |
|
| 401 |
### Option 2: Separate Analytics Service
|
| 402 |
|
| 403 |
```
|
| 404 |
Create new service:
|
| 405 |
-
/api/v1/analyses
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
```
|
| 410 |
|
| 411 |
### Option 3: Merge Everything
|
| 412 |
|
| 413 |
```
|
| 414 |
Consolidate interview + analytics + chat into ONE service:
|
| 415 |
-
/api/v1/analysis-sessions
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
```
|
| 420 |
|
| 421 |
**Rekomendasi**: **Option 1** - Extend existing chat-rooms dengan skill metadata
|
| 422 |
|
| 423 |
---
|
| 424 |
|
| 425 |
-
##
|
| 426 |
|
| 427 |
**Existing Room table**:
|
| 428 |
```sql
|
|
@@ -467,7 +467,7 @@ CREATE TABLE analysis_messages (
|
|
| 467 |
|
| 468 |
---
|
| 469 |
|
| 470 |
-
##
|
| 471 |
|
| 472 |
Endpoint existing **TIDAK ADA** untuk:
|
| 473 |
- Tracking skill status (available vs unavailable)
|
|
@@ -499,20 +499,20 @@ export const invokeSkill = (analysisId: string, skillName: string) => {
|
|
| 499 |
|
| 500 |
---
|
| 501 |
|
| 502 |
-
##
|
| 503 |
|
| 504 |
### Existing State:
|
| 505 |
-
|
| 506 |
|
| 507 |
### Untuk Unified Analysis Chat, butuh:
|
| 508 |
-
|
| 509 |
- Sebagian bisa reuse logika existing chat-rooms
|
| 510 |
- Perlu skill management logic baru
|
| 511 |
- Perlu message pagination
|
| 512 |
- Perlu data source aggregation
|
| 513 |
|
| 514 |
### Recommended Approach:
|
| 515 |
-
1. **Extend** existing `/api/v1/chat-rooms`
|
| 516 |
- Add `problem_statement`, `data_sources` fields
|
| 517 |
- Add skill metadata ke messages
|
| 518 |
2. **Create** new endpoints untuk skills
|
|
|
|
| 1 |
+
> Superseded planning note: For the 2026-06-30 dual-backend UX plan, use `frontend-dual-backend-contract.md` as the authoritative frontend contract. This file is retained as historical context from the older endpoint analysis.
|
| 2 |
# Analisis Endpoint API Existing di Frontend
|
| 3 |
|
| 4 |
+
**Tanggal**: 2026-06-15
|
| 5 |
**Purpose**: Memahami endpoint mana yang sudah ada vs yang perlu ditambah untuk fitur Unified Analysis Chat
|
| 6 |
|
| 7 |
---
|
| 8 |
|
| 9 |
+
## Lokasi API Service Files
|
| 10 |
|
| 11 |
```
|
| 12 |
src/services/
|
| 13 |
+
+-- api.ts # Main API service (Orchestration & Agentic)
|
| 14 |
+
+-- interviewApi.ts # Interview-specific API
|
| 15 |
```
|
| 16 |
|
| 17 |
---
|
| 18 |
|
| 19 |
+
## Base URLs yang Digunakan
|
| 20 |
|
| 21 |
```typescript
|
| 22 |
// Dari src/env atau environment variables:
|
| 23 |
|
| 24 |
ORCHESTRATION_API_BASE_URL = "https://api-orchestration.example.com"
|
| 25 |
+
v Untuk: Chat Rooms, Documents, Database Clients, Data Catalog
|
| 26 |
|
| 27 |
+
AGENTIC_API_BASE_URL = "https://api-agentic.example.com"
|
| 28 |
+
v Untuk: Chat Streaming (Analytics/Chat Page)
|
| 29 |
|
| 30 |
INTERVIEW_BASE_URL = "https://api-interview.example.com"
|
| 31 |
+
v Untuk: Interview Page
|
| 32 |
```
|
| 33 |
|
| 34 |
---
|
| 35 |
|
| 36 |
+
## Endpoint Existing - Diorganisir Berdasarkan Feature
|
| 37 |
|
| 38 |
### 1. AUTHENTICATION (src/services/api.ts)
|
| 39 |
|
|
|
|
| 68 |
// Location: src/services/api.ts:140-166
|
| 69 |
export const getRooms = (userId: string): Promise<Room[]> =>
|
| 70 |
request<{ status: string; message: string; data: Room[] | null }>(
|
| 71 |
+
ORCHESTRATION_BASE_URL,
|
| 72 |
`/api/v1/chat-rooms?user_id=${userId}`
|
| 73 |
).then(res => res.data ?? []);
|
| 74 |
|
| 75 |
export const getRoom = (roomId: string): Promise<RoomDetail> =>
|
| 76 |
request<{ status: string; message: string; data: RoomDetail }>(
|
| 77 |
+
ORCHESTRATION_BASE_URL,
|
| 78 |
`/api/v1/chat-rooms/${roomId}`
|
| 79 |
).then(res => res.data);
|
| 80 |
|
|
|
|
| 92 |
);
|
| 93 |
```
|
| 94 |
|
| 95 |
+
** Catatan**: Ini sudah mirip dengan Analysis CRUD yang kita butuh!
|
| 96 |
- Room = Analysis (conversation)
|
| 97 |
- RoomMessage = Message (tapi belum punya skill metadata)
|
| 98 |
|
|
|
|
| 112 |
// Location: src/services/api.ts:170-212
|
| 113 |
export const getDocuments = (userId: string): Promise<ApiDocument[]> =>
|
| 114 |
request<{ status: string; message: string; data: ApiDocument[] | null }>(
|
| 115 |
+
ORCHESTRATION_BASE_URL,
|
| 116 |
`/api/v1/documents/${userId}`
|
| 117 |
).then(res => res.data ?? []);
|
| 118 |
|
|
|
|
| 120 |
const form = new FormData();
|
| 121 |
form.append("user_id", userId);
|
| 122 |
form.append("file", file);
|
| 123 |
+
|
| 124 |
const res = await fetch(`${ORCHESTRATION_BASE_URL}/api/v1/document/upload`, {
|
| 125 |
method: "POST",
|
| 126 |
body: form,
|
|
|
|
| 136 |
);
|
| 137 |
```
|
| 138 |
|
| 139 |
+
** Catatan**: Ini adalah bagian dari "DataSources" yang kita butuh!
|
| 140 |
- Documents = Tipe data source (document type)
|
| 141 |
- Kita perlu: `GET /api/datasources` untuk menampilkan opsi pilihan data source dalam dialog
|
| 142 |
|
|
|
|
| 156 |
// Location: src/services/api.ts:264-295
|
| 157 |
export const getDatabaseClientTypes = (): Promise<DbTypeInfo[]> =>
|
| 158 |
request<{ status: string; message: string; data: DbTypeInfo[] | null }>(
|
| 159 |
+
ORCHESTRATION_BASE_URL,
|
| 160 |
"/api/v1/database-clients/dbtypes"
|
| 161 |
).then(res => res.data ?? []);
|
| 162 |
|
|
|
|
| 167 |
credentials: Record<string, string | number | boolean>
|
| 168 |
): Promise<DatabaseClient> =>
|
| 169 |
request<{ status: string; message: string; data: DatabaseClient }>(
|
| 170 |
+
ORCHESTRATION_BASE_URL,
|
| 171 |
`/api/v1/database-clients`, {
|
| 172 |
method: "POST",
|
| 173 |
body: JSON.stringify({ user_id: userId, name, db_type: dbType, credentials }),
|
|
|
|
| 175 |
|
| 176 |
export const getDatabaseClients = (userId: string): Promise<DatabaseClient[]> =>
|
| 177 |
request<{ status: string; message: string; data: DatabaseClient[] | null }>(
|
| 178 |
+
ORCHESTRATION_BASE_URL,
|
| 179 |
`/api/v1/database-clients/${userId}`
|
| 180 |
).then(res => res.data ?? []);
|
| 181 |
|
| 182 |
export const ingestDatabaseClient = (
|
| 183 |
+
clientId: string,
|
| 184 |
userId: string
|
| 185 |
): Promise<IngestResponse> =>
|
| 186 |
request<{ status: string; message: string; data: IngestResponse }>(
|
|
|
|
| 190 |
).then(res => res.data);
|
| 191 |
```
|
| 192 |
|
| 193 |
+
** Catatan**: Ini juga bagian dari "DataSources"!
|
| 194 |
- Database Clients = Type data source (database type)
|
| 195 |
- Ingest = Process untuk membuat data tersedia
|
| 196 |
|
|
|
|
| 241 |
});
|
| 242 |
```
|
| 243 |
|
| 244 |
+
** Catatan**: Ini endpoint untuk mengirim message ke chat!
|
| 245 |
- Mirip dengan: `POST /api/analyses/:id/messages` yang kita butuh
|
| 246 |
- Tapi ini hanya untuk plain message, belum ada skill invocation metadata
|
| 247 |
|
|
|
|
| 295 |
});
|
| 296 |
```
|
| 297 |
|
| 298 |
+
** Catatan**: Interview adalah fitur TERSENDIRI
|
| 299 |
- Kemungkinan: Kita akan **MERGER** interview + analytics menjadi satu Analysis Chat
|
| 300 |
- Tapi interview API tetap terpisah untuk structured interview flow
|
| 301 |
|
| 302 |
---
|
| 303 |
|
| 304 |
+
## Ringkasan Endpoint Existing
|
| 305 |
|
| 306 |
### Total: 22 Endpoint
|
| 307 |
|
|
|
|
| 309 |
|
| 310 |
| Feature | Count | Status |
|
| 311 |
|---------|-------|--------|
|
| 312 |
+
| Authentication | 1 | [OK] |
|
| 313 |
+
| Chat Rooms (Analysis equivalents) | 5 | [OK] |
|
| 314 |
+
| Documents (Data Sources) | 5 | [OK] |
|
| 315 |
+
| Database Clients (Data Sources) | 4 | [OK] |
|
| 316 |
+
| Data Catalog | 2 | [OK] |
|
| 317 |
+
| Chat Streaming | 1 | [OK] |
|
| 318 |
+
| Interview | 6 | [OK] |
|
| 319 |
|
| 320 |
---
|
| 321 |
|
| 322 |
+
## Endpoint BARU yang Dibutuhkan untuk Unified Analysis Chat
|
| 323 |
|
| 324 |
Dari dokumentasi yang sudah saya buat (`ENDPOINTS.md`), inilah endpoint BARU yang perlu ditambah:
|
| 325 |
|
| 326 |
| Endpoint | Status | Catatan |
|
| 327 |
|----------|--------|---------|
|
| 328 |
+
| `POST /api/analyses` | NEW | Bisa reuse logika `POST /api/v1/chat-rooms` |
|
| 329 |
+
| `GET /api/analyses` | NEW | Bisa reuse logika `GET /api/v1/chat-rooms` |
|
| 330 |
+
| `GET /api/analyses/:id` | NEW | Bisa reuse logika `GET /api/v1/chat-rooms/:id` |
|
| 331 |
+
| `PUT /api/analyses/:id` | NEW | TIDAK ADA yang mirip sekarang |
|
| 332 |
+
| `DELETE /api/analyses/:id` | NEW | Bisa reuse logika `DELETE /api/v1/chat-rooms/:id` |
|
| 333 |
+
| `POST /api/analyses/:id/messages` | NEW | Mirip `POST /api/v1/chat/stream`, tapi dengan skill metadata |
|
| 334 |
+
| `GET /api/analyses/:id/messages` | NEW | GET messages dengan pagination, TIDAK ADA sekarang |
|
| 335 |
+
| `GET /api/skills` | NEW | TIDAK ADA - untuk skill discovery |
|
| 336 |
+
| `GET /api/datasources` | NEW | BISA REUSE dari `/api/v1/documents` + `/api/v1/database-clients` |
|
| 337 |
+
| `GET /api/skills/:skillName` | NEW | Optional - untuk skill details |
|
| 338 |
|
| 339 |
---
|
| 340 |
|
| 341 |
+
## Refresh Mapping: Endpoint Existing -> Reusable untuk Analysis Chat
|
| 342 |
|
| 343 |
### BISA LANGSUNG REUSE:
|
| 344 |
|
| 345 |
```typescript
|
| 346 |
+
// Chat Room CRUD -> Analysis CRUD
|
| 347 |
// Struktur sama, tinggal ubah nama endpoint
|
| 348 |
|
| 349 |
+
[NO] OLD: GET /api/v1/chat-rooms?user_id=X
|
| 350 |
+
[OK] NEW: GET /api/analyses (dengan user diambil dari auth)
|
| 351 |
|
| 352 |
+
[NO] OLD: POST /api/v1/chat-rooms { user_id, title }
|
| 353 |
+
[OK] NEW: POST /api/analyses { title, problemStatement, dataSources }
|
| 354 |
|
| 355 |
+
[NO] OLD: GET /api/v1/chat-rooms/:roomId
|
| 356 |
+
[OK] NEW: GET /api/analyses/:id (ubah response structure untuk support dataSources)
|
| 357 |
|
| 358 |
+
[NO] OLD: DELETE /api/v1/chat-rooms/:roomId?user_id=X
|
| 359 |
+
[OK] NEW: DELETE /api/analyses/:id
|
| 360 |
```
|
| 361 |
|
| 362 |
### PERLU DIMODIFIKASI:
|
| 363 |
|
| 364 |
```typescript
|
| 365 |
+
// Chat Message -> Analysis Message + Skill Metadata
|
| 366 |
+
[NO] OLD: POST /api/v1/chat/stream { user_id, room_id, message }
|
| 367 |
+
[OK] NEW: POST /api/analyses/:id/messages {
|
| 368 |
+
content,
|
| 369 |
+
skillName?,
|
| 370 |
+
skillParams?
|
| 371 |
}
|
| 372 |
|
| 373 |
Perbedaan:
|
|
|
|
| 378 |
### PERLU DIBIKIN BARU:
|
| 379 |
|
| 380 |
```typescript
|
| 381 |
+
[OK] NEW: GET /api/analyses/:id/messages (paginated)
|
| 382 |
+
[OK] NEW: GET /api/skills?analysisId=X (skill discovery dengan status)
|
| 383 |
+
[OK] NEW: GET /api/datasources (list of documents + databases)
|
| 384 |
+
[OK] NEW: PUT /api/analyses/:id (update title, problem, datasources)
|
| 385 |
```
|
| 386 |
|
| 387 |
---
|
| 388 |
|
| 389 |
+
## Implementation Strategy untuk Backend
|
| 390 |
|
| 391 |
### Option 1: Reuse Chat Room (Recommended)
|
| 392 |
|
| 393 |
```
|
| 394 |
Rename/Extend existing endpoints:
|
| 395 |
+
/api/v1/chat-rooms -> /api/analyses
|
| 396 |
+
[OK] Minimal code change
|
| 397 |
+
[OK] Reuse existing DB schema (bisa extend dengan column dataSources)
|
| 398 |
+
[OK] Reuse existing pagination, auth logic
|
| 399 |
```
|
| 400 |
|
| 401 |
### Option 2: Separate Analytics Service
|
| 402 |
|
| 403 |
```
|
| 404 |
Create new service:
|
| 405 |
+
/api/v1/analyses -> Completely new implementation
|
| 406 |
+
[OK] Clean separation of concerns
|
| 407 |
+
[NO] More code duplication
|
| 408 |
+
[NO] Maintain 2 similar services
|
| 409 |
```
|
| 410 |
|
| 411 |
### Option 3: Merge Everything
|
| 412 |
|
| 413 |
```
|
| 414 |
Consolidate interview + analytics + chat into ONE service:
|
| 415 |
+
/api/v1/analysis-sessions -> Unified endpoint
|
| 416 |
+
[OK] Single source of truth
|
| 417 |
+
[NO] Big refactor, breaking changes
|
| 418 |
+
[NO] Remove existing interview API
|
| 419 |
```
|
| 420 |
|
| 421 |
**Rekomendasi**: **Option 1** - Extend existing chat-rooms dengan skill metadata
|
| 422 |
|
| 423 |
---
|
| 424 |
|
| 425 |
+
## Database Schema Changes Needed
|
| 426 |
|
| 427 |
**Existing Room table**:
|
| 428 |
```sql
|
|
|
|
| 467 |
|
| 468 |
---
|
| 469 |
|
| 470 |
+
## Skill Execution Logic Perlu Ditambah
|
| 471 |
|
| 472 |
Endpoint existing **TIDAK ADA** untuk:
|
| 473 |
- Tracking skill status (available vs unavailable)
|
|
|
|
| 499 |
|
| 500 |
---
|
| 501 |
|
| 502 |
+
## Kesimpulan & Next Steps
|
| 503 |
|
| 504 |
### Existing State:
|
| 505 |
+
[OK] Ada 22 endpoint untuk auth, chat rooms, documents, databases, chat streaming, interview
|
| 506 |
|
| 507 |
### Untuk Unified Analysis Chat, butuh:
|
| 508 |
+
Tambah ~9 endpoint baru (atau extend yang existing)
|
| 509 |
- Sebagian bisa reuse logika existing chat-rooms
|
| 510 |
- Perlu skill management logic baru
|
| 511 |
- Perlu message pagination
|
| 512 |
- Perlu data source aggregation
|
| 513 |
|
| 514 |
### Recommended Approach:
|
| 515 |
+
1. **Extend** existing `/api/v1/chat-rooms` -> `/api/analyses`
|
| 516 |
- Add `problem_statement`, `data_sources` fields
|
| 517 |
- Add skill metadata ke messages
|
| 518 |
2. **Create** new endpoints untuk skills
|
src/app/components/KnowledgeManagement.tsx
CHANGED
|
@@ -75,7 +75,7 @@ export default function KnowledgeManagement({
|
|
| 75 |
onClose,
|
| 76 |
variant = "modal",
|
| 77 |
}: KnowledgeManagementProps) {
|
| 78 |
-
//
|
| 79 |
const [docTypes, setDocTypes] = useState<DocTypeInfo[]>([]);
|
| 80 |
const [documents, setDocuments] = useState<ApiDocument[]>([]);
|
| 81 |
const [loadingDocs, setLoadingDocs] = useState(false);
|
|
@@ -85,11 +85,11 @@ export default function KnowledgeManagement({
|
|
| 85 |
const [processing, setProcessing] = useState<string | null>(null);
|
| 86 |
const [deleting, setDeleting] = useState<string | null>(null);
|
| 87 |
|
| 88 |
-
//
|
| 89 |
const [view, setView] = useState<View>("main");
|
| 90 |
const [selectedDbType, setSelectedDbType] = useState<DbType | null>(null);
|
| 91 |
|
| 92 |
-
//
|
| 93 |
const [dbTypeInfos, setDbTypeInfos] = useState<DbTypeInfo[]>([]);
|
| 94 |
const [dbClients, setDbClients] = useState<DatabaseClient[]>([]);
|
| 95 |
const [loadingDbTypes, setLoadingDbTypes] = useState(false);
|
|
@@ -104,11 +104,11 @@ export default function KnowledgeManagement({
|
|
| 104 |
});
|
| 105 |
const pollingTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
|
| 106 |
|
| 107 |
-
//
|
| 108 |
const [catalogSources, setCatalogSources] = useState<DataCatalogSource[]>([]);
|
| 109 |
const [loadingCatalog, setLoadingCatalog] = useState(false);
|
| 110 |
|
| 111 |
-
//
|
| 112 |
const [connectionName, setConnectionName] = useState("");
|
| 113 |
const [dbForm, setDbForm] = useState<Record<string, string | number | boolean>>({});
|
| 114 |
const [connecting, setConnecting] = useState(false);
|
|
@@ -184,7 +184,7 @@ export default function KnowledgeManagement({
|
|
| 184 |
onClose();
|
| 185 |
};
|
| 186 |
|
| 187 |
-
//
|
| 188 |
|
| 189 |
const loadDocuments = async (userId: string) => {
|
| 190 |
setLoadingDocs(true);
|
|
@@ -277,7 +277,7 @@ export default function KnowledgeManagement({
|
|
| 277 |
setDocuments([]);
|
| 278 |
};
|
| 279 |
|
| 280 |
-
//
|
| 281 |
|
| 282 |
const handleDbConnect = async () => {
|
| 283 |
const userId = getUserId();
|
|
@@ -335,7 +335,7 @@ export default function KnowledgeManagement({
|
|
| 335 |
}
|
| 336 |
};
|
| 337 |
|
| 338 |
-
//
|
| 339 |
|
| 340 |
const formatFileSize = (bytes: number) => {
|
| 341 |
if (bytes < 1024) return bytes + " B";
|
|
@@ -419,7 +419,7 @@ export default function KnowledgeManagement({
|
|
| 419 |
|
| 420 |
const isPage = variant === "page";
|
| 421 |
|
| 422 |
-
//
|
| 423 |
|
| 424 |
const selectedDbInfo = dbTypeInfos.find((d) => d.db_type === selectedDbType);
|
| 425 |
|
|
@@ -441,7 +441,7 @@ export default function KnowledgeManagement({
|
|
| 441 |
? () => setView("main")
|
| 442 |
: null;
|
| 443 |
|
| 444 |
-
//
|
| 445 |
|
| 446 |
return (
|
| 447 |
<div className={isPage ? "knowledge-surface h-full min-h-screen bg-slate-50" : "knowledge-surface fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4 backdrop-blur-sm"}>
|
|
@@ -490,7 +490,7 @@ export default function KnowledgeManagement({
|
|
| 490 |
{/* Content */}
|
| 491 |
<div className={isPage ? "flex-1 overflow-y-auto px-6 py-5" : "flex-1 overflow-y-auto px-5 py-4"}>
|
| 492 |
|
| 493 |
-
{/*
|
| 494 |
{view === "main" && (
|
| 495 |
<div className="space-y-4">
|
| 496 |
|
|
@@ -509,7 +509,7 @@ export default function KnowledgeManagement({
|
|
| 509 |
<div className="text-center">
|
| 510 |
<p className="text-sm font-medium text-slate-700">
|
| 511 |
{uploading
|
| 512 |
-
? "
|
| 513 |
: <>Drop files, or <span className="text-[#FF8F00]">browse</span></>
|
| 514 |
}
|
| 515 |
</p>
|
|
@@ -557,7 +557,7 @@ export default function KnowledgeManagement({
|
|
| 557 |
<div>
|
| 558 |
<div className="flex items-center justify-between mb-2">
|
| 559 |
<span className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">
|
| 560 |
-
Databases
|
| 561 |
</span>
|
| 562 |
</div>
|
| 563 |
<div className="space-y-1">
|
|
@@ -575,14 +575,14 @@ export default function KnowledgeManagement({
|
|
| 575 |
</div>
|
| 576 |
<div className="flex-1 min-w-0">
|
| 577 |
<p className="text-sm font-medium text-slate-800 truncate">{client.name}</p>
|
| 578 |
-
<p className="text-xs text-slate-400 capitalize">{client.db_type}
|
| 579 |
</div>
|
| 580 |
<div className="flex items-center gap-1.5 flex-shrink-0">
|
| 581 |
<div className="flex flex-col items-end gap-0.5">
|
| 582 |
<button
|
| 583 |
onClick={() => handleIngest(client.id)}
|
| 584 |
disabled={ingesting === client.id || client.status === "inactive"}
|
| 585 |
-
title={ingestTimestamps[client.id] ? `Last ingested: ${new Date(ingestTimestamps[client.id]).toLocaleString()}` : "Never ingested
|
| 586 |
className={[
|
| 587 |
"flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg transition disabled:opacity-40 disabled:cursor-not-allowed",
|
| 588 |
ingestTimestamps[client.id]
|
|
@@ -595,7 +595,7 @@ export default function KnowledgeManagement({
|
|
| 595 |
) : (
|
| 596 |
<Database className="w-3 h-3" />
|
| 597 |
)}
|
| 598 |
-
{ingesting === client.id ? "
|
| 599 |
</button>
|
| 600 |
{ingestTimestamps[client.id] && (
|
| 601 |
<span className="text-[10px] text-slate-300 leading-none">
|
|
@@ -676,7 +676,7 @@ export default function KnowledgeManagement({
|
|
| 676 |
{doc.filename}
|
| 677 |
</p>
|
| 678 |
<p className="text-xs text-slate-400">
|
| 679 |
-
{formatFileSize(doc.file_size)}
|
| 680 |
</p>
|
| 681 |
</div>
|
| 682 |
<div className="flex flex-shrink-0 items-center gap-1.5 sm:gap-2">
|
|
@@ -702,7 +702,7 @@ export default function KnowledgeManagement({
|
|
| 702 |
</div>
|
| 703 |
)}
|
| 704 |
|
| 705 |
-
{/*
|
| 706 |
{view === "db-select" && (
|
| 707 |
<div className="space-y-4">
|
| 708 |
<p className="text-sm text-slate-400">
|
|
@@ -760,7 +760,7 @@ export default function KnowledgeManagement({
|
|
| 760 |
</div>
|
| 761 |
)}
|
| 762 |
|
| 763 |
-
{/*
|
| 764 |
{view === "db-credentials" && (
|
| 765 |
<div className="space-y-4">
|
| 766 |
|
|
@@ -859,7 +859,7 @@ export default function KnowledgeManagement({
|
|
| 859 |
className="w-full flex items-center justify-center gap-2 bg-[#FF8F00] hover:bg-[#FF6F00] active:bg-[#E65100] text-white py-2.5 rounded-xl text-sm font-medium transition disabled:opacity-40 disabled:cursor-not-allowed"
|
| 860 |
>
|
| 861 |
{connecting ? (
|
| 862 |
-
<><Loader2 className="w-4 h-4 animate-spin" />
|
| 863 |
) : (
|
| 864 |
"Connect"
|
| 865 |
)}
|
|
@@ -867,7 +867,7 @@ export default function KnowledgeManagement({
|
|
| 867 |
</div>
|
| 868 |
)}
|
| 869 |
|
| 870 |
-
{/*
|
| 871 |
{view === "catalog" && (
|
| 872 |
<div className="space-y-3">
|
| 873 |
<button
|
|
@@ -897,7 +897,7 @@ export default function KnowledgeManagement({
|
|
| 897 |
<p className="text-sm font-medium text-slate-800 truncate">{src.name}</p>
|
| 898 |
<p className="text-xs text-slate-400">
|
| 899 |
{src.source_type === "schema" ? "Database" : "Tabular"}
|
| 900 |
-
{src.table_count != null ? `
|
| 901 |
</p>
|
| 902 |
</div>
|
| 903 |
</div>
|
|
|
|
| 75 |
onClose,
|
| 76 |
variant = "modal",
|
| 77 |
}: KnowledgeManagementProps) {
|
| 78 |
+
// -- Document state ----------------------------------------------------------
|
| 79 |
const [docTypes, setDocTypes] = useState<DocTypeInfo[]>([]);
|
| 80 |
const [documents, setDocuments] = useState<ApiDocument[]>([]);
|
| 81 |
const [loadingDocs, setLoadingDocs] = useState(false);
|
|
|
|
| 85 |
const [processing, setProcessing] = useState<string | null>(null);
|
| 86 |
const [deleting, setDeleting] = useState<string | null>(null);
|
| 87 |
|
| 88 |
+
// -- Navigation state --------------------------------------------------------
|
| 89 |
const [view, setView] = useState<View>("main");
|
| 90 |
const [selectedDbType, setSelectedDbType] = useState<DbType | null>(null);
|
| 91 |
|
| 92 |
+
// -- DB type & client state --------------------------------------------------
|
| 93 |
const [dbTypeInfos, setDbTypeInfos] = useState<DbTypeInfo[]>([]);
|
| 94 |
const [dbClients, setDbClients] = useState<DatabaseClient[]>([]);
|
| 95 |
const [loadingDbTypes, setLoadingDbTypes] = useState(false);
|
|
|
|
| 104 |
});
|
| 105 |
const pollingTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
|
| 106 |
|
| 107 |
+
// -- Data Catalog state ------------------------------------------------------
|
| 108 |
const [catalogSources, setCatalogSources] = useState<DataCatalogSource[]>([]);
|
| 109 |
const [loadingCatalog, setLoadingCatalog] = useState(false);
|
| 110 |
|
| 111 |
+
// -- DB credentials form state -----------------------------------------------
|
| 112 |
const [connectionName, setConnectionName] = useState("");
|
| 113 |
const [dbForm, setDbForm] = useState<Record<string, string | number | boolean>>({});
|
| 114 |
const [connecting, setConnecting] = useState(false);
|
|
|
|
| 184 |
onClose();
|
| 185 |
};
|
| 186 |
|
| 187 |
+
// -- Document handlers -------------------------------------------------------
|
| 188 |
|
| 189 |
const loadDocuments = async (userId: string) => {
|
| 190 |
setLoadingDocs(true);
|
|
|
|
| 277 |
setDocuments([]);
|
| 278 |
};
|
| 279 |
|
| 280 |
+
// -- DB handlers -------------------------------------------------------------
|
| 281 |
|
| 282 |
const handleDbConnect = async () => {
|
| 283 |
const userId = getUserId();
|
|
|
|
| 335 |
}
|
| 336 |
};
|
| 337 |
|
| 338 |
+
// -- Helpers -----------------------------------------------------------------
|
| 339 |
|
| 340 |
const formatFileSize = (bytes: number) => {
|
| 341 |
if (bytes < 1024) return bytes + " B";
|
|
|
|
| 419 |
|
| 420 |
const isPage = variant === "page";
|
| 421 |
|
| 422 |
+
// -- Header title & back button logic ----------------------------------------
|
| 423 |
|
| 424 |
const selectedDbInfo = dbTypeInfos.find((d) => d.db_type === selectedDbType);
|
| 425 |
|
|
|
|
| 441 |
? () => setView("main")
|
| 442 |
: null;
|
| 443 |
|
| 444 |
+
// -- Render -------------------------------------------------------------------
|
| 445 |
|
| 446 |
return (
|
| 447 |
<div className={isPage ? "knowledge-surface h-full min-h-screen bg-slate-50" : "knowledge-surface fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4 backdrop-blur-sm"}>
|
|
|
|
| 490 |
{/* Content */}
|
| 491 |
<div className={isPage ? "flex-1 overflow-y-auto px-6 py-5" : "flex-1 overflow-y-auto px-5 py-4"}>
|
| 492 |
|
| 493 |
+
{/* -- VIEW: main -- */}
|
| 494 |
{view === "main" && (
|
| 495 |
<div className="space-y-4">
|
| 496 |
|
|
|
|
| 509 |
<div className="text-center">
|
| 510 |
<p className="text-sm font-medium text-slate-700">
|
| 511 |
{uploading
|
| 512 |
+
? "Uploading..."
|
| 513 |
: <>Drop files, or <span className="text-[#FF8F00]">browse</span></>
|
| 514 |
}
|
| 515 |
</p>
|
|
|
|
| 557 |
<div>
|
| 558 |
<div className="flex items-center justify-between mb-2">
|
| 559 |
<span className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">
|
| 560 |
+
Databases - {dbClients.length}
|
| 561 |
</span>
|
| 562 |
</div>
|
| 563 |
<div className="space-y-1">
|
|
|
|
| 575 |
</div>
|
| 576 |
<div className="flex-1 min-w-0">
|
| 577 |
<p className="text-sm font-medium text-slate-800 truncate">{client.name}</p>
|
| 578 |
+
<p className="text-xs text-slate-400 capitalize">{client.db_type} - {client.status}</p>
|
| 579 |
</div>
|
| 580 |
<div className="flex items-center gap-1.5 flex-shrink-0">
|
| 581 |
<div className="flex flex-col items-end gap-0.5">
|
| 582 |
<button
|
| 583 |
onClick={() => handleIngest(client.id)}
|
| 584 |
disabled={ingesting === client.id || client.status === "inactive"}
|
| 585 |
+
title={ingestTimestamps[client.id] ? `Last ingested: ${new Date(ingestTimestamps[client.id]).toLocaleString()}` : "Never ingested -- click to ingest schema"}
|
| 586 |
className={[
|
| 587 |
"flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg transition disabled:opacity-40 disabled:cursor-not-allowed",
|
| 588 |
ingestTimestamps[client.id]
|
|
|
|
| 595 |
) : (
|
| 596 |
<Database className="w-3 h-3" />
|
| 597 |
)}
|
| 598 |
+
{ingesting === client.id ? "Ingesting..." : "Ingest"}
|
| 599 |
</button>
|
| 600 |
{ingestTimestamps[client.id] && (
|
| 601 |
<span className="text-[10px] text-slate-300 leading-none">
|
|
|
|
| 676 |
{doc.filename}
|
| 677 |
</p>
|
| 678 |
<p className="text-xs text-slate-400">
|
| 679 |
+
{formatFileSize(doc.file_size)} - {formatDate(doc.created_at)}
|
| 680 |
</p>
|
| 681 |
</div>
|
| 682 |
<div className="flex flex-shrink-0 items-center gap-1.5 sm:gap-2">
|
|
|
|
| 702 |
</div>
|
| 703 |
)}
|
| 704 |
|
| 705 |
+
{/* -- VIEW: db-select -- */}
|
| 706 |
{view === "db-select" && (
|
| 707 |
<div className="space-y-4">
|
| 708 |
<p className="text-sm text-slate-400">
|
|
|
|
| 760 |
</div>
|
| 761 |
)}
|
| 762 |
|
| 763 |
+
{/* -- VIEW: db-credentials -- */}
|
| 764 |
{view === "db-credentials" && (
|
| 765 |
<div className="space-y-4">
|
| 766 |
|
|
|
|
| 859 |
className="w-full flex items-center justify-center gap-2 bg-[#FF8F00] hover:bg-[#FF6F00] active:bg-[#E65100] text-white py-2.5 rounded-xl text-sm font-medium transition disabled:opacity-40 disabled:cursor-not-allowed"
|
| 860 |
>
|
| 861 |
{connecting ? (
|
| 862 |
+
<><Loader2 className="w-4 h-4 animate-spin" /> Connecting...</>
|
| 863 |
) : (
|
| 864 |
"Connect"
|
| 865 |
)}
|
|
|
|
| 867 |
</div>
|
| 868 |
)}
|
| 869 |
|
| 870 |
+
{/* -- VIEW: catalog -- */}
|
| 871 |
{view === "catalog" && (
|
| 872 |
<div className="space-y-3">
|
| 873 |
<button
|
|
|
|
| 897 |
<p className="text-sm font-medium text-slate-800 truncate">{src.name}</p>
|
| 898 |
<p className="text-xs text-slate-400">
|
| 899 |
{src.source_type === "schema" ? "Database" : "Tabular"}
|
| 900 |
+
{src.table_count != null ? ` - ${src.table_count} table${src.table_count !== 1 ? "s" : ""}` : ""}
|
| 901 |
</p>
|
| 902 |
</div>
|
| 903 |
</div>
|
src/app/components/analysis/AnalysisShell.tsx
CHANGED
|
@@ -43,8 +43,9 @@ const wait = (ms: number) => new Promise((resolve) => window.setTimeout(resolve,
|
|
| 43 |
|
| 44 |
function normalizeAssistantContent(content: string) {
|
| 45 |
return content
|
| 46 |
-
.replace(/
|
| 47 |
-
.replace(/\*\*
|
|
|
|
| 48 |
.trim();
|
| 49 |
}
|
| 50 |
|
|
@@ -61,6 +62,7 @@ export function AnalysisShell() {
|
|
| 61 |
const [loadingAnalyses, setLoadingAnalyses] = useState(true);
|
| 62 |
const [loadingMessages, setLoadingMessages] = useState(false);
|
| 63 |
const [streamState, setStreamState] = useState<StreamState>("idle");
|
|
|
|
| 64 |
const [newAnalysisOpen, setNewAnalysisOpen] = useState(false);
|
| 65 |
const [reportCollapsed, setReportCollapsed] = useState(false);
|
| 66 |
const [staleSources, setStaleSources] = useState<DataBindItem[]>([]);
|
|
@@ -189,6 +191,7 @@ export function AnalysisShell() {
|
|
| 189 |
},
|
| 190 |
]);
|
| 191 |
|
|
|
|
| 192 |
setStreamState(kind === "chat" ? "streaming-chat" : "streaming-help");
|
| 193 |
try {
|
| 194 |
const onEvent = (event: AgentStreamEvent) => {
|
|
@@ -221,6 +224,8 @@ export function AnalysisShell() {
|
|
| 221 |
|
| 222 |
const completedContent = normalizeAssistantContent(finalContent);
|
| 223 |
let traceTargetId = aiId;
|
|
|
|
|
|
|
| 224 |
setMessages((prev) =>
|
| 225 |
prev.map((msg) =>
|
| 226 |
msg.id === aiId
|
|
@@ -232,16 +237,18 @@ export function AnalysisShell() {
|
|
| 232 |
if (completedContent) {
|
| 233 |
const saved = await createAnalysisMessage(activeAnalysis.id, { role: "ai", content: completedContent, message_id: doneMessageId });
|
| 234 |
traceTargetId = saved.id;
|
|
|
|
|
|
|
| 235 |
setMessages((prev) =>
|
| 236 |
prev.map((msg) =>
|
| 237 |
msg.id === aiId
|
| 238 |
-
? { ...
|
| 239 |
: msg
|
| 240 |
)
|
| 241 |
);
|
| 242 |
}
|
| 243 |
|
| 244 |
-
await fetchTraceability(activeAnalysis.id,
|
| 245 |
} catch (err) {
|
| 246 |
const message = err instanceof Error ? err.message : "Agent stream failed";
|
| 247 |
setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, status: "error", statusText: message, traceabilityLoading: false } : msg)));
|
|
@@ -327,7 +334,7 @@ export function AnalysisShell() {
|
|
| 327 |
{loadingMessages ? (
|
| 328 |
<div className="flex h-full items-center justify-center text-sm text-slate-500">Loading messages</div>
|
| 329 |
) : (
|
| 330 |
-
<MessageList messages={messages} />
|
| 331 |
)}
|
| 332 |
</div>
|
| 333 |
<ChatInput disabled={!activeAnalysis} streaming={streamState !== "idle"} onSend={handleSend} onHelp={handleHelp} />
|
|
|
|
| 43 |
|
| 44 |
function normalizeAssistantContent(content: string) {
|
| 45 |
return content
|
| 46 |
+
.replace(/([A-Za-z0-9])(?=\*\*)/g, "$1 ")
|
| 47 |
+
.replace(/(\*\*[^*\n]+\*\*)(?=[A-Za-z0-9])/g, "$1 ")
|
| 48 |
+
.replace(/\*\*([^*\n]*?)\*\*/g, (_, inner) => `**${inner.trim()}**`)
|
| 49 |
.trim();
|
| 50 |
}
|
| 51 |
|
|
|
|
| 62 |
const [loadingAnalyses, setLoadingAnalyses] = useState(true);
|
| 63 |
const [loadingMessages, setLoadingMessages] = useState(false);
|
| 64 |
const [streamState, setStreamState] = useState<StreamState>("idle");
|
| 65 |
+
const [scrollTo, setScrollTo] = useState<"bottom" | "last-ai" | null>(null);
|
| 66 |
const [newAnalysisOpen, setNewAnalysisOpen] = useState(false);
|
| 67 |
const [reportCollapsed, setReportCollapsed] = useState(false);
|
| 68 |
const [staleSources, setStaleSources] = useState<DataBindItem[]>([]);
|
|
|
|
| 191 |
},
|
| 192 |
]);
|
| 193 |
|
| 194 |
+
setScrollTo("bottom");
|
| 195 |
setStreamState(kind === "chat" ? "streaming-chat" : "streaming-help");
|
| 196 |
try {
|
| 197 |
const onEvent = (event: AgentStreamEvent) => {
|
|
|
|
| 224 |
|
| 225 |
const completedContent = normalizeAssistantContent(finalContent);
|
| 226 |
let traceTargetId = aiId;
|
| 227 |
+
let resolvedMessageId = doneMessageId;
|
| 228 |
+
setScrollTo("last-ai");
|
| 229 |
setMessages((prev) =>
|
| 230 |
prev.map((msg) =>
|
| 231 |
msg.id === aiId
|
|
|
|
| 237 |
if (completedContent) {
|
| 238 |
const saved = await createAnalysisMessage(activeAnalysis.id, { role: "ai", content: completedContent, message_id: doneMessageId });
|
| 239 |
traceTargetId = saved.id;
|
| 240 |
+
const savedUi = toUiMessage(saved);
|
| 241 |
+
resolvedMessageId = savedUi.messageId ?? doneMessageId;
|
| 242 |
setMessages((prev) =>
|
| 243 |
prev.map((msg) =>
|
| 244 |
msg.id === aiId
|
| 245 |
+
? { ...savedUi, sources: msg.sources, traceabilityLoading: Boolean(savedUi.messageId) }
|
| 246 |
: msg
|
| 247 |
)
|
| 248 |
);
|
| 249 |
}
|
| 250 |
|
| 251 |
+
await fetchTraceability(activeAnalysis.id, resolvedMessageId, traceTargetId);
|
| 252 |
} catch (err) {
|
| 253 |
const message = err instanceof Error ? err.message : "Agent stream failed";
|
| 254 |
setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, status: "error", statusText: message, traceabilityLoading: false } : msg)));
|
|
|
|
| 334 |
{loadingMessages ? (
|
| 335 |
<div className="flex h-full items-center justify-center text-sm text-slate-500">Loading messages</div>
|
| 336 |
) : (
|
| 337 |
+
<MessageList messages={messages} scrollTo={scrollTo} onScrollDone={() => setScrollTo(null)} />
|
| 338 |
)}
|
| 339 |
</div>
|
| 340 |
<ChatInput disabled={!activeAnalysis} streaming={streamState !== "idle"} onSend={handleSend} onHelp={handleHelp} />
|
src/app/components/analysis/MarkdownContent.tsx
CHANGED
|
@@ -6,10 +6,9 @@ import rehypeKatex from "rehype-katex";
|
|
| 6 |
|
| 7 |
function repairMarkdownMarkers(line: string) {
|
| 8 |
return line
|
| 9 |
-
.replace(/\*\*\s+([^*\n]*?\S)\s*\*\*/g, "**$1**")
|
| 10 |
-
.replace(/\*\*([^*\n]*?\S)\s+\*\*/g, "**$1**")
|
| 11 |
.replace(/([A-Za-z0-9])(?=\*\*)/g, "$1 ")
|
| 12 |
-
.replace(/(\*\*[^*]+\*\*)(?=[A-Za-z0-9])/g, "$1 ")
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
function normalizeReadableMarkdown(content: string) {
|
|
@@ -50,14 +49,14 @@ function normalizeReadableMarkdown(content: string) {
|
|
| 50 |
}
|
| 51 |
|
| 52 |
const markdownComponents: Components = {
|
| 53 |
-
p: ({ children }) => <p className="my-
|
| 54 |
-
ul: ({ children }) => <ul className="my-
|
| 55 |
-
ol: ({ children }) => <ol className="my-
|
| 56 |
-
li: ({ children }) => <li className="pl-1 leading-
|
| 57 |
strong: ({ children }) => <strong className="font-semibold text-slate-950">{children}</strong>,
|
| 58 |
-
h1: ({ children }) => <h1 className="mb-
|
| 59 |
-
h2: ({ children }) => <h2 className="mb-
|
| 60 |
-
h3: ({ children }) => <h3 className="mb-
|
| 61 |
a: ({ children, href }) => (
|
| 62 |
<a href={href} className="font-medium text-emerald-700 underline decoration-emerald-200 underline-offset-4" target="_blank" rel="noreferrer">
|
| 63 |
{children}
|
|
|
|
| 6 |
|
| 7 |
function repairMarkdownMarkers(line: string) {
|
| 8 |
return line
|
|
|
|
|
|
|
| 9 |
.replace(/([A-Za-z0-9])(?=\*\*)/g, "$1 ")
|
| 10 |
+
.replace(/(\*\*[^*\n]+\*\*)(?=[A-Za-z0-9])/g, "$1 ")
|
| 11 |
+
.replace(/\*\*([^*\n]*?)\*\*/g, (_, inner) => `**${inner.trim()}**`);
|
| 12 |
}
|
| 13 |
|
| 14 |
function normalizeReadableMarkdown(content: string) {
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
const markdownComponents: Components = {
|
| 52 |
+
p: ({ children }) => <p className="my-1.5 leading-6 text-slate-800">{children}</p>,
|
| 53 |
+
ul: ({ children }) => <ul className="my-1.5 list-disc space-y-0.5 pl-5">{children}</ul>,
|
| 54 |
+
ol: ({ children }) => <ol className="my-1.5 list-decimal space-y-0.5 pl-5">{children}</ol>,
|
| 55 |
+
li: ({ children }) => <li className="pl-1 leading-6 text-slate-800">{children}</li>,
|
| 56 |
strong: ({ children }) => <strong className="font-semibold text-slate-950">{children}</strong>,
|
| 57 |
+
h1: ({ children }) => <h1 className="mb-2 mt-4 text-xl font-semibold leading-6 text-slate-950">{children}</h1>,
|
| 58 |
+
h2: ({ children }) => <h2 className="mb-2 mt-3 text-lg font-semibold leading-6 text-slate-950">{children}</h2>,
|
| 59 |
+
h3: ({ children }) => <h3 className="mb-1.5 mt-3 text-base font-semibold leading-6 text-slate-950">{children}</h3>,
|
| 60 |
a: ({ children, href }) => (
|
| 61 |
<a href={href} className="font-medium text-emerald-700 underline decoration-emerald-200 underline-offset-4" target="_blank" rel="noreferrer">
|
| 62 |
{children}
|
src/app/components/analysis/MessageList.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import { Bot, User } from "lucide-react";
|
| 2 |
import type { UiMessage } from "./types";
|
| 3 |
import { MarkdownContent } from "./MarkdownContent";
|
|
@@ -5,7 +6,28 @@ import { MessageTraceability } from "./MessageTraceability";
|
|
| 5 |
import { EmptyState } from "./AnalysisStates";
|
| 6 |
import { cx, formatDateTime } from "./utils";
|
| 7 |
|
| 8 |
-
export function MessageList({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
if (messages.length === 0) {
|
| 10 |
return (
|
| 11 |
<div className="flex h-full items-center justify-center p-6">
|
|
@@ -14,12 +36,15 @@ export function MessageList({ messages }: { messages: UiMessage[] }) {
|
|
| 14 |
);
|
| 15 |
}
|
| 16 |
|
|
|
|
|
|
|
| 17 |
return (
|
| 18 |
-
<div className="mx-auto flex w-full max-w-5xl flex-col gap-5 px-4 py-6">
|
| 19 |
-
{messages.map((message) => {
|
| 20 |
const isUser = message.role === "user";
|
|
|
|
| 21 |
return (
|
| 22 |
-
<article key={message.id} className={cx("flex min-w-0 gap-3", isUser && "justify-end")}>
|
| 23 |
{!isUser && (
|
| 24 |
<div className="mt-1 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-slate-900 text-white">
|
| 25 |
<Bot className="h-4 w-4" />
|
|
|
|
| 1 |
+
import { useEffect, useRef } from "react";
|
| 2 |
import { Bot, User } from "lucide-react";
|
| 3 |
import type { UiMessage } from "./types";
|
| 4 |
import { MarkdownContent } from "./MarkdownContent";
|
|
|
|
| 6 |
import { EmptyState } from "./AnalysisStates";
|
| 7 |
import { cx, formatDateTime } from "./utils";
|
| 8 |
|
| 9 |
+
export function MessageList({
|
| 10 |
+
messages,
|
| 11 |
+
scrollTo,
|
| 12 |
+
onScrollDone,
|
| 13 |
+
}: {
|
| 14 |
+
messages: UiMessage[];
|
| 15 |
+
scrollTo?: "bottom" | "last-ai" | null;
|
| 16 |
+
onScrollDone?: () => void;
|
| 17 |
+
}) {
|
| 18 |
+
const containerRef = useRef<HTMLDivElement>(null);
|
| 19 |
+
const lastAiRef = useRef<HTMLElement>(null);
|
| 20 |
+
|
| 21 |
+
useEffect(() => {
|
| 22 |
+
if (!scrollTo) return;
|
| 23 |
+
if (scrollTo === "bottom") {
|
| 24 |
+
containerRef.current?.scrollIntoView({ block: "end", behavior: "smooth" });
|
| 25 |
+
} else if (scrollTo === "last-ai" && lastAiRef.current) {
|
| 26 |
+
lastAiRef.current.scrollIntoView({ block: "start", behavior: "smooth" });
|
| 27 |
+
}
|
| 28 |
+
onScrollDone?.();
|
| 29 |
+
}, [scrollTo]);
|
| 30 |
+
|
| 31 |
if (messages.length === 0) {
|
| 32 |
return (
|
| 33 |
<div className="flex h-full items-center justify-center p-6">
|
|
|
|
| 36 |
);
|
| 37 |
}
|
| 38 |
|
| 39 |
+
const lastAiIndex = messages.reduce((last, msg, i) => (msg.role === "ai" ? i : last), -1);
|
| 40 |
+
|
| 41 |
return (
|
| 42 |
+
<div ref={containerRef} className="mx-auto flex w-full max-w-5xl flex-col gap-5 px-4 py-6">
|
| 43 |
+
{messages.map((message, index) => {
|
| 44 |
const isUser = message.role === "user";
|
| 45 |
+
const isLastAi = index === lastAiIndex;
|
| 46 |
return (
|
| 47 |
+
<article ref={isLastAi ? lastAiRef : null} key={message.id} className={cx("flex min-w-0 gap-3", isUser && "justify-end")}>
|
| 48 |
{!isUser && (
|
| 49 |
<div className="mt-1 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-slate-900 text-white">
|
| 50 |
<Bot className="h-4 w-4" />
|