Skip to content

Commit 4e0979a

Browse files
author
Conner Mo
committed
Optimize PR based on feedback - improve code quality and accessibility
- Extract localStorage clearing logic into shared utility function (web/utils/localStorage.ts) - Add accessibility improvements with aria-labels for checkboxes - Improve exception handling with specific exception types (OperationalError, ProgrammingError) - Add comprehensive error logging for better debugging - Enhanced user permission validation with security logging This addresses the main feedback points from the PR review: - Code duplication reduction - Better error handling and debugging - Improved accessibility for screen readers - Enhanced security measures
1 parent 642cd31 commit 4e0979a

File tree

6 files changed

+178
-75
lines changed

6 files changed

+178
-75
lines changed

api/controllers/console/app/conversation.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from datetime import datetime
2+
import logging
23

34
import pytz # pip install pytz
5+
6+
logger = logging.getLogger(__name__)
47
from flask_login import current_user
58
from flask_restx import Resource, marshal_with, reqparse
69
from flask_restx.inputs import int_range
710
from sqlalchemy import func, or_
11+
from sqlalchemy.exc import OperationalError, ProgrammingError
812
from sqlalchemy.orm import joinedload
913
from werkzeug.exceptions import Forbidden, NotFound
1014

@@ -110,6 +114,10 @@ def get(self, app_model):
110114
def delete(self, app_model):
111115
"""Clear completion conversations and related data including files"""
112116
if not current_user.is_editor:
117+
logger.warning(
118+
"Unauthorized deletion attempt: user %s tried to delete completion conversations for app %s",
119+
current_user.id, app_model.id
120+
)
113121
raise Forbidden()
114122

115123
parser = reqparse.RequestParser()
@@ -150,7 +158,7 @@ def delete(self, app_model):
150158
if all_message_ids:
151159
try:
152160
db.session.query(MessageFeedback).filter(MessageFeedback.message_id.in_(all_message_ids)).delete(synchronize_session=False)
153-
except Exception:
161+
except (OperationalError, ProgrammingError):
154162
pass # Table might not exist in this version
155163
try:
156164
db.session.query(MessageFile).filter(MessageFile.message_id.in_(all_message_ids)).delete(synchronize_session=False)
@@ -175,23 +183,23 @@ def delete(self, app_model):
175183
db.session.query(ConversationVariable).filter(
176184
ConversationVariable.conversation_id == conversation.id
177185
).delete()
178-
except Exception:
186+
except (OperationalError, ProgrammingError):
179187
pass # Table might not exist in this version
180188
try:
181189
db.session.query(ToolConversationVariables).filter(
182190
ToolConversationVariables.conversation_id == conversation.id
183191
).delete()
184-
except Exception:
192+
except (OperationalError, ProgrammingError):
185193
pass # Table might not exist in this version
186194
try:
187195
db.session.query(ToolFile).filter(ToolFile.conversation_id == conversation.id).delete()
188-
except Exception:
196+
except (OperationalError, ProgrammingError):
189197
pass # Table might not exist in this version
190198
try:
191199
db.session.query(PinnedConversation).filter(
192200
PinnedConversation.conversation_id == conversation.id
193201
).delete()
194-
except Exception:
202+
except (OperationalError, ProgrammingError):
195203
pass # Table might not exist in this version
196204

197205
# Delete upload file records
@@ -396,6 +404,10 @@ def get(self, app_model):
396404
def delete(self, app_model):
397405
"""Clear chat conversations and related data including files"""
398406
if not current_user.is_editor:
407+
logger.warning(
408+
"Unauthorized deletion attempt: user %s tried to delete chat conversations for app %s",
409+
current_user.id, app_model.id
410+
)
399411
raise Forbidden()
400412

