feat(history): establish Telegram SaaS platform foundation

This commit is contained in:
wmq
2026-07-27 23:03:00 +08:00
parent cf0eed46c3
commit cd06d049fe
92 changed files with 5608 additions and 3749 deletions
+5 -6
View File
@@ -1,5 +1,5 @@
import { http } from './client'
import type { AiAnalysis, AiReplySuggestion, AiTextActionPayload, InterpretLine, TranslatePayload, TranslateResult } from '@/types'
import type { AiAnalysis, AiReplySuggestion, AiTextActionPayload, TranslatePayload, TranslateResult } from '@/types'
/** POST /api/ai/translate */
export function translate(payload: TranslatePayload) {
@@ -21,12 +21,11 @@ export function summarizeConversation(conversationId: string) {
return http.post<{ summary: string }>('/api/ai/summarize', { conversationId })
}
/** POST /api/ai/interpretation */
export function nextInterpretLine(index: number) {
return http.post<InterpretLine>('/api/ai/interpretation', { index })
}
/** POST /api/ai/text-action(润色/缩写/扩写/语气/翻译) */
export function textAction(payload: AiTextActionPayload) {
return http.post<{ result: string }>('/api/ai/text-action', payload)
}
export function recordAiAdoption(conversationId: string, content: string, suggestionId = '') {
return http.post<{ id: string }>('/api/ai/adoptions', { conversationId, content, suggestionId })
}
+32 -3
View File
@@ -16,7 +16,36 @@ export function me() {
return http.get<{ user: AuthSession['user']; tenant: AuthSession['tenant'] }>('/api/auth/me')
}
/** 发送验证码(演示:固定可收到 6 位码) */
export function sendCode(account: string) {
return http.post<{ sent: boolean }>('/api/auth/send-code', { account }).catch(() => ({ sent: true }))
export function getWebSocketTicket() {
return http.post<{ ticket: string; expiresAt: string }>('/api/auth/ws-ticket', {})
}
export function changePassword(payload: { currentPassword: string; newPassword: string }) {
return http.post<{ success: boolean; loginRequired: boolean }>('/api/auth/change-password', payload)
}
export function revokeAllSessions() {
return http.post<{ success: boolean }>('/api/auth/logout-all', {})
}
export interface ApiKeyRecord {
id: string
name: string
prefix: string
role: string
createdAt: string
revoked: boolean
key?: string
}
export function getApiKeys() {
return http.get<ApiKeyRecord[]>('/api/api-keys')
}
export function createApiKey(name: string, role = 'service') {
return http.post<ApiKeyRecord>('/api/api-keys', { name, role })
}
export function revokeApiKey(id: string) {
return http.del<{ success: boolean }>(`/api/api-keys/${id}`)
}
+16
View File
@@ -17,6 +17,22 @@ export function connectChannel(payload: {
return http.post<ChannelConnection>('/api/channels/connect', payload)
}
export function startTelegramLogin(phone: string) {
return http.post<ChannelConnection>('/api/channels/telegram/start', { phone })
}
export function submitTelegramCode(channelId: string, code: string) {
return http.post<{ status: string; channel: ChannelConnection }>(`/api/channels/telegram/${channelId}/code`, { code })
}
export function submitTelegramPassword(channelId: string, password: string) {
return http.post<{ status: string; channel: ChannelConnection }>(`/api/channels/telegram/${channelId}/password`, { password })
}
export function syncTelegramChannel(channelId: string) {
return http.post<{ chats: number; messages: number }>(`/api/channels/${channelId}/sync`)
}
/** POST /api/channels/test */
export function testChannel(id: string) {
return http.post<{ success: boolean; latencyMs: number; message: string }>('/api/channels/test', { id })
+4 -11
View File
@@ -1,10 +1,5 @@
/**
* 统一 API 客户端
* - VITE_USE_MOCK=true :走内置 Mock 路由(本地模拟数据,含模拟网络延迟)
* - VITE_USE_MOCK=false :请求真实后端(VITE_API_BASE_URL),自动携带 Bearer Token
*/
/** 灵犀跨境统一 API 客户端:所有请求均访问真实后端。 */
import { env } from '@/config/env'
import { mockRequest } from '@/services/mock'
export class ApiError extends Error {
code: number
@@ -29,10 +24,6 @@ export function setTokenGetter(fn: () => string | null) {
export async function api<T = unknown>(path: string, options: RequestOptions = {}): Promise<T> {
const method = options.method ?? 'GET'
if (env.useMock) {
return mockRequest<T>(method, path, options.query ?? {}, options.body)
}
const base = env.apiBaseUrl.replace(/\/$/, '')
const url = new URL(`${base}${path}`, window.location.origin)
Object.entries(options.query ?? {}).forEach(([k, v]) => {
@@ -54,7 +45,9 @@ export async function api<T = unknown>(path: string, options: RequestOptions = {
}
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new ApiError(res.status, text || `请求失败(${res.status})`)
let message = text
try { message = (JSON.parse(text) as { message?: string }).message ?? text } catch { /* 保留原始响应 */ }
throw new ApiError(res.status, message || `请求失败(${res.status})`)
}
const json = (await res.json().catch(() => null)) as { code?: number; message?: string; data?: T } | T | null
// 兼容 {code,message,data} 与直接返回数据两种后端风格
+29 -1
View File
@@ -25,11 +25,39 @@ export function getMessages(id: string) {
return http.get<Message[]>(`/api/conversations/${id}/messages`)
}
export function updateMessage(id: string, messageId: string, payload: { isImportant?: boolean; internalNote?: string }) {
return http.patch<Message>(`/api/conversations/${id}/messages/${messageId}`, payload)
}
export function markConversationRead(id: string, messageIds?: string[]) {
return http.post<Conversation>(`/api/conversations/${id}/read`, { messageIds })
}
export function getMessageAttachment(id: string, messageId: string) {
return http.get<{ dataUrl: string; mimeType: string; name: string }>(
`/api/conversations/${id}/messages/${messageId}/attachment`,
)
}
/** POST /api/conversations/:id/messages */
export function sendMessage(id: string, payload: SendMessagePayload & { translatedText?: string }) {
export function sendMessage(id: string, payload: SendMessagePayload & { translatedText?: string; autoTranslate?: boolean }) {
return http.post<Message>(`/api/conversations/${id}/messages`, payload)
}
export function sendMedia(
id: string,
payload: { name: string; mimeType: string; base64: string; kind: 'document' | 'photo' | 'audio' | 'voice' | 'sticker' },
) {
return http.post<unknown>(`/api/conversations/${id}/media`, payload)
}
export function sendAlbum(
id: string,
files: Array<{ name: string; mimeType: string; base64: string }>,
) {
return http.post<unknown>(`/api/conversations/${id}/album`, { files })
}
/** PATCH /api/conversations/:id(标记已读/分配/状态) */
export function updateConversation(id: string, payload: { assigneeId?: string | null; markRead?: boolean; status?: string }) {
return http.patch<Conversation>(`/api/conversations/${id}`, payload)
+1 -1
View File
@@ -22,7 +22,7 @@ export function deleteKnowledge(id: string) {
}
/** POST /api/knowledge/upload */
export function uploadKnowledgeFiles(files: { name: string; type: string }[]) {
export function uploadKnowledgeFiles(files: { name: string; type: string; base64: string }[]) {
return http.post<{ items: KnowledgeItem[] }>('/api/knowledge/upload', { files })
}
+1 -1
View File
@@ -51,7 +51,7 @@ export function getBilling() {
/** POST /api/billing/upgrade */
export function upgradePlan(planKey: string) {
return http.post<BillingInfo>('/api/billing/upgrade', { planKey })
return http.post<{ checkoutUrl: string; sessionId: string }>('/api/billing/upgrade', { planKey })
}
/* ===== 设置 ===== */