mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(ui): URL-encode conversationId in path segments
When a webchat visitorId + sessionId pair exceeds the conversation_id
column width, WebChatController#deriveConversationId folds the variable
part into a SHA-256 hash prefixed with `#`:
webchat:<key8>:#<sha256[0..40]>
That `#` is the URL fragment delimiter. Every URL the admin console
builds by interpolating the conversationId into a path — message list,
status, rename, pin, model, delete, goals/by-conversation, chat/stop,
chat/pending-approvals — gets truncated at the `#` before reaching the
server. Symptom: opening one of these conversations in the console
surfaces as 405 (GET landing on @DeleteMapping("/{conversationId}"))
and 403 (owner check on the truncated id).
Add an `encId` helper (encodeURIComponent) and apply it to every
conversationId path segment. The server's @PathVariable decoder already
handles the percent-encoded form transparently, so this is purely a
client-side fix that recovers every existing hashed-id row in addition
to any future ones.
Issue: #372
This commit is contained in:
parent
7c36b0d752
commit
4f160b6ffb
@ -9,6 +9,17 @@ import type {
|
||||
GrantScope,
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* URL-encode a conversation id before interpolating it into a path. Some ids
|
||||
* contain characters that the browser interprets as URL structural — notably
|
||||
* `#`, which webchat emits as a hash marker when the visitorId+sessionId pair
|
||||
* exceeds the conversation_id column width (see WebChatController#deriveConversationId:
|
||||
* `webchat:<key8>:#<sha256[0..40]>`). Without encoding, everything after the
|
||||
* `#` is treated as a fragment and never reaches the server, producing 405 on
|
||||
* `@DeleteMapping` fallbacks and 403 from the owner check.
|
||||
*/
|
||||
const encId = (id: string) => encodeURIComponent(id)
|
||||
|
||||
// Axios 实例
|
||||
export const http = axios.create({
|
||||
baseURL: '/api/v1',
|
||||
@ -156,9 +167,9 @@ export const chatApi = {
|
||||
})
|
||||
},
|
||||
stop: (conversationId: string) =>
|
||||
http.post<{ stopped: boolean }>(`/chat/${conversationId}/stop`),
|
||||
http.post<{ stopped: boolean }>(`/chat/${encId(conversationId)}/stop`),
|
||||
getPendingApprovals: (conversationId: string) =>
|
||||
http.get(`/chat/${conversationId}/pending-approvals`),
|
||||
http.get(`/chat/${encId(conversationId)}/pending-approvals`),
|
||||
}
|
||||
|
||||
// ==================== Conversation ====================
|
||||
@ -172,17 +183,17 @@ export const conversationApi = {
|
||||
page: (params: { page?: number; size?: number; keyword?: string }) =>
|
||||
http.get('/conversations/page', { params }),
|
||||
listMessages: (conversationId: string, params?: { beforeId?: number; limit?: number }) =>
|
||||
http.get(`/conversations/${conversationId}/messages`, { params }),
|
||||
http.get(`/conversations/${encId(conversationId)}/messages`, { params }),
|
||||
getStatus: (conversationId: string) =>
|
||||
http.get(`/conversations/${conversationId}/status`),
|
||||
http.get(`/conversations/${encId(conversationId)}/status`),
|
||||
delete: (conversationId: string) =>
|
||||
http.delete(`/conversations/${conversationId}`),
|
||||
http.delete(`/conversations/${encId(conversationId)}`),
|
||||
clearMessages: (conversationId: string) =>
|
||||
http.delete(`/conversations/${conversationId}/messages`),
|
||||
http.delete(`/conversations/${encId(conversationId)}/messages`),
|
||||
rename: (conversationId: string, title: string) =>
|
||||
http.put(`/conversations/${conversationId}/title`, { title }),
|
||||
http.put(`/conversations/${encId(conversationId)}/title`, { title }),
|
||||
setPinned: (conversationId: string, pinned: boolean) =>
|
||||
http.put(`/conversations/${conversationId}/pin`, { pinned }),
|
||||
http.put(`/conversations/${encId(conversationId)}/pin`, { pinned }),
|
||||
/**
|
||||
* Pin this conversation to a specific (provider, model). Closes issue
|
||||
* #183 — lets the admin UI switch model for IM-channel conversations
|
||||
@ -190,7 +201,7 @@ export const conversationApi = {
|
||||
* not just for the Web channel. Both params required and non-empty.
|
||||
*/
|
||||
setModel: (conversationId: string, modelProvider: string, modelName: string) =>
|
||||
http.put(`/conversations/${conversationId}/model`, { modelProvider, modelName }),
|
||||
http.put(`/conversations/${encId(conversationId)}/model`, { modelProvider, modelName }),
|
||||
batchDelete: (conversationIds: string[]) =>
|
||||
http.post('/conversations/batch-delete', { conversationIds }),
|
||||
}
|
||||
@ -1377,7 +1388,7 @@ export const goalApi = {
|
||||
}) => http.post<Goal>('/goals', data),
|
||||
|
||||
findActive: (conversationId: string) =>
|
||||
http.get<Goal | null>(`/goals/by-conversation/${conversationId}`),
|
||||
http.get<Goal | null>(`/goals/by-conversation/${encId(conversationId)}`),
|
||||
|
||||
get: (id: string) => http.get<Goal>(`/goals/${id}`),
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user