diff --git a/src/api/agents.ts b/src/api/agents.ts index fc278b0..ff3bfa5 100644 --- a/src/api/agents.ts +++ b/src/api/agents.ts @@ -54,9 +54,10 @@ export interface Agent { } export const AgentAPI = { - list: () => api.get('/agents').then((r) => r.data), + list: (phone: string) => api.post('/agents', { phone }).then((r) => r.data), + mine: (phone: string) => api.post('/agents/mine', { phone }).then((r) => r.data), detail: (id: string) => api.get(`/agents/${id}`).then((r) => r.data), - create: (payload: Partial) => api.post('/agents', payload).then((r) => r.data), + create: (payload: Partial) => api.post('/agents/create', payload).then((r) => r.data), update: (id: string, payload: Partial) => api.put(`/agents/${id}`, payload).then((r) => r.data), remove: (id: string) => api.delete(`/agents/${id}`).then((r) => r.data), diff --git a/src/api/auth.ts b/src/api/auth.ts index 1345712..c279887 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -3,28 +3,28 @@ import { api, API_BASE_URL } from './http'; export interface AuthUser { id: string; - email: string; + phone: string; name: string; role: 'admin' | 'user'; + token: string; } export const AuthAPI = { me: () => api.get('/auth/me').then((r) => r.data), - verify: async (email: string, password: string) => { + verify: async (phone: string, password: string) => { try { - const res = await axios.post(`${API_BASE_URL}/urser`, { email, password }, { timeout: 3000 }); + const res = await axios.post(`${API_BASE_URL}/urser`, { phone, password }, { timeout: 3000 }); return res.data; } catch (e) { console.warn('Backend /urser not available, fallback to mock true', e); return true; } }, - login: (email: string, password: string) => api.post('/auth/login', { email, password }).then((r) => r.data), - register: (payload: { email: string; password: string; name: string; inviteCode?: string }) => + login: (phone: string, password: string) => api.post('/auth/login', { phone, password }).then((r) => r.data), + register: (payload: { phone: string; password: string; name: string; inviteCode?: string }) => api.post('/auth/register', payload).then((r) => r.data), logout: () => api.post('/auth/logout').then((r) => r.data), listInvites: () => api.get('/auth/invites').then((r) => r.data), - createInvite: (payload: { email?: string; teamId?: string; role?: string; ttlHours?: number }) => api.post('/auth/invites', payload).then((r) => r.data), + createInvite: (payload: { phone?: string; teamId?: string; role?: string; ttlHours?: number }) => api.post('/auth/invites', payload).then((r) => r.data), deleteInvite: (code: string) => api.delete(`/auth/invites/${code}`).then((r) => r.data) }; - diff --git a/src/api/http.ts b/src/api/http.ts index cde3297..541e123 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -1,10 +1,10 @@ import axios from 'axios'; +import { clearUserStorage } from '../utils/storage'; -export const API_BASE_URL = 'https://tianchaoai.cc/aura/v1'; +export const API_BASE_URL = import.meta.env.DEV ? '/api' : 'https://tianchaoai.cc/aura/v1'; const APP_BASE = (import.meta.env.BASE_URL || '/').replace(/\/$/, ''); export const withAppBase = (path: string) => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`; export const withApiBase = (path: string) => `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; -const isMockAuth = () => typeof localStorage !== 'undefined' && localStorage.getItem('mock-auth') === '1'; export const api = axios.create({ baseURL: API_BASE_URL, @@ -12,15 +12,36 @@ export const api = axios.create({ withCredentials: true }); +// 请求拦截器 +api.interceptors.request.use((config) => { + const token = localStorage.getItem('aura-token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + api.interceptors.response.use( (r) => r, (err) => { const isLoginPage = location.pathname === '/login' || location.pathname === withAppBase('/login'); - if (err?.response?.status === 401 && !isLoginPage && !isMockAuth()) { - const next = encodeURIComponent(location.pathname + location.search); + const isAuthPath = err.config?.url?.includes('/auth/login') || err.config?.url?.includes('/auth/register'); + + if (err?.response?.status === 401 && !isLoginPage && !isAuthPath) { + clearUserStorage(); + + let nextPath = location.pathname; + let nextSearch = location.search; + + // 如果是聊天页面,只进入 /chat 列表页,不进入具体的智能体或会话,防止切换账号后的权限冲突 + if (nextPath.includes('/chat')) { + nextPath = '/chat'; + nextSearch = ''; + } + + const next = encodeURIComponent(nextPath + nextSearch); location.href = `${withAppBase('/login')}?next=${next}`; } return Promise.reject(err); } ); - diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index c5594e1..d2f9abf 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -118,7 +118,7 @@ export default function Sidebar({ onOpenPalette, onNavigate }: Props) { items: [ { key: 'name', - label: {user.email}, + label: {user.phone}, disabled: true }, { type: 'divider' }, diff --git a/src/pages/AgentList/AgentListLogic.ts b/src/pages/AgentList/AgentListLogic.ts index 0712b72..be1b861 100644 --- a/src/pages/AgentList/AgentListLogic.ts +++ b/src/pages/AgentList/AgentListLogic.ts @@ -1,14 +1,17 @@ import { useEffect, useMemo, useState } from 'react'; import { Agent, AgentAPI } from '../../api'; +import { useAuth } from '../../store/auth'; export function useAgentListLogic() { + const { user } = useAuth(); const [list, setList] = useState([]); const [loading, setLoading] = useState(false); const load = async () => { + if (!user?.phone) return; setLoading(true); try { - setList(await AgentAPI.list()); + setList(await AgentAPI.mine(user.phone)); } finally { setLoading(false); } @@ -16,7 +19,7 @@ export function useAgentListLogic() { useEffect(() => { load(); - }, []); + }, [user?.phone]); const handleDelete = async (id: string) => { await AgentAPI.remove(id); diff --git a/src/pages/AgentList/components/AgentListWeb.css b/src/pages/AgentList/components/AgentListWeb.css new file mode 100644 index 0000000..ff54dba --- /dev/null +++ b/src/pages/AgentList/components/AgentListWeb.css @@ -0,0 +1,197 @@ +.agent-list-header { + border-radius: 24px; + padding: 30px 30px 26px; + background: linear-gradient(135deg, rgba(255,255,255,0.98) 0%, rgba(236,253,245,0.92) 48%, rgba(239,246,255,0.96) 100%); + border: 1px solid rgba(8, 145, 178, 0.12); + box-shadow: 0 20px 48px rgba(15, 23, 42, 0.06); + margin-bottom: 24px; +} + +.agent-list-header-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 20px; + flex-wrap: wrap; + margin-bottom: 22px; +} + +.agent-list-intro { + max-width: 620px; +} + +.agent-list-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 999px; + background: rgba(255,255,255,0.78); + border: 1px solid rgba(8, 145, 178, 0.10); + color: var(--color-text-secondary); + font-size: 12px; + font-weight: 600; + margin-bottom: 16px; +} + +.agent-list-badge-icon { + color: var(--color-brand); +} + +.agent-list-stats-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; +} + +.agent-list-stat-card { + border-radius: 18px; + padding: 16px 18px; + background: rgba(255,255,255,0.72); + border: 1px solid rgba(255,255,255,0.7); +} + +.agent-list-stat-label { + font-size: 12.5px; + color: var(--color-text-secondary); + margin-bottom: 10px; +} + +.agent-list-stat-content { + display: flex; + align-items: baseline; + gap: 8px; +} + +.agent-list-stat-value { + font-size: 28px; + font-weight: 700; + color: var(--color-text); +} + +.agent-list-stat-badge { + border-radius: 999px; + padding: 4px 8px; + font-size: 12px; + font-weight: 600; +} + +.agent-card { + border-radius: 20px; + padding: 20px; + min-height: 292px; + background: linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(252,252,253,1) 100%); + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.045); + display: flex; + flex-direction: column; +} + +.agent-card-header { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.agent-card-avatar { + border-radius: 50%; + overflow: hidden; + width: 54px; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + font-weight: 700; + color: #fff; +} + +.agent-card-title-group { + flex: 1; + min-width: 0; +} + +.agent-card-name { + font-weight: 700; + font-size: 17px; + color: var(--color-text); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.agent-card-update-time { + font-size: 12.5px; + color: var(--color-text-tertiary); +} + +.agent-card-desc-container { + margin-top: 16px; + padding: 16px 16px 18px; + border-radius: 16px; + background: linear-gradient(180deg, rgba(248,250,252,0.9) 0%, rgba(255,255,255,0.95) 100%); + border: 1px solid rgba(148, 163, 184, 0.14); +} + +.agent-card-desc { + min-height: 66px; + font-size: 13.5px; + line-height: 1.7; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.agent-card-tags { + margin-top: 14px; +} + +/* 修复模型标签上下不居中问题 */ +.agent-card-tag-model { + display: inline-flex !important; + align-items: center !important; + vertical-align: middle; +} + +.agent-card-tag-model-text { + display: inline-block; + max-width: 190px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.2; /* 确保文字行高不影响居中 */ +} + +.agent-card-actions { + display: flex; + gap: 8px; + margin-top: auto; + padding-top: 16px; + border-top: 1px solid var(--color-border); +} + +.agent-list-banner { + margin-top: 24px; + border-radius: 20px; + padding: 18px 20px; + background: var(--color-surface); + border: 1px solid var(--color-border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} + +.agent-list-banner-title { + font-size: 15px; + font-weight: 600; + color: var(--color-text); + margin-bottom: 4px; +} + +.agent-list-banner-desc { + font-size: 13px; + color: var(--color-text-secondary); +} diff --git a/src/pages/AgentList/components/AgentListWeb.tsx b/src/pages/AgentList/components/AgentListWeb.tsx index ef8439d..48108d9 100644 --- a/src/pages/AgentList/components/AgentListWeb.tsx +++ b/src/pages/AgentList/components/AgentListWeb.tsx @@ -9,8 +9,8 @@ import { import { Button, Col, Row, Empty, Popconfirm, App as AntApp, Tag, Space } from 'antd'; import { Link, useNavigate } from 'react-router-dom'; import dayjs from 'dayjs'; -import type { Agent } from '../../../api'; import type { AgentListLogicOutput } from '../AgentListLogic'; +import './AgentListWeb.css'; interface Props { logic: AgentListLogicOutput; @@ -23,44 +23,11 @@ export default function AgentListWeb({ logic }: Props) { return (
-
-
-
-
- +
+
+
+
+ 我的 Agent 资产
@@ -82,28 +49,17 @@ export default function AgentListWeb({ logic }: Props) {
-
+
{stats.map((item) => ( -
-
{item.label}
-
- {item.value} +
+
{item.label}
+
+ {item.value} 实时统计 @@ -126,20 +82,11 @@ export default function AgentListWeb({ logic }: Props) { {list.map((a) => ( -
-
+
+
{isImageUrl(a.avatar) ? ( avatar @@ -147,29 +94,21 @@ export default function AgentListWeb({ logic }: Props) { (a.name?.charAt(0) || '?').toUpperCase() )}
-
-
{a.name}
-
+
+
{a.name}
+
最近更新于 {dayjs(a.updated_at).format('YYYY-MM-DD')}
-
-
+
+
{a.description || '还没有填写描述,可以补充这个智能体适合解决什么问题。'}
- + {a.visibility === 'public' && ( 公开 @@ -186,8 +125,12 @@ export default function AgentListWeb({ logic }: Props) { )} {getModelLabel(a.model) && ( - - + + {getModelLabel(a.model)} @@ -199,7 +142,7 @@ export default function AgentListWeb({ logic }: Props) { )} -
+