Compare commits

...

2 Commits

2 changed files with 84 additions and 23 deletions

View File

@@ -13,11 +13,44 @@ function createClient(): AxiosInstance {
instance.interceptors.request.use((request) => { instance.interceptors.request.use((request) => {
const { apiKey } = getRuntimeConfig() const { apiKey } = getRuntimeConfig()
const requestId = crypto.randomUUID()
if (apiKey) request.headers['x-api-key'] = apiKey if (apiKey) request.headers['x-api-key'] = apiKey
request.headers['x-request-id'] = crypto.randomUUID() request.headers['x-request-id'] = requestId
request.headers['x-client-name'] = 'fabric-frontend'
console.info('[api:request]', {
method: request.method,
url: `${request.baseURL ?? ''}${request.url ?? ''}`,
requestId,
})
return request return request
}) })
instance.interceptors.response.use(
(response) => {
const requestId = response.headers['x-request-id'] ?? response.config.headers['x-request-id']
console.info('[api:response]', {
method: response.config.method,
url: `${response.config.baseURL ?? ''}${response.config.url ?? ''}`,
status: response.status,
requestId,
})
return response
},
(error) => {
const response = error?.response
const config = error?.config ?? {}
const requestId = response?.headers?.['x-request-id'] ?? config?.headers?.['x-request-id']
console.error('[api:error]', {
method: config.method,
url: `${config.baseURL ?? ''}${config.url ?? ''}`,
status: response?.status,
requestId,
})
return Promise.reject(error)
},
)
return instance return instance
} }

View File

@@ -26,6 +26,8 @@ export default function ChatPage() {
const [socketState, setSocketState] = useState<'offline' | 'online'>('offline') const [socketState, setSocketState] = useState<'offline' | 'online'>('offline')
const [onlineCount, setOnlineCount] = useState(0) const [onlineCount, setOnlineCount] = useState(0)
const [typingUsers, setTypingUsers] = useState<string[]>([]) const [typingUsers, setTypingUsers] = useState<string[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [seqFrom, setSeqFrom] = useState('1') const [seqFrom, setSeqFrom] = useState('1')
const [seqTo, setSeqTo] = useState('999999') const [seqTo, setSeqTo] = useState('999999')
const [limit, setLimit] = useState('50') const [limit, setLimit] = useState('50')
@@ -86,40 +88,63 @@ export default function ChatPage() {
async function pullMessages() { async function pullMessages() {
if (!channelId) return if (!channelId) return
const res = await getApiClient().get(`/channels/${channelId}/messages`, { setLoading(true)
params: { setError('')
seq_from: Number(seqFrom || '1'), try {
seq_to: Number(seqTo || '999999'), const res = await getApiClient().get(`/channels/${channelId}/messages`, {
limit: Number(limit || '50'), params: {
}, seq_from: Number(seqFrom || '1'),
}) seq_to: Number(seqTo || '999999'),
setMessages(res.data.items ?? []) limit: Number(limit || '50'),
setPageInfo(res.data.page ?? null) },
})
setMessages(res.data.items ?? [])
setPageInfo(res.data.page ?? null)
} catch {
setError('消息拉取失败')
} finally {
setLoading(false)
}
} }
async function sendMessage() { async function sendMessage() {
if (!channelId || !content.trim()) return if (!channelId || !content.trim()) return
await getApiClient().post(`/channels/${channelId}/messages`, { setError('')
content, try {
authorUserId: 'frontend-user', await getApiClient().post(`/channels/${channelId}/messages`, {
}) content,
socket.emit('typing.stop', { channelId }) authorUserId: 'frontend-user',
setContent('') })
socket.emit('typing.stop', { channelId })
setContent('')
} catch {
setError('发送失败')
}
} }
async function editMessage() { async function editMessage() {
if (!channelId || !editingMessageId || !editingContent.trim()) return if (!channelId || !editingMessageId || !editingContent.trim()) return
await getApiClient().patch(`/channels/${channelId}/messages/${editingMessageId}`, { setError('')
content: editingContent, try {
}) await getApiClient().patch(`/channels/${channelId}/messages/${editingMessageId}`, {
setEditingContent('') content: editingContent,
await pullMessages() })
setEditingContent('')
await pullMessages()
} catch {
setError('编辑失败')
}
} }
async function deleteMessage(messageId: string) { async function deleteMessage(messageId: string) {
if (!channelId || !messageId) return if (!channelId || !messageId) return
await getApiClient().delete(`/channels/${channelId}/messages/${messageId}`) setError('')
await pullMessages() try {
await getApiClient().delete(`/channels/${channelId}/messages/${messageId}`)
await pullMessages()
} catch {
setError('删除失败')
}
} }
function onChangeChannel(value: string) { function onChangeChannel(value: string) {
@@ -155,6 +180,8 @@ export default function ChatPage() {
<p>Guild: {guildId || '-'}</p> <p>Guild: {guildId || '-'}</p>
<p>Socket: {socketState}</p> <p>Socket: {socketState}</p>
<p>线: {onlineCount}</p> <p>线: {onlineCount}</p>
{loading ? <p>...</p> : null}
{error ? <p style={{ color: 'crimson' }}>{error}</p> : null}
{typingUsers.length ? <p>: {typingUsers.join(', ')}</p> : null} {typingUsers.length ? <p>: {typingUsers.join(', ')}</p> : null}
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}> <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={channelId} onChange={(e) => onChangeChannel(e.target.value)} placeholder="Channel ID" /> <input value={channelId} onChange={(e) => onChangeChannel(e.target.value)} placeholder="Channel ID" />
@@ -186,6 +213,7 @@ export default function ChatPage() {
</li> </li>
))} ))}
</ul> </ul>
{!loading && !messages.length ? <p></p> : null}
{pageInfo ? ( {pageInfo ? (
<p> <p>
next_expected_seq: {pageInfo.nextExpectedSeq ?? '-'} | highest_committed_seq:{' '} next_expected_seq: {pageInfo.nextExpectedSeq ?? '-'} | highest_committed_seq:{' '}