Skip to content

Commit 4454c47

Browse files
connermoclaude
andcommitted
fix: improve log clearing and prevent 404 errors
This commit fixes critical issues with the log clearing functionality: Backend fixes: - Fixed DELETE request body parsing with proper default values - Added conversation queue routing for Celery tasks - Ensured proper handling of empty conversation_ids array Frontend fixes: - Fixed DELETE request body handling in HTTP client - Improved SWR cache invalidation with correct key matching - Added proactive conversation ID validation to prevent 404 errors - Simplified error handling and removed debug logging Key improvements: - Request body now always sent for DELETE (empty array vs undefined) - Multi-layer cache clearing (log list, explore apps, conversation data) - Auto-cleanup of stale conversation IDs from localStorage - Error handling for deleted conversations with 404 recovery All changes pass lint and type-check validation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 31f6a06 commit 4454c47

File tree

6 files changed

+119
-37
lines changed

6 files changed

+119
-37
lines changed

api/controllers/console/app/conversation.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,14 @@ def get(self, app_model):
151151
def delete(self, app_model):
152152
current_user, _ = current_account_with_tenant()
153153
parser = reqparse.RequestParser()
154-
parser.add_argument("conversation_ids", type=list, location="json", required=False)
154+
parser.add_argument("conversation_ids", type=list, location="json", required=False, default=None)
155155
args = parser.parse_args()
156156

157-
conversation_ids = [str(id) for id in args["conversation_ids"]] if args.get("conversation_ids") else None
157+
# Convert conversation IDs to strings if provided and non-empty
158+
conversation_ids_raw = args.get("conversation_ids")
159+
conversation_ids = (
160+
[str(id) for id in conversation_ids_raw] if conversation_ids_raw and len(conversation_ids_raw) > 0 else None
161+
)
158162

