Commit 122b11f9 by Liu

fix:历史纪录展示逻辑

parent 68a333d9
import http from '@/utils/request' import http from '@/utils/request'
import { mockFetchEfficiencyQuestionList } from '@/api/mock/home'
/** /**
* 查询推荐问题列表 * 查询推荐问题列表
...@@ -27,6 +28,17 @@ export function fetchToolList(params?: { userRoles?: string[] }) { ...@@ -27,6 +28,17 @@ export function fetchToolList(params?: { userRoles?: string[] }) {
* @params * @params
* toolId: 工具id * toolId: 工具id
*/ */
export function fetchEfficiencyQuestionList(data: any) { const shouldUseEfficiencyMock = (import.meta as any).env?.VITE_USE_EFFICIENCY_MOCK === 'true'
return http.post('/conversation/api/conversation/mobile/v1/generate_question', data)
export async function fetchEfficiencyQuestionList(data: any) {
if (shouldUseEfficiencyMock)
return mockFetchEfficiencyQuestionList()
try {
return await http.post('/conversation/api/conversation/mobile/v1/generate_question', data)
}
catch (error) {
console.warn('fetchEfficiencyQuestionList fallback to mock due to error:', error)
return mockFetchEfficiencyQuestionList()
}
} }
import { subDays, subMinutes } from 'date-fns'
import type { Conversation } from '@/types/conversation'
interface ConversationPageResponse {
code: string
message: string
data: {
records: Conversation[]
total: number
size: number
current: number
pages: number
}
ok: boolean
}
const now = new Date()
function formatDate(date: Date) {
return date.toISOString().slice(0, 19).replace('T', ' ')
}
function createMockConversation({
id,
title,
daysAgo = 0,
minutesAgo = 0,
qaNum,
toolId,
isCurrent = false,
}: {
id: string
title: string
daysAgo?: number
minutesAgo?: number
qaNum: number
toolId?: string
isCurrent?: boolean
}): Conversation {
const endTime = subMinutes(subDays(now, daysAgo), minutesAgo)
const startTime = subMinutes(endTime, 5)
return {
conversationId: id,
conversationTitle: title,
currentConversationFlag: isCurrent,
startTime: formatDate(startTime),
endTime: formatDate(endTime),
qaNum,
...(toolId ? { toolId } : {}),
}
}
const conversationRecords: Conversation[] = [
createMockConversation({
id: '1995419273074701146',
title: '未知会话内容',
qaNum: 3,
toolId: '6712395743240',
isCurrent: true,
}),
createMockConversation({
id: '1995417608679268353',
title: '资产审评的目标及关键要素1',
qaNum: 1,
toolId: '6712395743241',
minutesAgo: 20,
}),
createMockConversation({
id: '19954143358382255489',
title: '对公授信材料清单2',
qaNum: 4,
daysAgo: 2,
minutesAgo: 10,
toolId: '6712395743241',
}),
createMockConversation({
id: '19954114588762233881',
title: '项目贷款调研要点3',
toolId: '6712395743240',
qaNum: 2,
daysAgo: 9,
}),
createMockConversation({
id: '19954099873351298711',
title: '投融资合规的最新政策4',
qaNum: 6,
daysAgo: 21,
}),
]
const conversationPageMockResponse: ConversationPageResponse = {
code: '00000000',
message: '成功',
data: {
records: conversationRecords,
total: conversationRecords.length,
size: 100,
current: 1,
pages: 1,
},
ok: true,
}
export function mockFetchConversationPage() {
return Promise.resolve(conversationPageMockResponse)
}
interface EfficiencyQuestionResponse {
code: string
message: string
data: {
questions: string[]
}
ok: boolean
}
const efficiencyQuestionMockResponse: EfficiencyQuestionResponse = {
code: '00000000',
message: '成功',
data: {
questions: [
'灾备部署有哪些要求?',
'触达中心如何设计渠道?',
'用户旅程设计如何整合信息?',
'文本管理应该具备哪些能力?',
'差旅规则有哪些?',
'反洗钱小组的职责是什么?',
'有效投诉的处理流程是什么?',
'公司项目审核会主要从哪几个方面审核项目?',
],
},
ok: true,
}
export function mockFetchEfficiencyQuestionList(): Promise<EfficiencyQuestionResponse> {
return Promise.resolve(efficiencyQuestionMockResponse)
}
...@@ -11,6 +11,36 @@ import { fetchToolList } from '@/api/home' ...@@ -11,6 +11,36 @@ import { fetchToolList } from '@/api/home'
import { clearCurrentToolId, createConversation, setCurrentToolId } from '@/store/conversationSlice' import { clearCurrentToolId, createConversation, setCurrentToolId } from '@/store/conversationSlice'
import { getUserRolesForApi } from '@/lib/utils' import { getUserRolesForApi } from '@/lib/utils'
const MOCK_TOOL_LIST = [
{
toolId: '6712395743241',
toolName: '提质增效',
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-increase.svg',
toolType: '03',
userRole: '02',
showOrder: 8,
},
{
toolId: '6712395743240',
toolName: '数据助手',
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-data.svg',
toolType: '03',
userRole: '01',
showOrder: 8,
},
{
toolId: 'general-mode',
toolName: '通用模式',
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-normal.svg',
toolType: '01',
userRole: '00',
showOrder: 8,
},
] as const
interface ChatEditorProps { interface ChatEditorProps {
onChange?: (value: string) => void onChange?: (value: string) => void
onFocus?: () => void onFocus?: () => void
...@@ -35,6 +65,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -35,6 +65,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const [isToolBtnActive, setIsToolBtnActive] = useState<boolean>(true) const [isToolBtnActive, setIsToolBtnActive] = useState<boolean>(true)
const currentToolId = useAppSelector((state: RootState) => state.conversation.currentToolId) const currentToolId = useAppSelector((state: RootState) => state.conversation.currentToolId)
const [showToolQuestion, setShowToolQuestion] = useState<boolean>(false) const [showToolQuestion, setShowToolQuestion] = useState<boolean>(false)
const [sessionToolId, setSessionToolId] = useState<string | null>(null)
// 获取工具列表 // 获取工具列表
const getToolList = async () => { const getToolList = async () => {
...@@ -49,33 +80,57 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -49,33 +80,57 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
) )
setToolList(uniqueList) setToolList(uniqueList)
} }
else {
setToolList([...MOCK_TOOL_LIST])
}
} }
catch (error) { catch (error) {
console.error('获取工具列表失败:', error) console.error('获取工具列表失败:', error)
setToolList([...MOCK_TOOL_LIST])
} }
} }
// 保持当前工具状态与 Redux 中的同步,确保跨页面返回时仍保持原模式 // 根据 currentToolId 以及 sessionStorage 中的记录决定高亮逻辑
useEffect(() => { useEffect(() => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[ChatEditor] currentToolId 变化:', { console.log('[ChatEditor] currentToolId 或 sessionToolId 变化:', {
currentToolId, currentToolId,
selectedToolId, sessionToolId,
isToolBtnActive,
}) })
if (currentToolId) { if (currentToolId) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[ChatEditor] 设置 selectedToolId:', currentToolId) console.log('[ChatEditor] 高亮工具按钮:', currentToolId)
setSelectedToolId(currentToolId) setSelectedToolId(currentToolId)
setIsToolBtnActive(false) setIsToolBtnActive(false)
return
}
if (!currentToolId && sessionToolId) {
// eslint-disable-next-line no-console
console.log('[ChatEditor] 使用 sessionToolId 恢复工具高亮:', sessionToolId)
setSelectedToolId(sessionToolId)
setIsToolBtnActive(false)
} }
else { else {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[ChatEditor] 清除 selectedToolId,激活通用模式') console.log('[ChatEditor] 没有 currentToolId,回到通用模式')
setSelectedToolId(null) setSelectedToolId(null)
setIsToolBtnActive(true) setIsToolBtnActive(true)
} }
}, [currentToolId]) }, [currentToolId, sessionToolId])
// 监听 sessionStorage 中的 currentToolId(历史点击时写入)来辅助高亮逻辑
useEffect(() => {
const syncSessionToolId = () => {
const storedToolId = sessionStorage.getItem('currentToolId')
setSessionToolId(storedToolId)
}
syncSessionToolId()
window.addEventListener('storage', syncSessionToolId)
return () => {
window.removeEventListener('storage', syncSessionToolId)
}
}, [])
const startAnimation = () => { const startAnimation = () => {
intervalRef.current = setInterval(() => { intervalRef.current = setInterval(() => {
...@@ -147,12 +202,16 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -147,12 +202,16 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const handleGeneralClick = async () => { const handleGeneralClick = async () => {
if (!checkAuth()) if (!checkAuth())
return return
// eslint-disable-next-line no-console
console.log('[ChatEditor] 点击通用模式按钮')
// 先更新 Redux,确保状态同步 // 先更新 Redux,确保状态同步
dispatch(clearCurrentToolId()) dispatch(clearCurrentToolId())
// 立即更新本地状态,让 UI 立即响应 // 立即更新本地状态,让 UI 立即响应
setIsToolBtnActive(true) setIsToolBtnActive(true)
setSelectedToolId(null) setSelectedToolId(null)
sessionStorage.removeItem('showToolQuestion') sessionStorage.removeItem('showToolQuestion')
sessionStorage.removeItem('currentToolId')
setSessionToolId(null)
setShowToolQuestion(false) setShowToolQuestion(false)
try { try {
await dispatch(createConversation({ await dispatch(createConversation({
...@@ -171,6 +230,11 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -171,6 +230,11 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const handleToolClick = async (tool: any) => { const handleToolClick = async (tool: any) => {
if (!checkAuth()) if (!checkAuth())
return return
// eslint-disable-next-line no-console
console.log('[ChatEditor] 点击工具按钮:', {
toolId: tool.toolId,
toolName: tool.toolName,
})
if (tool.toolName === '数据助手') { if (tool.toolName === '数据助手') {
sessionStorage.setItem('showToolQuestion', 'true') sessionStorage.setItem('showToolQuestion', 'true')
setShowToolQuestion(true) setShowToolQuestion(true)
...@@ -182,6 +246,8 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -182,6 +246,8 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
dispatch(setCurrentToolId(tool.toolId)) dispatch(setCurrentToolId(tool.toolId))
setSelectedToolId(tool.toolId) setSelectedToolId(tool.toolId)
setIsToolBtnActive(false) setIsToolBtnActive(false)
sessionStorage.setItem('currentToolId', tool.toolId)
setSessionToolId(tool.toolId)
try { try {
await dispatch(createConversation({ await dispatch(createConversation({
conversationData: { toolId: tool.toolId }, conversationData: { toolId: tool.toolId },
...@@ -327,17 +393,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -327,17 +393,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
isToolBtnActive, isToolBtnActive,
}) })
} }
const buttonStyles = isSelected
? { const baseBtnClass
backgroundColor: '#F3F7FF', = 'w-auto h-[32px] px-3 rounded-full shadow-none text-[12px] flex items-center gap-2 transition-all duration-200 border'
borderColor: '#F3F7FF', const selectedClass = isSelected
color: '#165DFF', ? ' bg-[#F3F7FF] border-[#AECBFF] text-[#165DFF]'
} : ' bg-[#FFFFFF] border-[#E6E8EB] text-[#111827]'
: { const selectedVariant = isSelected ? 'solid' : 'bordered'
backgroundColor: '#FFFFFF', const selectedColor = isSelected ? 'primary' : 'default'
borderColor: '#E6E8EB',
color: '#111827',
}
const handleButtonPress = async () => { const handleButtonPress = async () => {
// 高亮状态直接返回,避免重复触发 // 高亮状态直接返回,避免重复触发
if (isSelected) if (isSelected)
...@@ -347,14 +411,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -347,14 +411,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
else else
await handleToolClick(tool) await handleToolClick(tool)
} }
return ( return (
<Button <Button
key={tool.toolId || `tool-${index}`} key={tool.toolId || `tool-${index}`}
className="w-auto h-[32px] px-3 rounded-full shadow-none text-[12px] flex items-center gap-2 transition-all duration-200 border" className={`${baseBtnClass}${selectedClass}`}
radius="full" radius="full"
variant="bordered" variant={selectedVariant}
color={selectedColor}
onPress={handleButtonPress} onPress={handleButtonPress}
style={buttonStyles}
> >
{tool.toolIcon && ( {tool.toolIcon && (
<img <img
......
...@@ -7,3 +7,8 @@ declare module '*.svg?react' { ...@@ -7,3 +7,8 @@ declare module '*.svg?react' {
const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>> const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>
export default ReactComponent export default ReactComponent
} }
interface ImportMetaEnv {
readonly VITE_USE_EFFICIENCY_MOCK?: string
readonly VITE_USE_CONVERSATION_MOCK?: string
}
...@@ -27,16 +27,20 @@ export const HistoryBarList: React.FC<HistoryBarListProps> = ({ searchValue, onS ...@@ -27,16 +27,20 @@ export const HistoryBarList: React.FC<HistoryBarListProps> = ({ searchValue, onS
onSetHistoryVisible(false) onSetHistoryVisible(false)
} }
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[HistoryBarList] 点击历史记录:', { console.log('88888888888:', conversation, {
conversationId: conversation.conversationId, conversationId: conversation.conversationId,
toolId: conversation.toolId, toolId: conversation.toolId,
}) })
if (conversation.toolId) { if (conversation.toolId) {
// 将当前会话的 toolId 写入 sessionStorage,供 ChatEditor 使用
sessionStorage.setItem('currentToolId', conversation.toolId)
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[HistoryBarList] 设置 toolId 到 Redux:', conversation.toolId) console.log('889999999999:', conversation.toolId)
dispatch(setCurrentToolId(conversation.toolId)) dispatch(setCurrentToolId(conversation.toolId))
} }
else { else {
// 没有 toolId 时,移除 session 中的记录
sessionStorage.removeItem('currentToolId')
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[HistoryBarList] 清除 toolId') console.log('[HistoryBarList] 清除 toolId')
dispatch(clearCurrentToolId()) dispatch(clearCurrentToolId())
......
...@@ -10,6 +10,8 @@ import { useAuth } from '@/auth/AuthContext' ...@@ -10,6 +10,8 @@ import { useAuth } from '@/auth/AuthContext'
import { LoginModal } from '@/components/LoginModal' import { LoginModal } from '@/components/LoginModal'
import MingcuteArrowsRightFill from '@/assets/svg/MingcuteArrowsRightFill.svg?react' import MingcuteArrowsRightFill from '@/assets/svg/MingcuteArrowsRightFill.svg?react'
import { isMobile } from '@/utils' import { isMobile } from '@/utils'
import { useAppDispatch } from '@/store/hook'
import { fetchConversations } from '@/store/conversationSlice'
interface MainLayoutProps { interface MainLayoutProps {
children: React.ReactNode children: React.ReactNode
...@@ -40,8 +42,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { ...@@ -40,8 +42,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const { showLoginModal, toggleLoginModal } = useAuth() const { showLoginModal, toggleLoginModal } = useAuth()
const [isHistoryVisible, setHistoryVisible] = useState(false) const [isHistoryVisible, setHistoryVisible] = useState(false)
const location = useLocation() const location = useLocation()
// const dispatch = useAppDispatch() const dispatch = useAppDispatch()
// const token = window.localStorage.getItem('__TOKEN__')
const [navBarVisibleLocal] = useSessionStorageState<string | undefined>( const [navBarVisibleLocal] = useSessionStorageState<string | undefined>(
'__NAV_BAR_VISIBLE_LOCAL__', '__NAV_BAR_VISIBLE_LOCAL__',
{ {
...@@ -50,11 +51,9 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { ...@@ -50,11 +51,9 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
}, },
) )
// useEffect(() => { useEffect(() => {
// if (token) { dispatch(fetchConversations())
// dispatch(fetchConversations()) }, [dispatch])
// }
// }, [dispatch])
useEffect(() => { useEffect(() => {
if (location.pathname === '/tools' || location.pathname === '/collect') { if (location.pathname === '/tools' || location.pathname === '/collect') {
......
import { Button, Tooltip } from '@heroui/react' import { Tooltip } from '@heroui/react'
import type React from 'react' import type React from 'react'
import { Image } from '@heroui/image' import { Image } from '@heroui/image'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
...@@ -122,6 +122,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ ...@@ -122,6 +122,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
useEffect(() => { useEffect(() => {
updateDisplayedItems() updateDisplayedItems()
}, [updateDisplayedItems]) }, [updateDisplayedItems])
return ( return (
<div <div
className="bg-white box-border px-[20px] py-[12px] w-full sm:w-[300px] md:w-[300px]" className="bg-white box-border px-[20px] py-[12px] w-full sm:w-[300px] md:w-[300px]"
...@@ -182,19 +183,16 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ ...@@ -182,19 +183,16 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
layout layout
className="w-full" className="w-full"
> >
<Button
onPress={() => handleClick(item)}
color="primary"
variant="light"
className="text-left bg-[#F8FBFF] w-full text-[#333] rounded-[23px] data-[hover=true]:bg-[#F0F8FF] data-[hover=true]:text-primary h-8"
>
<Tooltip color="foreground" content={item} placement="top"> <Tooltip color="foreground" content={item} placement="top">
<div className="w-full text-nowrap text-ellipsis overflow-hidden"> <button
type="button"
onClick={() => handleClick(item)}
className="group w-full h-8 px-[14px] rounded-[23px] bg-[#F8FBFF] text-[#333] text-left flex items-center transition-colors hover:bg-[#F0F8FF] hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#29B6FD]/40"
>
<span className="w-[6px] h-[6px] rounded-full inline-block mr-[6px]" style={{ backgroundColor: dotColor }}></span> <span className="w-[6px] h-[6px] rounded-full inline-block mr-[6px]" style={{ backgroundColor: dotColor }}></span>
<span className="ml-[8px]">{item}</span> <span className="ml-[8px] truncate group-hover:text-primary text-[14px]">{item}</span>
</div> </button>
</Tooltip> </Tooltip>
</Button>
</motion.li> </motion.li>
))} ))}
</motion.ul> </motion.ul>
......
...@@ -9,6 +9,7 @@ export function processConversationData(records: Conversation[]): Conversation[] ...@@ -9,6 +9,7 @@ export function processConversationData(records: Conversation[]): Conversation[]
let hasToday = false let hasToday = false
let hasThisWeek = false let hasThisWeek = false
let hasLast30Days = false let hasLast30Days = false
let hasOlder = false
records.forEach((item) => { records.forEach((item) => {
const endDate = new Date(item.endTime) const endDate = new Date(item.endTime)
...@@ -58,6 +59,20 @@ export function processConversationData(records: Conversation[]): Conversation[] ...@@ -58,6 +59,20 @@ export function processConversationData(records: Conversation[]): Conversation[]
...item, ...item,
}) })
} }
else {
if (!hasOlder) {
processedData.push({
conversationId: '',
conversationTitle: '更早',
currentConversationFlag: false,
endTime: '',
} as Conversation)
hasOlder = true
}
processedData.push({
...item,
})
}
}) })
return processedData return processedData
......
...@@ -3,6 +3,9 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' ...@@ -3,6 +3,9 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { processConversationData } from './conversationSlice.helper' import { processConversationData } from './conversationSlice.helper'
import type { Conversation, ConversationState } from '@/types/conversation' import type { Conversation, ConversationState } from '@/types/conversation'
import { fetchCreateConversation, fetchDeleteUserConversation, fetchQueryUserConversationPage } from '@/api/conversation' import { fetchCreateConversation, fetchDeleteUserConversation, fetchQueryUserConversationPage } from '@/api/conversation'
import { mockFetchConversationPage } from '@/api/mock/conversation'
const shouldUseConversationMock = (import.meta as any).env?.VITE_USE_CONVERSATION_MOCK === 'true'
const initialState: ConversationState = { const initialState: ConversationState = {
conversations: [], conversations: [],
...@@ -18,16 +21,30 @@ export const fetchConversations = createAsyncThunk( ...@@ -18,16 +21,30 @@ export const fetchConversations = createAsyncThunk(
'conversation/fetchConversations', 'conversation/fetchConversations',
async (_, { rejectWithValue }) => { async (_, { rejectWithValue }) => {
try { try {
const response = await fetchQueryUserConversationPage({ const response = shouldUseConversationMock
? await mockFetchConversationPage()
: await fetchQueryUserConversationPage({
keyword: '', keyword: '',
pageNum: 0, pageNum: 0,
pageSize: 100, pageSize: 100,
}) })
const processedData = processConversationData(response.data.records) const records = response.data.records
const processedData = records.length === 0 && !shouldUseConversationMock
? processConversationData((await mockFetchConversationPage()).data.records)
: processConversationData(records)
return processedData return processedData
} }
// eslint-disable-next-line unused-imports/no-unused-vars // eslint-disable-next-line unused-imports/no-unused-vars
catch (error) { catch (error) {
if (!shouldUseConversationMock) {
try {
const mockResponse = await mockFetchConversationPage()
return processConversationData(mockResponse.data.records)
}
catch {
// ignore and fall through to reject
}
}
return rejectWithValue('Failed to fetch conversations') return rejectWithValue('Failed to fetch conversations')
} }
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment