chore: merge upstream with local feature additions

- Merged dexscreener connector, composio connectors, crypto realtime tools from upstream
- Kept local additions: dropbox/onedrive connectors, memory routes, model_list routes, RefreshToken model
- Resolved frontend conflicts: kept tool UIs from both sides
- Accepted upstream lock files (uv.lock, pnpm-lock.yaml)
This commit is contained in:
Vonic 2026-04-13 23:31:52 +07:00
commit 6e86cd7e8a
803 changed files with 152168 additions and 14005 deletions

View file

@ -0,0 +1,229 @@
# Story 1.0: Hệ thống Xác thực (Authentication System)
Status: ready-for-dev
## Story
**Là một** SurfSense user,
**Tôi muốn** đăng nhập vào extension với tài khoản SurfSense của tôi,
**Để** extension có thể đồng bộ settings, lịch sử chat, và truy cập backend APIs.
## Acceptance Criteria
### AC 1.0.1: User Login Flow
- **Given** user chưa đăng nhập vào extension
- **When** user click nút "Login" trong side panel header
- **Then** Chrome Identity API popup mở ra với tùy chọn Google OAuth
- **And** user hoàn tất quy trình OAuth
- **Then** extension nhận JWT token từ backend
- **And** extension chuyển hướng về side panel
- **And** avatar/email của user hiển thị trong header
**Error Scenario:**
- **Given** user đang trong quy trình OAuth
- **When** OAuth thất bại (user hủy hoặc lỗi mạng)
- **Then** extension hiển thị error toast "Login failed. Please try again."
- **And** user vẫn ở trạng thái chưa xác thực
### AC 1.0.2: JWT Token Management
- **Given** backend trả về JWT token (hết hạn sau 24 giờ - theo config hiện tại)
- **When** extension nhận được token
- **Then** extension lưu encrypted JWT trong Plasmo Storage
- **And** thời gian hết hạn của token được lưu
**Auto-Refresh:**
- **Given** JWT token còn < 1 giờ hết hạn
- **When** extension kiểm tra token expiry (mỗi 30 phút)
- **Then** extension gọi API `/auth/jwt/refresh`
- **And** extension cập nhật token đã lưu
**Logout:**
- **Given** user click "Logout" trong settings dropdown
- **When** hành động logout được kích hoạt
- **Then** extension xóa JWT khỏi Plasmo Storage
- **And** user chuyển hướng về màn hình welcome/login
### AC 1.0.3: Authenticated API Requests
- **Given** user đã đăng nhập (JWT đã lưu)
- **When** extension thực hiện API request
- **Then** request bao gồm header `Authorization: Bearer {JWT}`
- **And** backend xác thực JWT
- **And** request thành công với status 200
**Expired Token:**
- **Given** JWT token đã hết hạn
- **When** extension thực hiện API request
- **Then** backend trả về lỗi 401 Unauthorized
- **And** extension cố gắng auto-refresh
- **And** nếu refresh thành công, thử lại request ban đầu
- **And** nếu refresh thất bại, chuyển hướng user đến trang login
### AC 1.0.4: Offline Handling
- **Given** user đã đăng nhập trước đó
- **And** user mất kết nối internet
- **When** extension cố gắng kết nối backend
- **Then** extension hiển thị chỉ báo "Offline" trong header
- **And** extension cache trạng thái auth gần nhất
## Tasks / Subtasks
- [ ] Task 1: Chrome Identity API Integration (AC: 1.0.1)
- [ ] 1.1 Tạo `lib/auth/chrome-identity.ts` - wrapper cho Chrome Identity API
- [ ] 1.2 Implement `launchWebAuthFlow` với backend OAuth URL
- [ ] 1.3 Handle OAuth callback và extract JWT từ redirect URL
- [ ] 1.4 Xử lý các error cases (user cancel, network error)
- [ ] Task 2: JWT Token Manager (AC: 1.0.2)
- [ ] 2.1 Tạo `lib/auth/jwt-manager.ts` - quản lý JWT storage
- [ ] 2.2 Implement token encryption/decryption với Plasmo Storage
- [ ] 2.3 Implement token expiry checking và auto-refresh logic
- [ ] 2.4 Implement logout và clear token
- [ ] Task 3: Authenticated API Client (AC: 1.0.3)
- [ ] 3.1 Tạo `lib/auth/api-client.ts` - HTTP client với auth headers
- [ ] 3.2 Implement request interceptor để inject Bearer token
- [ ] 3.3 Implement 401 response handler với auto-retry
- [ ] 3.4 Implement offline detection và caching
- [ ] Task 4: Auth UI Components (AC: 1.0.1, 1.0.4)
- [ ] 4.1 Tạo `sidepanel/auth/LoginButton.tsx` - nút đăng nhập
- [ ] 4.2 Tạo `sidepanel/auth/UserProfile.tsx` - hiển thị avatar/email
- [ ] 4.3 Tạo `sidepanel/auth/AuthProvider.tsx` - React context cho auth state
- [ ] 4.4 Update `sidepanel/chat/ChatHeader.tsx` để integrate auth UI
- [ ] Task 5: Integration Testing
- [ ] 5.1 Test OAuth flow end-to-end
- [ ] 5.2 Test token refresh mechanism
- [ ] 5.3 Test offline mode behavior
- [ ] 5.4 Test logout flow
## Dev Notes
### Backend Authentication (ALREADY EXISTS)
Backend đã có đầy đủ authentication system sử dụng `fastapi-users`:
**Existing Endpoints:**
```
POST /auth/jwt/login - Email/password login
POST /auth/jwt/logout - Logout
GET /auth/google/authorize - Google OAuth initiation
GET /auth/google/callback - Google OAuth callback
POST /auth/register - User registration
GET /verify-token - Verify JWT validity
GET /users/me - Get current user info
```
**JWT Configuration (từ `surfsense_backend/app/users.py`):**
- Secret: `config.SECRET_KEY`
- Lifetime: 24 giờ (`3600 * 24` seconds)
- Transport: Bearer token
- OAuth redirect: `{NEXT_FRONTEND_URL}/auth/callback?token={token}`
### Extension Architecture Pattern
**Plasmo Storage (đã có trong project):**
```typescript
import { Storage } from "@plasmohq/storage";
const storage = new Storage({ area: "local" });
```
**Chrome Identity API:**
```typescript
chrome.identity.launchWebAuthFlow({
url: `${BACKEND_URL}/auth/google/authorize`,
interactive: true,
}, (redirectUrl) => {
// Extract JWT from redirect URL
const url = new URL(redirectUrl);
const token = url.searchParams.get('token');
});
```
### Critical Implementation Details
**1. OAuth Flow cho Extension:**
- Backend hiện redirect về `{NEXT_FRONTEND_URL}/auth/callback?token={token}`
- Extension cần sử dụng Chrome Identity API với custom redirect
- Có thể cần thêm endpoint mới hoặc config cho extension redirect
**2. Token Storage Security:**
- KHÔNG lưu plaintext JWT
- Sử dụng encryption trước khi lưu vào Plasmo Storage
- Xem xét sử dụng `chrome.storage.session` cho sensitive data
**3. CORS Configuration:**
Backend đã có CORS cho localhost, cần thêm extension origin:
```python
# surfsense_backend/app/app.py - line 74-81
allowed_origins.extend([
"chrome-extension://*", # Cần thêm
])
```
### Project Structure Notes
**Files cần tạo mới:**
```
surfsense_browser_extension/
├── lib/
│ └── auth/
│ ├── chrome-identity.ts # Chrome Identity API wrapper
│ ├── jwt-manager.ts # JWT storage & refresh
│ └── api-client.ts # Authenticated HTTP client
└── sidepanel/
└── auth/
├── LoginButton.tsx # Login UI component
├── UserProfile.tsx # User avatar/menu
└── AuthProvider.tsx # Auth context provider
```
**Files cần modify:**
- `sidepanel/chat/ChatHeader.tsx` - Thêm auth UI
- `sidepanel/index.tsx` - Wrap với AuthProvider
- `background/index.ts` - Handle auth messages (nếu cần)
### Dependencies
**Existing (không cần cài thêm):**
- `@plasmohq/storage` - Đã có
- `react`, `react-dom` - Đã có
- `lucide-react` - Đã có (cho icons)
**Backend Dependencies (đã có):**
- `fastapi-users` - Authentication framework
- `httpx-oauth` - Google OAuth client
- `python-jose` - JWT handling
### Security Considerations
1. **KHÔNG** lưu API keys trong extension code
2. Mã hóa JWT trước khi lưu vào storage
3. Sử dụng HTTPS cho tất cả API calls
4. Validate JWT signature trên backend (đã có)
5. Implement CSRF protection cho OAuth flow
### References
- [Source: surfsense_backend/app/users.py] - JWT strategy, OAuth config
- [Source: surfsense_backend/app/app.py#L91-160] - Auth routes registration
- [Source: _bmad-epics/epic-1-ai-powered-crypto-assistant.md#Story-1.0] - Full requirements
- [Source: _bmad-output/architecture-extension.md] - Extension architecture
- [Source: _bmad-output/architecture-backend.md] - Backend auth flow
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Completion Notes List
- Story created: 2026-02-04
- Backend auth system already exists and is fully functional
- Extension needs new auth layer to integrate with existing backend
- P0 BLOCKER - This story blocks all sync features (Settings, Chat, Capture)
### Debug Log References
(To be filled during implementation)
### File List
(To be filled during implementation)

View file

@ -0,0 +1,328 @@
# Story 1.6: Đồng bộ Cài đặt (Settings Sync) với Frontend
Status: ready-for-dev
## Story
**Là một** SurfSense user,
**Tôi muốn** extension sử dụng cùng model và search space như web dashboard,
**Để** tôi không phải cấu hình lại.
## Dependencies
- **REQUIRES:** Story 1.0 (Authentication System) - Must be completed first
- Extension must have valid JWT token to call backend APIs
## Acceptance Criteria
### AC 1.6.1: Hiển thị Dropdown Cài đặt
- **Given** user đã đăng nhập
- **When** user click icon ⚙️ trong header
- **Then** settings dropdown mở ra
- **And** dropdown hiển thị:
- Current model: "GPT-4 Turbo" (chỉ xem, bị mờ)
- Current search space: "Crypto Research" (chỉ xem, bị mờ)
- Links đến web dashboard:
- "🔗 Manage Connectors"
- "💬 View All Chats"
- "⚙️ Full Settings"
- Nút "🚪 Logout"
### AC 1.6.2: Đồng bộ Cài đặt khi Đăng nhập
- **Given** user hoàn tất đăng nhập
- **When** nhận được JWT token
- **Then** extension gọi `GET /api/v1/searchspaces` để lấy danh sách search spaces
- **And** extension gọi `GET /api/v1/search-spaces/{id}/llm-preferences` để lấy LLM config
- **And** settings được lưu trong Plasmo Storage
- **And** settings hiển thị trong dropdown
**Response Format (từ backend):**
```json
// GET /api/v1/searchspaces
[
{
"id": 1,
"name": "Crypto Research",
"description": "...",
"agent_llm_id": 0,
"document_summary_llm_id": 0
}
]
// GET /api/v1/search-spaces/{id}/llm-preferences
{
"agent_llm_id": 0,
"document_summary_llm_id": 0,
"agent_llm": {
"id": 0,
"name": "Auto (Load Balanced)",
"provider": "AUTO",
"model_name": "auto"
}
}
```
### AC 1.6.3: Tự động cập nhật Cài đặt
- **Given** user thay đổi model trên web dashboard
- **When** extension phát hiện thay đổi (qua polling)
- **Then** extension lấy settings đã cập nhật
- **And** dropdown phản ánh model mới
- **And** các cuộc chat tiếp theo sử dụng model mới
**Polling:**
- **Given** extension đang hoạt động
- **When** mỗi 5 phút
- **Then** extension polls `GET /api/v1/searchspaces` và LLM preferences
### AC 1.6.4: Search Space Selector
- **Given** user có nhiều search spaces
- **When** user click vào search space selector trong header
- **Then** dropdown hiển thị tất cả search spaces của user
- **And** user có thể chọn search space khác
- **And** extension lưu selection vào Plasmo Storage
- **And** các API calls tiếp theo sử dụng search_space_id mới
### AC 1.6.5: Offline Handling
- **Given** user đã đăng nhập và có settings đã cache
- **When** user mất kết nối internet
- **Then** extension sử dụng settings đã cache
- **And** hiển thị indicator "Using cached settings"
- **When** kết nối được khôi phục
- **Then** extension sync settings mới từ backend
## Tasks / Subtasks
- [ ] Task 1: Settings Service (AC: 1.6.2, 1.6.3)
- [ ] 1.1 Tạo `lib/settings/settings-service.ts` - API calls cho settings
- [ ] 1.2 Implement `fetchSearchSpaces()` - GET /api/v1/searchspaces
- [ ] 1.3 Implement `fetchLLMPreferences(spaceId)` - GET LLM config
- [ ] 1.4 Implement polling mechanism (5 phút interval)
- [ ] 1.5 Implement settings caching trong Plasmo Storage
- [ ] Task 2: Settings State Management (AC: 1.6.1, 1.6.4)
- [ ] 2.1 Tạo `lib/settings/settings-store.ts` - Zustand/Context store
- [ ] 2.2 Define settings types và interfaces
- [ ] 2.3 Implement search space selection logic
- [ ] 2.4 Implement settings sync on login
- [ ] Task 3: Settings UI Components (AC: 1.6.1, 1.6.4)
- [ ] 3.1 Update `sidepanel/chat/ChatHeader.tsx` - Integrate real settings
- [ ] 3.2 Tạo `sidepanel/settings/SettingsDropdown.tsx` - Enhanced dropdown
- [ ] 3.3 Tạo `sidepanel/settings/SearchSpaceSelector.tsx` - Space picker
- [ ] 3.4 Tạo `sidepanel/settings/ModelDisplay.tsx` - Read-only model info
- [ ] Task 4: Integration với Auth (AC: 1.6.2, 1.6.5)
- [ ] 4.1 Hook settings fetch vào auth flow (sau login thành công)
- [ ] 4.2 Implement offline detection và fallback
- [ ] 4.3 Clear settings on logout
- [ ] 4.4 Handle 401 errors (redirect to login)
- [ ] Task 5: Testing
- [ ] 5.1 Test settings sync sau login
- [ ] 5.2 Test polling mechanism
- [ ] 5.3 Test search space switching
- [ ] 5.4 Test offline mode với cached settings
## Dev Notes
### Backend APIs (ALREADY EXISTS)
Backend đã có đầy đủ APIs cho settings:
**Search Spaces:**
```
GET /api/v1/searchspaces - List all user's search spaces
POST /api/v1/searchspaces - Create new search space
GET /api/v1/searchspaces/{id} - Get single search space
PUT /api/v1/searchspaces/{id} - Update search space
DELETE /api/v1/searchspaces/{id} - Delete search space
```
**LLM Preferences:**
```
GET /api/v1/search-spaces/{id}/llm-preferences - Get LLM config for space
PUT /api/v1/search-spaces/{id}/llm-preferences - Update LLM config
```
**Global LLM Configs:**
```
GET /api/v1/global-new-llm-configs - List available LLM models
```
### Existing Extension Code
**ChatHeader.tsx (đã có UI cơ bản):**
- Search space selector dropdown (hardcoded data)
- Settings dropdown với menu items
- User avatar với logout
- Token search bar
**Cần update:**
- Replace hardcoded search spaces với real data từ API
- Add model display trong settings dropdown
- Connect logout button với auth service
### Data Types
**SearchSpace (từ backend):**
```typescript
interface SearchSpace {
id: number;
name: string;
description?: string;
user_id: string;
agent_llm_id: number;
document_summary_llm_id: number;
created_at: string;
}
```
**LLMPreferences (từ backend):**
```typescript
interface LLMPreferences {
agent_llm_id: number;
document_summary_llm_id: number;
agent_llm?: LLMConfig;
document_summary_llm?: LLMConfig;
}
interface LLMConfig {
id: number;
name: string;
provider: string;
model_name: string;
is_global?: boolean;
is_auto_mode?: boolean;
}
```
### Project Structure Notes
**Files cần tạo mới:**
```
surfsense_browser_extension/
├── lib/
│ └── settings/
│ ├── settings-service.ts # API calls
│ ├── settings-store.ts # State management
│ └── types.ts # TypeScript interfaces
└── sidepanel/
└── settings/
├── SettingsDropdown.tsx # Enhanced dropdown
├── SearchSpaceSelector.tsx # Space picker
└── ModelDisplay.tsx # Read-only model info
```
**Files cần modify:**
- `sidepanel/chat/ChatHeader.tsx` - Integrate real settings data
- `sidepanel/index.tsx` - Add SettingsProvider
- `lib/auth/api-client.ts` - Add settings endpoints (từ Story 1.0)
### Implementation Pattern
**Settings Service:**
```typescript
// lib/settings/settings-service.ts
import { apiClient } from '../auth/api-client';
export const settingsService = {
async fetchSearchSpaces(): Promise<SearchSpace[]> {
return apiClient.get('/api/v1/searchspaces');
},
async fetchLLMPreferences(spaceId: number): Promise<LLMPreferences> {
return apiClient.get(`/api/v1/search-spaces/${spaceId}/llm-preferences`);
},
async fetchGlobalLLMConfigs(): Promise<LLMConfig[]> {
return apiClient.get('/api/v1/global-new-llm-configs');
}
};
```
**Settings Store (Plasmo Storage):**
```typescript
// lib/settings/settings-store.ts
import { Storage } from "@plasmohq/storage";
const storage = new Storage({ area: "local" });
export const settingsStore = {
async saveSearchSpaces(spaces: SearchSpace[]) {
await storage.set('searchSpaces', spaces);
},
async getSearchSpaces(): Promise<SearchSpace[] | null> {
return storage.get('searchSpaces');
},
async saveSelectedSpaceId(id: number) {
await storage.set('selectedSearchSpaceId', id);
},
async getSelectedSpaceId(): Promise<number | null> {
return storage.get('selectedSearchSpaceId');
}
};
```
### Polling Implementation
```typescript
// Polling every 5 minutes
const POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
useEffect(() => {
const pollSettings = async () => {
try {
const spaces = await settingsService.fetchSearchSpaces();
await settingsStore.saveSearchSpaces(spaces);
const selectedId = await settingsStore.getSelectedSpaceId();
if (selectedId) {
const prefs = await settingsService.fetchLLMPreferences(selectedId);
await settingsStore.saveLLMPreferences(prefs);
}
} catch (error) {
console.error('Settings poll failed:', error);
}
};
const interval = setInterval(pollSettings, POLLING_INTERVAL);
return () => clearInterval(interval);
}, []);
```
### Security Considerations
1. Settings API calls require valid JWT (handled by api-client)
2. Cache settings locally for offline access
3. Clear cached settings on logout
4. Handle 401 errors gracefully (redirect to login)
### References
- [Source: surfsense_backend/app/routes/search_spaces_routes.py] - Search space APIs
- [Source: surfsense_backend/app/routes/new_llm_config_routes.py] - LLM config APIs
- [Source: surfsense_browser_extension/sidepanel/chat/ChatHeader.tsx] - Existing UI
- [Source: _bmad-epics/epic-1-ai-powered-crypto-assistant.md#Story-1.6] - Full requirements
- [Source: _bmad-output/implementation-artifacts/1-0-authentication-system.md] - Auth dependency
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Completion Notes List
- Story created: 2026-02-04
- Backend APIs already exist and are fully functional
- Extension UI partially exists (ChatHeader.tsx has basic structure)
- DEPENDS ON Story 1.0 (Authentication) - must complete auth first
- Settings are read-only in extension (changes made via web dashboard)
### Debug Log References
(To be filled during implementation)
### File List
(To be filled during implementation)