159163
result = ConversationService.clear_conversations(
160164
app_model=app_model, user=current_user, conversation_ids=conversation_ids
@@ -392,10 +396,14 @@ def get(self, app_model):
392396
def delete(self, app_model):
393397
current_user, _ = current_account_with_tenant()
394398
parser = reqparse.RequestParser()
395-
parser.add_argument("conversation_ids", type=list, location="json", required=False)
399+
parser.add_argument("conversation_ids", type=list, location="json", required=False, default=None)
396400
args = parser.parse_args()
397401

398-
conversation_ids = [str(id) for id in args["conversation_ids"]] if args.get("conversation_ids") else None
402+
# Convert conversation IDs to strings if provided and non-empty
403+
conversation_ids_raw = args.get("conversation_ids")
404+
conversation_ids = (
405+
[str(id) for id in conversation_ids_raw] if conversation_ids_raw and len(conversation_ids_raw) > 0 else None
406+
)
399407

400408
result = ConversationService.clear_conversations(
401409
app_model=app_model, user=current_user, conversation_ids=conversation_ids

api/tasks/clear_conversation_task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
BATCH_SIZE = 1000
2525

2626

27-
@shared_task(bind=True, max_retries=3)
27+
@shared_task(bind=True, max_retries=3, queue="conversation")
2828
def clear_conversations_task(
2929
self,
3030
app_id: str,

web/app/components/app/log/filter.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Sort from '@/app/components/base/sort'
1313
import Button from '@/app/components/base/button'
1414
import Confirm from '@/app/components/base/confirm'
1515
import { useToastContext } from '@/app/components/base/toast'
16-
import { fetchAnnotationsCount, clearChatConversations, clearCompletionConversations } from '@/service/log'
16+
import { clearChatConversations, clearCompletionConversations, fetchAnnotationsCount } from '@/service/log'
1717
dayjs.extend(quarterOfYear)
1818

1919
const today = dayjs()
@@ -50,21 +50,24 @@ const Filter: FC<IFilterProps> = ({ isChatMode, appId, queryParams, setQueryPara
5050
const handleClearLogs = async (conversationIds?: string[]) => {
5151
setIsClearing(true)
5252
try {
53-
if (isChatMode) {
53+
if (isChatMode)
5454
await clearChatConversations({ appId, conversationIds })
55-
} else {
55+
else
5656
await clearCompletionConversations({ appId, conversationIds })
57-
}
58-
const message = conversationIds
59-
? t('appLog.filter.clearSelectedSuccess')
57+
58+
const message = conversationIds
59+
? t('appLog.filter.clearSelectedSuccess')
6060
: t('appLog.filter.clearSuccess')
6161
notify({ type: 'success', message })
62-
onRefresh?.()
62+
63+
// Wait for onRefresh to complete before clearing selection
64+
await onRefresh?.()
6365
onClearSelected?.(conversationIds)
64-
} catch (error) {
65-
console.error('Failed to clear logs:', error)
66+
}
67+
catch {
6668
notify({ type: 'error', message: t('appLog.filter.clearFailed') })
67-
} finally {
69+
}
70+
finally {
6871
setIsClearing(false)
6972
setShowConfirm(false)
7073
}
@@ -158,8 +161,8 @@ const Filter: FC<IFilterProps> = ({ isChatMode, appId, queryParams, setQueryPara
158161
{/* Confirmation Dialog */}
159162
{showConfirm && (
160163
<Confirm
161-
title={showConfirm === 'selected'
162-
? t('appLog.filter.clearSelectedConfirm.title')
164+
title={showConfirm === 'selected'
165+
? t('appLog.filter.clearSelectedConfirm.title')
163166
: t('appLog.filter.clearConfirm.title')
164167
}
165168
content={showConfirm === 'selected'

web/app/components/base/chat/chat-with-history/hooks.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,36 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
198198
const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(
199199
chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null,
200200
() => fetchChatList(chatShouldReloadKey, isInstalledApp, appId),
201-
{ revalidateOnFocus: false, revalidateOnReconnect: false },
201+
{
202+
revalidateOnFocus: false,
203+
revalidateOnReconnect: false,
204+
onError: (error) => {
205+
// Handle 404 errors for deleted conversations
206+
if ((error?.status === 404 || error?.message?.includes('Conversation Not Exists')) && appId)
207+
handleConversationIdInfoChange('')
208+
},
209+
},
202210
)
203211

212+
// Validate if currentConversationId exists in the conversation list
213+
// If not found, clear it from localStorage to prevent 404 errors
214+
useEffect(() => {
215+
if (!currentConversationId || !appConversationData || !appPinnedConversationData)
216+
return
217+
218+
const allConversations = [
219+
...(appPinnedConversationData?.data || []),
220+
...(appConversationData?.data || []),
221+
]
222+
223+
const conversationExists = allConversations.some(
224+
(conv: ConversationItem) => conv.id === currentConversationId,
225+
)
226+
227+
if (!conversationExists)
228+
handleConversationIdInfoChange('')
229+
}, [currentConversationId, appConversationData, appPinnedConversationData, handleConversationIdInfoChange])
230+
204231
const [clearChatList, setClearChatList] = useState(false)
205232
const [isResponding, setIsResponding] = useState(false)
206233
const appPrevChatTree = useMemo(

web/service/fetch.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions:
180180
},
181181
})
182182

183+
// Ensure body is sent for DELETE requests if provided
184+
// ky should handle this, but we ensure it's explicitly set
185+
let bodyOptions = {}
186+
if (body !== undefined && body !== null)
187+
bodyOptions = bodyStringify ? { json: body } : { body: body as BodyInit }
188+
183189
const res = await client(fetchPathname, {
184190
...init,
185191
headers,
@@ -189,7 +195,7 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions:
189195
retry: {
190196
methods: [],
191197
},
192-
...(bodyStringify ? { json: body } : { body: body as BodyInit }),
198+
...bodyOptions,
193199
searchParams: params,
194200
fetch(resource: RequestInfo | URL, options?: RequestInit) {
195201
if (resource instanceof Request && options) {

web/service/log.ts

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,30 +84,49 @@ export const fetchAgentLogDetail = ({ appID, params }: { appID: string; params:
8484
// Clear chat conversations (all or selected)
8585
export const clearChatConversations = async ({ appId, conversationIds }: { appId: string; conversationIds?: string[] }) => {
8686
try {
87-
const body = conversationIds ? { conversation_ids: conversationIds } : {}
87+
const body = conversationIds && conversationIds.length > 0
88+
? { conversation_ids: conversationIds }
89+
: { conversation_ids: [] }
90+
8891
const result = await del<any>(`/apps/${appId}/chat-conversations`, { body })
8992

90-
// Clear localStorage to prevent 404 errors on explore pages
91-
// Only clear conversation IDs for this specific app
92-
clearConversationIds(appId, { debug: true })
93+
// Clear localStorage to prevent 404 errors
94+
clearConversationIds(appId)
9395

94-
// Clear SWR caches
96+
// Clear SWR caches to force reload of conversation lists
9597
await Promise.all([
96-
mutate(`/apps/${appId}/chat-conversations`),
97-
mutate(`/apps/${appId}/completion-conversations`),
98+
// Clear log list caches (key is an object with url and params)
99+
// Force cache invalidation with populateCache: false
100+
mutate(
101+
key =>
102+
typeof key === 'object' && key !== null && 'url' in key
103+
&& (key.url === `/apps/${appId}/chat-conversations` || key.url === `/apps/${appId}/completion-conversations`),
104+
undefined,
105+
{
106+
revalidate: true,
107+
populateCache: false,
108+
},
109+
),
110+
// Clear explore apps caches
98111
mutate(
99112
key =>
100113
typeof key === 'string' && key.includes('/explore/apps'),
101114
undefined,
102115
{ revalidate: false },
103116
),
117+
// Clear conversation list caches to trigger validation in useChatWithHistory
118+
mutate(
119+
key =>
120+
Array.isArray(key) && key[0] === 'appConversationData' && key[2] === appId,
121+
undefined,
122+
{ revalidate: true },
123+
),
104124
])
105125

106-
console.log(`✅ Cleared chat conversations for app: ${appId}`)
107126
return result
108127
}
109128
catch (error) {
110-
console.error('Failed to clear chat conversations for app:', appId, error)
129+
console.error('Failed to clear chat conversations:', error)
111130
if (error instanceof Error)
112131
throw new Error(`Failed to clear chat conversations: ${error.message}`)
113132

@@ -118,30 +137,49 @@ export const clearChatConversations = async ({ appId, conversationIds }: { appId
118137
// Clear completion conversations (all or selected)
119138
export const clearCompletionConversations = async ({ appId, conversationIds }: { appId: string; conversationIds?: string[] }) => {
120139
try {
121-
const body = conversationIds ? { conversation_ids: conversationIds } : {}
140+
const body = conversationIds && conversationIds.length > 0
141+
? { conversation_ids: conversationIds }
142+
: { conversation_ids: [] }
143+
122144
const result = await del<any>(`/apps/${appId}/completion-conversations`, { body })
123145

124-
// Clear localStorage to prevent 404 errors on explore pages
125-
// Only clear conversation IDs for this specific app
126-
clearConversationIds(appId, { debug: true })
146+
// Clear localStorage to prevent 404 errors
147+
clearConversationIds(appId)
127148

128-
// Clear SWR caches
149+
// Clear SWR caches to force reload of conversation lists
129150
await Promise.all([
130-
mutate(`/apps/${appId}/chat-conversations`),
131-
mutate(`/apps/${appId}/completion-conversations`),
151+
// Clear log list caches (key is an object with url and params)
152+
// Force cache invalidation with populateCache: false
153+
mutate(
154+
key =>
155+
typeof key === 'object' && key !== null && 'url' in key
156+
&& (key.url === `/apps/${appId}/chat-conversations` || key.url === `/apps/${appId}/completion-conversations`),
157+
undefined,
158+
{
159+
revalidate: true,
160+
populateCache: false,
161+
},
162+
),
163+
// Clear explore apps caches
132164
mutate(
133165
key =>
134166
typeof key === 'string' && key.includes('/explore/apps'),
135167
undefined,
136168
{ revalidate: false },
137169
),
170+
// Clear conversation list caches to trigger validation in useChatWithHistory
171+
mutate(
172+
key =>
173+
Array.isArray(key) && key[0] === 'appConversationData' && key[2] === appId,
174+
undefined,
175+
{ revalidate: true },
176+
),
138177
])
139178

140-
console.log(`✅ Cleared completion conversations for app: ${appId}`)
141179
return result
142180
}
143181
catch (error) {
144-
console.error('Failed to clear completion conversations for app:', appId, error)
182+
console.error('Failed to clear completion conversations:', error)
145183
if (error instanceof Error)
146184
throw new Error(`Failed to clear completion conversations: ${error.message}`)
147185

0 commit comments

Comments
 (0)