401413
parser = reqparse.RequestParser()
@@ -436,7 +448,7 @@ def delete(self, app_model):
436448
if all_message_ids:
437449
try:
438450
db.session.query(MessageFeedback).filter(MessageFeedback.message_id.in_(all_message_ids)).delete(synchronize_session=False)
439-
except Exception:
451+
except (OperationalError, ProgrammingError):
440452
pass # Table might not exist in this version
441453
try:
442454
db.session.query(MessageFile).filter(MessageFile.message_id.in_(all_message_ids)).delete(synchronize_session=False)
@@ -461,23 +473,23 @@ def delete(self, app_model):
461473
db.session.query(ConversationVariable).filter(
462474
ConversationVariable.conversation_id == conversation.id
463475
).delete()
464-
except Exception:
476+
except (OperationalError, ProgrammingError):
465477
pass # Table might not exist in this version
466478
try:
467479
db.session.query(ToolConversationVariables).filter(
468480
ToolConversationVariables.conversation_id == conversation.id
469481
).delete()
470-
except Exception:
482+
except (OperationalError, ProgrammingError):
471483
pass # Table might not exist in this version
472484
try:
473485
db.session.query(ToolFile).filter(ToolFile.conversation_id == conversation.id).delete()
474-
except Exception:
486+
except (OperationalError, ProgrammingError):
475487
pass # Table might not exist in this version
476488
try:
477489
db.session.query(PinnedConversation).filter(
478490
PinnedConversation.conversation_id == conversation.id
479491
).delete()
480-
except Exception:
492+
except (OperationalError, ProgrammingError):
481493
pass # Table might not exist in this version
482494

483495
# Delete upload file records

