Commit e3e83b1c by Liu

feat: add chat tactics flow

parent 0b968cbd
// 问答功能独立的 API 接口
import http from '@/utils/request'
/**
* 查询问答功能会话列表
*/
export function fetchTacticsConversationPage<T>(data: T) {
return http.post('/conversation/api/conversation/mobile/v1/query_user_conversation_page', data)
}
/**
* 创建问答功能会话
*/
export function fetchCreateTacticsConversation<T>(data: T) {
return http.post('/conversation/api/conversation/mobile/v1/create_conversation', data)
}
/**
* 查询问答功能历史记录
*/
export function fetchTacticsQaRecordPage(conversationId: string) {
return http.post('/conversation/api/conversation/mobile/v1/query_user_qa_record_list', { conversationId })
}
/**
* 删除问答功能会话
*/
export function fetchDeleteTacticsConversation(conversationIdList: string[]) {
return http.post('/conversation/api/conversation/mobile/v1/delete_user_conversation', {
conversationIdList,
})
}
...@@ -20,9 +20,10 @@ interface ChatEditorProps { ...@@ -20,9 +20,10 @@ interface ChatEditorProps {
placeholders: string[] placeholders: string[]
showContentTips?: boolean showContentTips?: boolean
initialValue?: string initialValue?: string
hideTools?: boolean // 隐藏工具选择部分
} }
// onToolClick // onToolClick
const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, onChange, onFocus, onSubmit, onToolClick, placeholders, showContentTips = false, initialValue = '' }) => { const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, onChange, onFocus, onSubmit, onToolClick, placeholders, showContentTips = false, initialValue = '', hideTools = false }) => {
// const dispatch = useAppDispatch() // const dispatch = useAppDispatch()
const [content, setContent] = useState(initialValue) const [content, setContent] = useState(initialValue)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
...@@ -292,8 +293,10 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -292,8 +293,10 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
// 组件加载时和路由参数变化时获取工具列表 // 组件加载时和路由参数变化时获取工具列表
useEffect(() => { useEffect(() => {
getToolList() if (!hideTools) {
}, [location.pathname, location.search]) getToolList()
}
}, [location.pathname, location.search, hideTools])
// 监听 sessionStorage 中的 showToolQuestion // 监听 sessionStorage 中的 showToolQuestion
useEffect(() => { useEffect(() => {
...@@ -335,7 +338,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -335,7 +338,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
background: '#FFFFFF', background: '#FFFFFF',
border: '1px solid #0a0a0a78', border: '1px solid #0a0a0a78',
boxShadow: '0 8px 12px 0 rgba(235,235,235,0.30)', boxShadow: '0 8px 12px 0 rgba(235,235,235,0.30)',
...(toolList && toolList.length > 0 ? { height: '102px' } : {}), ...(!hideTools && toolList && toolList.length > 0 ? { height: '102px' } : {}),
}} }}
> >
<div <div
...@@ -355,7 +358,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -355,7 +358,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
resize: 'none', resize: 'none',
maxHeight: '48px', maxHeight: '48px',
wordBreak: 'break-all', wordBreak: 'break-all',
...(toolList && toolList.length > 0 ? { marginBottom: '40px' } : {}), ...(!hideTools && toolList && toolList.length > 0 ? { marginBottom: '40px' } : {}),
}} }}
> >
</div> </div>
...@@ -391,7 +394,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -391,7 +394,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
)} )}
</AnimatePresence> </AnimatePresence>
</div> </div>
{toolList && toolList.length > 0 && ( {!hideTools && toolList && toolList.length > 0 && (
<div className="absolute left-4 bottom-2 flex items-center gap-3 pointer-events-auto pl-[16px]"> <div className="absolute left-4 bottom-2 flex items-center gap-3 pointer-events-auto pl-[16px]">
{toolList.map((tool: any, index: number) => { {toolList.map((tool: any, index: number) => {
// 根据 selectedToolId 或路由中的 toolId 进行匹配,注意数据类型统一转换为字符串比较 // 根据 selectedToolId 或路由中的 toolId 进行匹配,注意数据类型统一转换为字符串比较
......
...@@ -61,41 +61,45 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { ...@@ -61,41 +61,45 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
} }
}, [location.pathname]) }, [location.pathname])
// 判断是否为问答功能页面
const isTacticsPage = location.pathname.startsWith('/tactics')
return ( return (
<motion.main className={styles.layoutMain}> <motion.main className={styles.layoutMain}>
{/* hidden */} {/* 问答功能页面不显示导航栏和历史记录 */}
<motion.div {!isTacticsPage && (
animate={navBarVisibleLocal === '0' ? isHistoryVisible ? 'shrunk' : 'expanded' : 'navTween'} <motion.div
variants={contentVariants} animate={navBarVisibleLocal === '0' ? isHistoryVisible ? 'shrunk' : 'expanded' : 'navTween'}
className={`fixed right-[-12px] top-[10px] z-[49] h-auto sm:relative flex sm:h-full items-center ${isHistoryVisible && !isMobile() ? 'w-[340px]' : 'w-[90px]'} box-border`} variants={contentVariants}
> className={`fixed right-[-12px] top-[10px] z-[49] h-auto sm:relative flex sm:h-full items-center ${isHistoryVisible && !isMobile() ? 'w-[340px]' : 'w-[90px]'} box-border`}
>
<Navbar <Navbar
isHistoryVisible={isHistoryVisible} isHistoryVisible={isHistoryVisible}
onSetHistoryVisible={setHistoryVisible} onSetHistoryVisible={setHistoryVisible}
/> />
<HistoryBar isVisible={isHistoryVisible} onSetHistoryVisible={setHistoryVisible} /> <HistoryBar isVisible={isHistoryVisible} onSetHistoryVisible={setHistoryVisible} />
{!isHistoryVisible && ( {!isHistoryVisible && (
<motion.div <motion.div
initial="hidden" initial="hidden"
animate="visible" animate="visible"
variants={{ variants={{
hidden: { hidden: {
x: -5, x: -5,
opacity: 0, opacity: 0,
}, },
}} }}
className={`${styles.sidebarArrow} side-bar-arrow h-[42px] flex items-center`} className={`${styles.sidebarArrow} side-bar-arrow h-[42px] flex items-center`}
> >
<MingcuteArrowsRightFill className="text-[#818d91]" /> <MingcuteArrowsRightFill className="text-[#818d91]" />
</motion.div> </motion.div>
)} )}
</motion.div> </motion.div>
)}
<motion.div <motion.div
variants={contentVariants} variants={contentVariants}
animate={navBarVisibleLocal === '0' ? '' : 'mainTween'} animate={navBarVisibleLocal === '0' || isTacticsPage ? '' : 'mainTween'}
className={`${styles.layoutContent} px-[12px]`} className={`${styles.layoutContent} ${isTacticsPage ? 'px-0' : 'px-[12px]'}`}
> >
{children} {children}
</motion.div> </motion.div>
......
...@@ -28,10 +28,10 @@ export const ChatWelcome: React.FC<ChatWelcomeProps> = ({ toolName: _toolName }) ...@@ -28,10 +28,10 @@ export const ChatWelcome: React.FC<ChatWelcomeProps> = ({ toolName: _toolName })
return ( return (
<div className="chatWelcomeContainer w-full"> <div className="chatWelcomeContainer w-full">
<div className="h-[20px] sm:h-[32px] w-full"></div> <div className="h-[20px] sm:h-[32px] w-full"></div>
<div className="flex"> <div className="flex items-start">
<Avatar className="mr-[12px] hidden sm:block flex-shrink-0" src={viteOutputObj === 'inner' ? AIIcon : AvatarBot} /> <Avatar className="mr-[12px] flex-shrink-0" src={viteOutputObj === 'inner' ? AIIcon : AvatarBot} />
<motion.div <motion.div
className="sm:ml-[20px] rounded-[20px] box-border px-[16px] py-[16px] sm:px-[24px] sm:py-[20px]" className="rounded-[20px] box-border px-[16px] py-[16px] sm:px-[24px] sm:py-[20px]"
style={{ background: '#F7FAFD' }} style={{ background: '#F7FAFD' }}
> >
<div className="content"> <div className="content">
......
// 问答功能独立首页
import type React from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useLocalStorageState } from 'ahooks'
import styles from '../Home/Home.module.less'
import { ChatWelcome } from '../Chat/components/ChatWelcome'
import { clearTacticsNavigationFlag, createTacticsConversation, fetchTacticsConversations } from '@/store/tacticsSlice'
import { useAppDispatch, useAppSelector } from '@/store/hook'
import type { RootState } from '@/store'
import { fetchLoginByToken, fetchLoginByUid } from '@/api/common'
import { getUserRolesFromRoute } from '@/lib/utils'
import { ChatEditor } from '@/components/ChatEditor'
import { RECOMMEND_QUESTIONS_OTHER } from '@/config/recommendQuestion'
export const TacticsHome: React.FC = () => {
const viteOutputObj = import.meta.env.VITE_OUTPUT_OBJ || 'open'
const dispatch = useAppDispatch()
const location = useLocation()
const navigate = useNavigate()
const hasFetched = useRef(false)
const { shouldNavigateToNewConversation, currentConversationId, shouldSendQuestion } = useAppSelector((state: RootState) => state.tactics)
const [token, setToken] = useLocalStorageState<string | undefined>('__TOKEN__', {
defaultValue: '',
})
const initTacticsConversation = () => {
const fromCollect = location.state?.fromCollect
// 只有在访问问答首页时才创建新对话
if (!fromCollect && location.pathname === '/tactics') {
dispatch(
createTacticsConversation({
conversationData: {},
shouldNavigate: true,
shouldSendQuestion: '',
}),
)
}
// 清除状态以避免下次影响
if (location.state?.fromCollect) {
window.history.replaceState({}, document.title, window.location.pathname)
}
}
// 处理创建对话并跳转(用于输入框提交)
const handleCreateConversation = useCallback((question: string) => {
dispatch(
createTacticsConversation({
conversationData: {},
shouldNavigate: true,
shouldSendQuestion: question,
}),
)
}, [dispatch])
// 监听导航到新对话
useEffect(() => {
if (shouldNavigateToNewConversation && currentConversationId) {
const url = `/tactics/chat/${currentConversationId}`
navigate(url, {
state: {
shouldSendQuestion,
},
})
dispatch(clearTacticsNavigationFlag())
}
}, [shouldNavigateToNewConversation, currentConversationId, shouldSendQuestion, navigate, dispatch])
const login = useCallback(async () => {
if (hasFetched.current) {
return
}
hasFetched.current = true
const url = new URL(window.location.href)
const searchParams = new URLSearchParams(url.search)
const _loginCode = searchParams.get('loginCode')
let res = {} as any
if (viteOutputObj === 'inner') {
if (_loginCode) {
res = await fetchLoginByToken(_loginCode)
if (res.data) {
setToken(res.data.token)
window.dispatchEvent(
new StorageEvent('storage', {
key: '__TOKEN__',
oldValue: token,
newValue: res.data.token,
url: window.location.href,
storageArea: localStorage,
}),
)
initTacticsConversation()
dispatch(fetchTacticsConversations())
}
}
else {
initTacticsConversation()
dispatch(fetchTacticsConversations())
}
}
else {
res = await fetchLoginByUid('123123')
if (res.data) {
setToken(res.data.token)
window.dispatchEvent(
new StorageEvent('storage', {
key: '__TOKEN__',
oldValue: token,
newValue: res.data.token,
url: window.location.href,
storageArea: localStorage,
}),
)
initTacticsConversation()
dispatch(fetchTacticsConversations())
}
}
}, [setToken, dispatch, token])
// 监听路由参数变化
useEffect(() => {
getUserRolesFromRoute()
}, [location.search])
useEffect(() => {
login()
}, [])
return (
<div
className={styles.homePage}
style={{
height: '100vh',
display: 'flex',
flexDirection: 'column',
width: '420px',
marginLeft: 'auto',
marginRight: 0,
position: 'fixed',
right: 0,
top: 0,
backgroundColor: '#FFFFFF',
zIndex: 1000,
}}
>
<div className="h-full w-full flex flex-col">
{/* 主要内容区域 - 全屏显示 */}
<div className="flex-1 overflow-hidden flex flex-col">
{/* 欢迎语区域 */}
<div className="flex-1 overflow-y-auto scrollbar-hide px-[16px] pt-[24px]">
<ChatWelcome />
</div>
{/* 底部输入框 */}
<div className="box-border px-[16px] pb-[18px] pt-[12px] bg-white border-t border-gray-100">
<ChatEditor
showContentTips
onSubmit={handleCreateConversation}
placeholders={RECOMMEND_QUESTIONS_OTHER}
hideTools
/>
<div className="w-full text-center mt-[12px] text-[#3333334d] text-[12px]">
内容由AI模型生成,其准确性和完整性无法保证,仅供参考
</div>
</div>
</div>
</div>
</div>
)
}
export { TacticsHome } from './TacticsHome'
export { TacticsChat } from './TacticsChat'
...@@ -11,6 +11,10 @@ import { type WithAuthProps, withAuth } from '@/auth/withAuth' ...@@ -11,6 +11,10 @@ import { type WithAuthProps, withAuth } from '@/auth/withAuth'
import { useAppDispatch } from '@/store/hook' import { useAppDispatch } from '@/store/hook'
import { createConversation } from '@/store/conversationSlice' import { createConversation } from '@/store/conversationSlice'
interface WelcomeWordProps {
onCreateConversation?: () => void // 自定义创建对话函数
}
const BotEye: React.FC = () => { const BotEye: React.FC = () => {
const controls = useAnimation() const controls = useAnimation()
...@@ -55,10 +59,14 @@ const BotAnimateBox: React.FC = () => { ...@@ -55,10 +59,14 @@ const BotAnimateBox: React.FC = () => {
) )
} }
const WelcomeWordBase: React.FC<WithAuthProps> = ({ checkAuth }) => { const WelcomeWordBase: React.FC<WithAuthProps & WelcomeWordProps> = ({ checkAuth, onCreateConversation }) => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const handleCreateConversation = () => { const handleCreateConversation = () => {
if (onCreateConversation) {
onCreateConversation()
return
}
dispatch(createConversation({ dispatch(createConversation({
conversationData: {}, conversationData: {},
shouldNavigate: true, shouldNavigate: true,
......
...@@ -5,6 +5,7 @@ import { Chat } from '../pages/Chat' ...@@ -5,6 +5,7 @@ import { Chat } from '../pages/Chat'
import { Collect } from '../pages/Collect' import { Collect } from '../pages/Collect'
import { Tools } from '../pages/Tools' import { Tools } from '../pages/Tools'
import { Protocol } from '../pages/Protocol' import { Protocol } from '../pages/Protocol'
import { TacticsChat, TacticsHome } from '../pages/ChatTactics'
import { withRouteChangeHandler } from './RouteChangeHandler' import { withRouteChangeHandler } from './RouteChangeHandler'
const AppRoutesComponent: React.FC = () => { const AppRoutesComponent: React.FC = () => {
...@@ -14,6 +15,9 @@ const AppRoutesComponent: React.FC = () => { ...@@ -14,6 +15,9 @@ const AppRoutesComponent: React.FC = () => {
<Route path="/chat/:id" element={<Chat />} /> <Route path="/chat/:id" element={<Chat />} />
</Route> </Route>
<Route path="/home" element={<Home />}></Route> <Route path="/home" element={<Home />}></Route>
<Route path="/tactics" element={<TacticsHome />}>
<Route path="/tactics/chat/:id" element={<TacticsChat />} />
</Route>
<Route path="/collect" element={<Collect />} /> <Route path="/collect" element={<Collect />} />
<Route path="/tools" element={<Tools />} /> <Route path="/tools" element={<Tools />} />
<Route path="/protocol/:id" element={<Protocol />} /> <Route path="/protocol/:id" element={<Protocol />} />
......
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { clearCurrentConversation, setCurrentConversation } from '@/store/conversationSlice' import { clearCurrentConversation, setCurrentConversation } from '@/store/conversationSlice'
import { clearCurrentTacticsConversation, setCurrentTacticsConversation } from '@/store/tacticsSlice'
import { useAppDispatch, useAppSelector } from '@/store/hook' import { useAppDispatch, useAppSelector } from '@/store/hook'
import { setIsAsking } from '@/store/chatSlice' import { setIsAsking } from '@/store/chatSlice'
import type { RootState } from '@/store' import type { RootState } from '@/store'
...@@ -17,6 +18,20 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) { ...@@ -17,6 +18,20 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) {
const oldPath = beforeLocationPathName const oldPath = beforeLocationPathName
useEffect(() => { useEffect(() => {
// 处理 /home?from=tactics 重定向到 /tactics
if (location.pathname === '/home') {
const searchParams = new URLSearchParams(location.search)
const from = searchParams.get('from')
if (from === 'tactics') {
// 重定向到 /tactics,保留其他查询参数
const newSearchParams = new URLSearchParams(location.search)
newSearchParams.delete('from')
const newSearch = newSearchParams.toString()
navigate(`/tactics${newSearch ? `?${newSearch}` : ''}`, { replace: true })
return
}
}
if (isAsking && newPath !== oldPath && oldPath !== '') { if (isAsking && newPath !== oldPath && oldPath !== '') {
dispatch(setIsAsking(false)) dispatch(setIsAsking(false))
} }
...@@ -46,9 +61,33 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) { ...@@ -46,9 +61,33 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) {
dispatch(setCurrentConversation(conversationId)) dispatch(setCurrentConversation(conversationId))
} }
// 处理问答功能的聊天路由
else if (location.pathname.startsWith('/tactics/chat/')) {
const conversationId = location.pathname.split('/')[3]
const tokenStr = window.localStorage.getItem('__TOKEN__') || '""'
let token = ''
try {
token = JSON.parse(tokenStr)
}
catch {
navigate('/tactics')
return
}
if (!token) {
navigate('/tactics')
return
}
dispatch(setCurrentTacticsConversation(conversationId))
}
else if (location.pathname === '/tactics') {
dispatch(clearCurrentTacticsConversation())
}
// 这里可以添加其他路由相关的逻辑 // 这里可以添加其他路由相关的逻辑
beforeLocationPathName = newPath beforeLocationPathName = newPath
}, [location, dispatch]) }, [location, dispatch, navigate])
return <WrappedComponent {...props} /> return <WrappedComponent {...props} />
} }
......
import { configureStore } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit'
import conversationReducer from './conversationSlice' import conversationReducer from './conversationSlice'
import chatReducer from './chatSlice' import chatReducer from './chatSlice'
import tacticsReducer from './tacticsSlice'
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
conversation: conversationReducer, conversation: conversationReducer,
chat: chatReducer, chat: chatReducer,
tactics: tacticsReducer,
}, },
}) })
// 为了在TypeScript中使用,我们导出这些类型 // 为了在TypeScript中使用,我们导出这些类型
......
// 问答功能独立的状态管理
import type { PayloadAction } from '@reduxjs/toolkit'
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import type { TacticsConversation, TacticsConversationState } from '@/types/tactics'
import { fetchCreateTacticsConversation, fetchDeleteTacticsConversation, fetchTacticsConversationPage } from '@/api/tactics'
const initialState: TacticsConversationState = {
conversations: [],
currentConversationId: null,
isLoading: false,
error: null,
shouldNavigateToNewConversation: false,
shouldSendQuestion: '',
}
// 处理会话数据(简化版,可根据实际需求调整)
function processTacticsConversationData(records: any[]): TacticsConversation[] {
return records.map(record => ({
id: record.conversationId || record.id,
title: record.title || record.conversationTitle,
createdAt: record.createTime || record.createdAt,
updatedAt: record.updateTime || record.updatedAt,
}))
}
export const fetchTacticsConversations = createAsyncThunk(
'tactics/fetchTacticsConversations',
async (_, { rejectWithValue }) => {
try {
const response = await fetchTacticsConversationPage({
pageNum: 1,
pageSize: 100,
})
const records = response.data?.records || []
return processTacticsConversationData(records)
}
catch (error) {
console.error('Failed to fetch tactics conversations:', error)
return rejectWithValue('Failed to fetch tactics conversations')
}
},
)
export const deleteTacticsConversations = createAsyncThunk<
boolean,
string[],
{ rejectValue: boolean }
>(
'tactics/deleteTacticsConversations',
async (conversationIds, { dispatch, rejectWithValue }) => {
try {
await fetchDeleteTacticsConversation(conversationIds)
await dispatch(fetchTacticsConversations())
return true
}
catch (error) {
console.error('Failed to delete tactics conversations:', error)
return rejectWithValue(false)
}
},
)
export const createTacticsConversation = createAsyncThunk<
{ conversation: TacticsConversation, shouldNavigate: boolean, shouldSendQuestion: string },
{ conversationData: Partial<TacticsConversation>, shouldNavigate: boolean, shouldSendQuestion: string }
>(
'tactics/createTacticsConversation',
async ({ conversationData, shouldNavigate, shouldSendQuestion }, { dispatch }) => {
const response = await fetchCreateTacticsConversation(conversationData)
const newConversation = response.data
await dispatch(fetchTacticsConversations())
return {
conversation: {
id: newConversation.conversationId || newConversation.id,
title: newConversation.title || newConversation.conversationTitle,
createdAt: newConversation.createTime || newConversation.createdAt,
updatedAt: newConversation.updateTime || newConversation.updatedAt,
},
shouldNavigate,
shouldSendQuestion,
}
},
)
const tacticsSlice = createSlice({
name: 'tactics',
initialState,
reducers: {
setCurrentTacticsConversation: (state, action: PayloadAction<string>) => {
state.currentConversationId = action.payload
},
clearCurrentTacticsConversation: (state) => {
state.currentConversationId = null
},
clearTacticsShouldSendQuestion: (state) => {
state.shouldSendQuestion = ''
},
setTacticsShouldSendQuestion: (state, action: PayloadAction<string>) => {
state.shouldSendQuestion = action.payload
},
addTacticsConversation: (state, action: PayloadAction<TacticsConversation>) => {
state.conversations.unshift(action.payload)
},
clearTacticsNavigationFlag: (state) => {
state.shouldNavigateToNewConversation = false
},
removeTacticsConversation: (state, action: PayloadAction<string>) => {
state.conversations = state.conversations.filter(conv => conv.id !== action.payload)
if (state.currentConversationId === action.payload) {
state.currentConversationId = state.conversations[0]?.id || null
}
},
},
extraReducers: (builder) => {
builder
.addCase(fetchTacticsConversations.pending, (state) => {
state.isLoading = true
state.error = null
})
.addCase(fetchTacticsConversations.fulfilled, (state, action) => {
state.isLoading = false
state.conversations = action.payload
})
.addCase(fetchTacticsConversations.rejected, (state, action) => {
state.isLoading = false
state.error = action.payload as string
})
.addCase(createTacticsConversation.pending, (state) => {
state.isLoading = true
state.error = null
})
.addCase(createTacticsConversation.fulfilled, (state, action) => {
state.isLoading = false
if (action.payload.shouldNavigate) {
state.currentConversationId = action.payload.conversation.id
}
state.shouldNavigateToNewConversation = action.payload.shouldNavigate
state.shouldSendQuestion = action.payload.shouldSendQuestion
})
.addCase(createTacticsConversation.rejected, (state, action) => {
state.isLoading = false
state.error = action.error.message || 'Failed to create tactics conversation'
})
.addCase(deleteTacticsConversations.fulfilled, () => {
})
.addCase(deleteTacticsConversations.rejected, () => {
})
},
})
export const {
setCurrentTacticsConversation,
clearCurrentTacticsConversation,
clearTacticsNavigationFlag,
clearTacticsShouldSendQuestion,
setTacticsShouldSendQuestion,
} = tacticsSlice.actions
export default tacticsSlice.reducer
// 问答功能独立的类型定义
export interface TacticsConversation {
id: string
title?: string
createdAt?: string
updatedAt?: string
}
export interface TacticsConversationState {
conversations: TacticsConversation[]
currentConversationId: string | null
isLoading: boolean
error: string | null
shouldNavigateToNewConversation: boolean
shouldSendQuestion: string
}
export interface TacticsChatRecord {
role: 'system' | 'user' | 'ai'
question?: string
answer?: string
groupId?: string
recordId?: string
conversationId?: string
qaTime?: string
}
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