mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +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,
|
GrantScope,
|
||||||
} from '@/types'
|
} 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 实例
|
// Axios 实例
|
||||||
export const http = axios.create({
|
export const http = axios.create({
|
||||||
baseURL: '/api/v1',
|
baseURL: '/api/v1',
|
||||||
@ -156,9 +167,9 @@ export const chatApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
stop: (conversationId: string) =>
|
stop: (conversationId: string) =>
|
||||||
http.post<{ stopped: boolean }>(`/chat/${conversationId}/stop`),
|
http.post<{ stopped: boolean }>(`/chat/${encId(conversationId)}/stop`),
|
||||||
getPendingApprovals: (conversationId: string) =>
|
getPendingApprovals: (conversationId: string) =>
|
||||||
http.get(`/chat/${conversationId}/pending-approvals`),
|
http.get(`/chat/${encId(conversationId)}/pending-approvals`),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Conversation ====================
|
// ==================== Conversation ====================
|
||||||
@ -172,17 +183,17 @@ export const conversationApi = {
|
|||||||
page: (params: { page?: number; size?: number; keyword?: string }) =>
|
page: (params: { page?: number; size?: number; keyword?: string }) =>
|
||||||
http.get('/conversations/page', { params }),
|
http.get('/conversations/page', { params }),
|
||||||
listMessages: (conversationId: string, params?: { beforeId?: number; limit?: number }) =>
|
listMessages: (conversationId: string, params?: { beforeId?: number; limit?: number }) =>
|
||||||
http.get(`/conversations/${conversationId}/messages`, { params }),
|
http.get(`/conversations/${encId(conversationId)}/messages`, { params }),
|
||||||
getStatus: (conversationId: string) =>
|
getStatus: (conversationId: string) =>
|
||||||
http.get(`/conversations/${conversationId}/status`),
|
http.get(`/conversations/${encId(conversationId)}/status`),
|
||||||
delete: (conversationId: string) =>
|
delete: (conversationId: string) =>
|
||||||
http.delete(`/conversations/${conversationId}`),
|
http.delete(`/conversations/${encId(conversationId)}`),
|
||||||
clearMessages: (conversationId: string) =>
|
clearMessages: (conversationId: string) =>
|
||||||
http.delete(`/conversations/${conversationId}/messages`),
|
http.delete(`/conversations/${encId(conversationId)}/messages`),
|
||||||
rename: (conversationId: string, title: string) =>
|
rename: (conversationId: string, title: string) =>
|
||||||
http.put(`/conversations/${conversationId}/title`, { title }),
|
http.put(`/conversations/${encId(conversationId)}/title`, { title }),
|
||||||
setPinned: (conversationId: string, pinned: boolean) =>
|
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
|
* Pin this conversation to a specific (provider, model). Closes issue
|
||||||
* #183 — lets the admin UI switch model for IM-channel conversations
|
* #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.
|
* not just for the Web channel. Both params required and non-empty.
|
||||||
*/
|
*/
|
||||||
setModel: (conversationId: string, modelProvider: string, modelName: string) =>
|
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[]) =>
|
batchDelete: (conversationIds: string[]) =>
|
||||||
http.post('/conversations/batch-delete', { conversationIds }),
|
http.post('/conversations/batch-delete', { conversationIds }),
|
||||||
}
|
}
|
||||||
@ -1377,7 +1388,7 @@ export const goalApi = {
|
|||||||
}) => http.post<Goal>('/goals', data),
|
}) => http.post<Goal>('/goals', data),
|
||||||
|
|
||||||
findActive: (conversationId: string) =>
|
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}`),
|
get: (id: string) => http.get<Goal>(`/goals/${id}`),
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user