* useConversation composable
* 提供会话管理和持久化功能
*/
import { computed, reactive, watch, type ComputedRef } from 'vue';
import type { ChatMessage, ConversationState, Conversation, UseConversationOptions } from '@opentiny/tiny-robot-kit';
import { MessageManager } from './messageManager';
import { useMessage, type UseMessageReturn } from './useMessage';
import { IndexedDBStrategy } from './indexedDBStrategy';
import { useI18n } from '../i18n';
* useConversation返回值接口
*/
export interface UseConversationReturn {
state: ConversationState;
messageManager: ComputedRef<UseMessageReturn>;
createConversation: (title?: string, metadata?: Record<string, unknown>) => string;
switchConversation: (id: string) => void;
deleteConversation: (id: string) => void;
updateTitle: (id: string, title: string) => void;
updateMetadata: (id: string, metadata: Record<string, unknown>) => void;
saveConversations: () => Promise<void>;
loadConversations: () => Promise<void>;
generateTitle: (id: string) => Promise<string>;
getCurrentConversation: () => Conversation | null;
}
* 生成唯一ID
*/
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
}
* useConversation composable
* 提供会话管理和持久化功能
*
* @param options useConversation选项
* @returns UseConversationReturn
*/
export function useConversation(options: UseConversationOptions): UseConversationReturn {
const { t } = useI18n();
const {
client,
storage = new IndexedDBStrategy(),
autoSave = true,
allowEmpty = false,
useStreamByDefault = true,
errorMessage: errorMessageOption,
events,
} = options;
const errorMessage = errorMessageOption || t('message.requestFailed');
const state = reactive<ConversationState>({
conversations: [],
currentId: null,
loading: false,
});
let hasTriggeredOnLoaded = false;
const messageManager = new MessageManager(state, {
client,
useStreamByDefault,
conversationId: '',
errorMessage,
initialMessages: [],
events: {
onReceiveData: events?.onReceiveData,
onFinish: events?.onFinish,
},
});
const currentMessageManager = computed<UseMessageReturn>(() => {
if (state.currentId) {
return messageManager.getMessageManager(state.currentId);
}
return useMessage({
client,
useStreamByDefault,
conversationId: '',
errorMessage,
initialMessages: [],
events: {
onReceiveData: events?.onReceiveData,
onFinish: events?.onFinish,
},
});
});
watch(
() => currentMessageManager.value,
() => {
messageManager.messageManagerMap.forEach((messageManager) => {
const messages = messageManager.messages.value;
if (state.currentId && messages.length > 0) {
const index = state.conversations.findIndex((row: Conversation) => row.id === messageManager.conversationId);
if (index !== -1) {
state.conversations[index].messages = [...messages];
state.conversations[index].updatedAt = Date.now();
if (autoSave) {
saveConversations();
}
}
}
});
},
{ deep: true },
);
* 创建新会话
*/
const createConversation = (title?: string, metadata: Record<string, unknown> = {}): string => {
const defaultTitle = title || t('conversation.newConversation');
if (!allowEmpty && currentMessageManager.value.messages.value.length === 0 && state.currentId) {
return state.currentId;
}
const id = generateId();
const newConversation: Conversation = {
id,
title: defaultTitle,
createdAt: Date.now(),
updatedAt: Date.now(),
messages: [],
metadata,
};
state.conversations.unshift(newConversation);
messageManager.setMessageManager(id, { ...options, conversationId: id });
switchConversation(id);
if (autoSave) {
saveConversations();
}
return id;
};
* 切换会话
*/
const switchConversation = (id: string): void => {
const conversation = state.conversations.find((conv: Conversation) => conv.id === id);
if (conversation) {
state.currentId = id;
messageManager.setMessageManager(id, { ...options, initialMessages: conversation.messages, conversationId: id });
}
};
* 删除会话
*/
const deleteConversation = (id: string): void => {
const index = state.conversations.findIndex((conv: Conversation) => conv.id === id);
if (index !== -1) {
messageManager.deleteMessageManager(id);
state.conversations.splice(index, 1);
if (state.currentId === id) {
if (state.conversations.length > 0) {
switchConversation(state.conversations[0].id);
} else {
state.currentId = null;
}
}
if (autoSave) {
saveConversations();
}
}
};
* 更新会话标题
*/
const updateTitle = (id: string, title: string): void => {
const conversation = state.conversations.find((conv: Conversation) => conv.id === id);
if (conversation) {
conversation.title = title;
conversation.updatedAt = Date.now();
if (autoSave) {
saveConversations();
}
}
};
* 更新会话元数据
*/
const updateMetadata = (id: string, metadata: Record<string, unknown>): void => {
const conversation = state.conversations.find((conv: Conversation) => conv.id === id);
if (conversation) {
conversation.metadata = { ...conversation.metadata, ...metadata };
conversation.updatedAt = Date.now();
if (autoSave) {
saveConversations();
}
}
};
* 保存会话
*/
const saveConversations = async (): Promise<void> => {
try {
await storage.saveConversations(state.conversations);
} catch (error) {
console.error('保存会话失败:', error);
}
};
* 加载会话
*/
const loadConversations = async (): Promise<void> => {
state.loading = true;
try {
const conversations = await storage.loadConversations();
state.conversations = conversations;
if (conversations.length > 0 && !state.currentId) {
switchConversation(conversations[0].id);
}
if (!hasTriggeredOnLoaded && events?.onLoaded) {
hasTriggeredOnLoaded = true;
events.onLoaded(conversations);
}
} catch (error) {
console.error('加载会话失败:', error);
} finally {
state.loading = false;
}
};
* 生成会话标题
* 基于会话内容自动生成标题
*/
const generateTitle = async (id: string): Promise<string> => {
const conversation = state.conversations.find((conv: Conversation) => conv.id === id);
if (!conversation || conversation.messages.length < 2) {
return conversation?.title || t('conversation.newConversation');
}
try {
const prompt: ChatMessage = {
role: 'system',
content:
'请根据以下对话内容,生成一个简短的标题(不超过20个字符)。只需要返回标题文本,不需要任何解释或额外内容。',
};
const contextMessages = conversation.messages.slice(0, Math.min(4, conversation.messages.length));
const response = await client.chat({
messages: [prompt, ...contextMessages],
options: {
stream: false,
max_tokens: 30,
},
});
const title = response.choices[0].message.content.trim();
updateTitle(id, title);
return title;
} catch (error) {
console.error('生成标题失败:', error);
return conversation.title;
}
};
* 获取当前会话
*/
const getCurrentConversation = (): Conversation | null => {
if (!state.currentId) return null;
return state.conversations.find((conv: Conversation) => conv.id === state.currentId) || null;
};
loadConversations();
return {
state,
messageManager: currentMessageManager,
createConversation,
switchConversation,
deleteConversation,
updateTitle,
updateMetadata,
saveConversations,
loadConversations,
generateTitle,
getCurrentConversation,
};
}