web/app/components/app/log/list.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh, s
973973
checked={isAllSelected}
974974
indeterminate={isSomeSelected}
975975
onCheck={handleSelectAll}
976+
aria-label={t('appLog.table.header.selectAllConversations')}
976977
/>
977978
</td>
978979
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
@@ -999,6 +1000,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh, s
9991000
<Checkbox
10001001
checked={selectedItems.includes(log.id)}
10011002
onCheck={() => handleSelectItem(log.id)}
1003+
aria-label={t('appLog.table.header.selectConversation')}
10021004
/>
10031005
{!log.read_at && (
10041006
<span className='inline-block h-1.5 w-1.5 rounded bg-util-colors-blue-blue-500'></span>

web/i18n/en-US/app-log.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const translation = {
2020
tokens: 'TOKENS',
2121
user: 'End User or Account',
2222
version: 'VERSION',
23+
selectAllConversations: 'Select all conversations',
24+
selectConversation: 'Select conversation',
2325
},
2426
pagination: {
2527
previous: 'Prev',

web/i18n/zh-Hans/app-log.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const translation = {
2020
tokens: 'TOKENS',
2121
user: '用户或账户',
2222
version: '版本',
23+
selectAllConversations: '全选会话',
24+
selectConversation: '选择会话',
2325
},
2426
pagination: {
2527
previous: '上一页',

web/service/log.ts

Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Fetcher } from 'swr'
22
import { del, get, post } from './base'
33
import { mutate } from 'swr'
4-
import { CONVERSATION_ID_INFO } from '../app/components/base/chat/constants'
4+
import { clearConversationIds } from '@/utils/localStorage'
55
import type {
66
AgentLogDetailRequest,
77
AgentLogDetailResponse,
@@ -88,36 +88,7 @@ export const clearChatConversations = async ({ appId, conversationIds }: { appId
8888
const result = await del<any>(`/apps/${appId}/chat-conversations`, { body })
8989

9090
// Clear localStorage to prevent 404 errors on explore pages
91-
if (typeof window !== 'undefined') {
92-
const conversationIdInfo = JSON.parse(localStorage.getItem(CONVERSATION_ID_INFO) || '{}')
93-
94-
// Clear conversation ID for the current app (from logs page)
95-
let cleared = false
96-
if (conversationIdInfo[appId]) {
97-
delete conversationIdInfo[appId]
98-
cleared = true
99-
console.log(`✅ Cleared conversation ID info for app ${appId}`)
100-
}
101-
102-
// ADDITIONAL FIX: Also clear ALL conversation IDs to prevent explore page 404 errors
103-
const keysToDelete = Object.keys(conversationIdInfo)
104-
if (keysToDelete.length > 0) {
105-
keysToDelete.forEach(key => {
106-
delete conversationIdInfo[key]
107-
console.log(`🧹 Cleared conversation ID for ${key} to prevent 404 errors`)
108-
})
109-
cleared = true
110-
}
111-
112-
if (cleared) {
113-
localStorage.setItem(CONVERSATION_ID_INFO, JSON.stringify(conversationIdInfo))
114-
window.dispatchEvent(new StorageEvent('storage', {
115-
key: CONVERSATION_ID_INFO,
116-
newValue: JSON.stringify(conversationIdInfo),
117-
storageArea: localStorage
118-
}))
119-
}
120-
}
91+
clearConversationIds(appId, { clearAll: true, debug: true })
12192

12293
// Clear SWR caches
12394
await Promise.all([
@@ -135,8 +106,11 @@ export const clearChatConversations = async ({ appId, conversationIds }: { appId
135106
return result
136107
}
137108
catch (error) {
138-
console.error('Failed to clear chat conversations:', error)
139-
throw error
109+
console.error('Failed to clear chat conversations for app:', appId, error)
110+
if (error instanceof Error)
111+
throw new Error(`Failed to clear chat conversations: ${error.message}`)
112+
113+
throw new Error('Failed to clear chat conversations')
140114
}
141115
}
142116

@@ -147,36 +121,7 @@ export const clearCompletionConversations = async ({ appId, conversationIds }: {
147121
const result = await del<any>(`/apps/${appId}/completion-conversations`, { body })
148122

149123
// Clear localStorage to prevent 404 errors on explore pages
150-
if (typeof window !== 'undefined') {
151-
const conversationIdInfo = JSON.parse(localStorage.getItem(CONVERSATION_ID_INFO) || '{}')
152-
153-
// Clear conversation ID for the current app (from logs page)
154-
let cleared = false
155-
if (conversationIdInfo[appId]) {
156-
delete conversationIdInfo[appId]
157-
cleared = true
158-
console.log(`✅ Cleared conversation ID info for app ${appId}`)
159-
}
160-
161-
// ADDITIONAL FIX: Also clear ALL conversation IDs to prevent explore page 404 errors
162-
const keysToDelete = Object.keys(conversationIdInfo)
163-
if (keysToDelete.length > 0) {
164-
keysToDelete.forEach(key => {
165-
delete conversationIdInfo[key]
166-
console.log(`🧹 Cleared conversation ID for ${key} to prevent 404 errors`)
167-
})
168-
cleared = true
169-
}
170-
171-
if (cleared) {
172-
localStorage.setItem(CONVERSATION_ID_INFO, JSON.stringify(conversationIdInfo))
173-
window.dispatchEvent(new StorageEvent('storage', {
174-
key: CONVERSATION_ID_INFO,
175-
newValue: JSON.stringify(conversationIdInfo),
176-
storageArea: localStorage
177-
}))
178-
}
179-
}
124+
clearConversationIds(appId, { clearAll: true, debug: true })
180125

181126
// Clear SWR caches
182127
await Promise.all([
@@ -194,7 +139,10 @@ export const clearCompletionConversations = async ({ appId, conversationIds }: {
194139
return result
195140
}
196141
catch (error) {
197-
console.error('Failed to clear completion conversations:', error)
198-
throw error
142+
console.error('Failed to clear completion conversations for app:', appId, error)
143+
if (error instanceof Error)
144+
throw new Error(`Failed to clear completion conversations: ${error.message}`)
145+
146+
throw new Error('Failed to clear completion conversations')
199147
}
200148
}

web/utils/localStorage.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { CONVERSATION_ID_INFO } from '@/config'
2+
3+
/**
4+
* Utility functions for managing conversation IDs in localStorage
5+
*/
6+
7+
export type ConversationIdInfo = {
8+
[appId: string]: {
9+
[userId: string]: string
10+
}
11+
}
12+
13+
/**
14+
* Clear conversation IDs from localStorage to prevent 404 errors
15+
* @param appId - Optional specific app ID to clear. If not provided, clears all conversation IDs
16+
* @param options - Additional options for clearing behavior
17+
*/
18+
export function clearConversationIds(
19+
appId?: string,
20+
options: {
21+
/** Clear all conversation IDs regardless of appId */
22+
clearAll?: boolean
23+
/** Enable debug logging */
24+
debug?: boolean
25+
} = {},
26+
): boolean {
27+
if (typeof window === 'undefined')
28+
return false
29+
30+
const { clearAll = true, debug = true } = options
31+
32+
try {
33+
const conversationIdInfo: ConversationIdInfo = JSON.parse(
34+
localStorage.getItem(CONVERSATION_ID_INFO) || '{}',
35+
)
36+
37+
let cleared = false
38+
39+
// Clear specific app ID if provided
40+
if (appId && conversationIdInfo[appId]) {
41+
delete conversationIdInfo[appId]
42+
cleared = true
43+
if (debug)
44+
console.log(`✅ Cleared conversation ID info for app ${appId}`)
45+
}
46+
47+
// Clear all conversation IDs to prevent explore page 404 errors
48+
if (clearAll) {
49+
const keysToDelete = Object.keys(conversationIdInfo)
50+
if (keysToDelete.length > 0) {
51+
keysToDelete.forEach((key) => {
52+
delete conversationIdInfo[key]
53+
if (debug)
54+
console.log(`🧹 Cleared conversation ID for ${key} to prevent 404 errors`)
55+
})
56+
cleared = true
57+
}
58+
}
59+
60+
// Update localStorage and dispatch event if changes were made
61+
if (cleared) {
62+
const newValue = JSON.stringify(conversationIdInfo)
63+
localStorage.setItem(CONVERSATION_ID_INFO, newValue)
64+
65+
// Dispatch storage event for cross-tab synchronization
66+
window.dispatchEvent(new StorageEvent('storage', {
67+
key: CONVERSATION_ID_INFO,
68+
newValue,
69+
storageArea: localStorage,
70+
}))
71+
72+
if (debug)
73+
console.log('📱 localStorage conversation IDs updated and storage event dispatched')
74+
}
75+
76+
return cleared
77+
}
78+
catch (error) {
79+
console.error('Failed to clear conversation IDs from localStorage:', error)
80+
return false
81+
}
82+
}
83+
84+
/**
85+
* Get conversation ID for a specific app and user
86+
*/
87+
export function getConversationId(appId: string, userId = 'DEFAULT'): string {
88+
if (typeof window === 'undefined')
89+
return ''
90+
91+
try {
92+
const conversationIdInfo: ConversationIdInfo = JSON.parse(
93+
localStorage.getItem(CONVERSATION_ID_INFO) || '{}',
94+
)
95+
96+
return conversationIdInfo[appId]?.[userId] || ''
97+
}
98+
catch (error) {
99+
console.error('Failed to get conversation ID from localStorage:', error)
100+
return ''
101+
}
102+
}
103+
104+
/**
105+
* Set conversation ID for a specific app and user
106+
*/
107+
export function setConversationId(appId: string, conversationId: string, userId = 'DEFAULT'): boolean {
108+
if (typeof window === 'undefined')
109+
return false
110+
111+
try {
112+
const conversationIdInfo: ConversationIdInfo = JSON.parse(
113+
localStorage.getItem(CONVERSATION_ID_INFO) || '{}',
114+
)
115+
116+
if (!conversationIdInfo[appId])
117+
conversationIdInfo[appId] = {}
118+
119+
conversationIdInfo[appId][userId] = conversationId
120+
121+
const newValue = JSON.stringify(conversationIdInfo)
122+
localStorage.setItem(CONVERSATION_ID_INFO, newValue)
123+
124+
// Dispatch storage event for cross-tab synchronization
125+
window.dispatchEvent(new StorageEvent('storage', {
126+
key: CONVERSATION_ID_INFO,
127+
newValue,
128+
storageArea: localStorage,
129+
}))
130+
131+
return true
132+
}
133+
catch (error) {
134+
console.error('Failed to set conversation ID in localStorage:', error)
135+
return false
136+
}
137+
}

0 commit comments

Comments
 (0)