Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions packages/plugins/robot/src/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
v-model:show="robotVisible"
v-model:input="inputMessage"
:status="mappedStatus"
:chat-mode="robotSettingState.chatMode"
:prompt-items="promptItems"
:bubble-renderers="bubbleRenderers"
:allowFiles="isVisualModel && robotSettingState.chatMode === ChatMode.Agent"
:show-aborted="robotSettingState.chatMode !== ChatMode.Agent"
:message-content-resolver="resolveChatMessageContent"
:beforeSubmit="checkApiKey"
:promptClickHandler="promptClickHandler"
@fileSelected="handleFileSelected"
Expand Down Expand Up @@ -106,6 +106,7 @@ const { robotSettingState, getModelCapabilities, updateThinkingState, getSelecte

const robotVisible = ref(false)
const fullscreen = ref(false)
const inputMessage = ref('')

watch(robotVisible, (visible) => {
useLayout().layoutState.toolbars.render = visible ? META_APP.Robot : ''
Expand Down Expand Up @@ -150,7 +151,6 @@ const showSetting = ref(false)

const {
mappedStatus,
inputMessage,
messages,
changeChatMode,
abortRequest,
Expand Down Expand Up @@ -248,6 +248,55 @@ const openAIRobot = () => {
// 当前Robot的bubbleRenderers无法做到响应式更新,因此Agent模式的type要与Chat模式不同
const bubbleRenderers = { 'agent-content': AgentRenderer, 'agent-loading': AgentRenderer }

const resolveChatMessageContent = (message: any, context: { messages: any[]; status: string }) => {
const hasAgentContent = message.renderContent?.some((item: any) => {
return item.type === 'agent-content' || item.type === 'agent-loading'
})
const isAgentMessage = message.metadata?.chatMode === 'agent' || hasAgentContent

if (!isAgentMessage || message.role !== 'assistant') {
return Array.isArray(message.renderContent) && message.renderContent.length > 0
? message.renderContent
: message.content
Comment thread
lichunn marked this conversation as resolved.
}

const isLastMessage = context.messages.at(-1) === message
const isGenerating = Boolean(message.loading) || (isLastMessage && context.status !== 'finished')
const renderContent = isGenerating
? message.renderContent || []
: (message.renderContent || []).filter((item: any) => item.type !== 'agent-loading')
const agentContents = renderContent.filter((item: any) => item.type === 'agent-content')
const finalStatus = agentContents.findLast((item: any) => ['success', 'failed', 'fix'].includes(item.status))?.status

if (!Array.isArray(message.renderContent) || message.renderContent.length === 0) {
const agentStatus = ['success', 'failed', 'fix'].includes(message.metadata?.agentStatus)
? message.metadata.agentStatus
: 'failed'
return [
{
type: 'agent-content',
status: agentStatus,
content: message.content
}
]
}

return renderContent.map((item: any) => {
if (item.type !== 'agent-content' || isGenerating) {
return item
}

if (!item.status || item.status === 'loading') {
return {
...item,
status: finalStatus || message.metadata?.agentStatus || 'failed'
}
}

return item
})
}

const handleFileSelected = async (formData: FormData, updateAttachment: (resourceUrl: string) => void) => {
try {
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
Expand Down
139 changes: 74 additions & 65 deletions packages/plugins/robot/src/components/chat/RobotChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ const props = defineProps({
type: Function
},
status: { type: String },
chatMode: { type: String },
messageContentResolver: {
type: Function
},
allowFiles: {
type: Boolean,
default: false
Expand Down Expand Up @@ -169,51 +171,35 @@ const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) =>
!message.loading && message.content && (!content?.type || ['markdown', 'text'].includes(content.type)),
find: (_message: any, content: any) => !content?.type || ['markdown', 'text'].includes(content.type),
renderer: MarkdownRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
find: (_message: any, content: any) => ['img', 'image'].includes(content?.type),
renderer: ImgRenderer
}
])

const isAgentMessage = (message: any) => {
const hasAgentContent = message.renderContent?.some((item: any) => {
return item.type === 'agent-content' || item.type === 'agent-loading'
})
return message.metadata?.chatMode === 'agent' || hasAgentContent
}

const resolveAgentRenderContent = (message: any) => {
if (!isAgentMessage(message) || message.role !== 'assistant') {
return message.renderContent
const getTextContent = (content: any) => {
if (typeof content === 'string') {
return content
}

const isLastMessage = messages.value.at(-1) === message
const isGenerating = Boolean(message.loading) || (isLastMessage && GeneratingStatus.includes(props.status as any))
const renderContent = isGenerating
? message.renderContent
: message.renderContent.filter((item: any) => item.type !== 'agent-loading')
const agentContents = renderContent.filter((item: any) => item.type === 'agent-content')
const finalStatus = agentContents.findLast((item: any) => ['success', 'failed', 'fix'].includes(item.status))?.status

return renderContent.map((item: any) => {
if (item.type !== 'agent-content' || isGenerating) {
return item
}

if (!item.status || item.status === 'loading') {
return {
...item,
status: finalStatus || message.metadata?.agentStatus || 'failed'
}
}

return item
})
if (Array.isArray(content)) {
return content
.map((item) => {
if (typeof item === 'string') {
return item
}
if (item?.type === 'text') {
return item.text ?? item.content ?? ''
}
return ''
})
.filter(Boolean)
.join('\n')
}
return ''
}

// 处理文件选择事件
Expand Down Expand Up @@ -272,21 +258,43 @@ const aiAvatar = getSvgIcon('AI')
const welcomeIcon = getSvgIcon('AI', { fontSize: '44px' })

const resolveMessageContent = (message: any) => {
if (Array.isArray(message.renderContent) && message.renderContent.length > 0) {
return resolveAgentRenderContent(message)
if (props.messageContentResolver) {
return props.messageContentResolver(message, {
messages: messages.value,
status: props.status
})
}

if (isAgentMessage(message) && message.role === 'assistant' && message.content) {
const agentStatus = ['success', 'failed', 'fix'].includes(message.metadata?.agentStatus)
? message.metadata.agentStatus
: 'failed'
return [
{
type: 'agent-content',
status: agentStatus,
content: message.content
if (Array.isArray(message.renderContent) && message.renderContent.length > 0) {
return message.renderContent.map((item: any) => {
if (item?.type === 'img' || item?.type === 'image') {
return {
type: 'img',
content: item.content || item.url || item.image_url?.url || ''
}
}
]
if (item?.type === 'text') {
return {
type: 'text',
content: item.content ?? item.text ?? ''
}
}
return item
})
}

if (Array.isArray(message.content) && message.content.length > 0) {
const textContent = getTextContent(
message.content.map((item: any) => item?.text ?? item?.content ?? item?.image_url?.url ?? '')
)
if (textContent) {
return textContent
}
}

const textContent = getTextContent(message.content)
if (textContent) {
return textContent
}

return message.content
Expand Down Expand Up @@ -326,28 +334,29 @@ const handleSendMessage = async (content: string) => {
}
const files = selectedAttachments.value.filter((item) => item.status === 'success')
if (files.length > 0) {
const fileMessages: ChatMessage[] = files.map((file) => ({
role: 'user',
content: '',
renderContent: [
{
type: 'img',
content: file.url
}
]
}))
messages.value.push(...fileMessages)
userMessage.content = files
.map((item) => ({
userMessage.content = [
{
type: 'text',
text: messageContent
},
...files.map((item) => ({
type: 'image_url',
image_url: {
url: item.url
}
}))
.concat({
] as any
userMessage.renderContent = [
{
type: 'text',
text: messageContent
})
content: messageContent
},
...files.map((item) => ({
type: 'img',
content: item.url
}))
]
} else {
userMessage.renderContent = [
{
type: 'text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ hljs.registerLanguage('xml', xml)
hljs.registerLanguage('shell', shell)

interface MarkdownMessage {
content: string
content: string | string[] | Record<string, unknown>[]
}

const props = defineProps({
Expand Down Expand Up @@ -71,7 +71,15 @@ const markdownIt = new MarkdownIt({
})

const renderContent = computed(() => {
return DOMPurify.sanitize(markdownIt.render(props.message.content))
const content = Array.isArray(props.message.content)
? props.message.content
.map((item: any) => item?.text ?? item?.content ?? '')
.filter(Boolean)
.join('\n')
: typeof props.message.content === 'string'
? props.message.content
: ''
Comment thread
lichunn marked this conversation as resolved.
return DOMPurify.sanitize(markdownIt.render(content))
})
</script>

Expand Down
31 changes: 27 additions & 4 deletions packages/plugins/robot/src/composables/core/useConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ export interface ConversationMetadata {

let currentConversationMetadata: ConversationMetadata = {}

const extractMessageText = (content: unknown): string => {
if (typeof content === 'string') {
return content
}

if (Array.isArray(content)) {
return content
.map((item: any) => {
if (typeof item === 'string') {
return item
}
if (item?.type === 'text') {
return item.text ?? item.content ?? ''
}
return ''
})
.filter(Boolean)
.join('\n')
}

return ''
}

const createResponseProvider = (
provider: Pick<OpenAICompatibleProvider, 'chatStream'>
): UseMessageOptions['responseProvider'] => {
Expand Down Expand Up @@ -180,7 +203,7 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {

const saveConversations = () => {
conversations.value.forEach((conversation) => {
void saveConversation(conversation)
saveConversation(conversation)
})
}

Expand All @@ -198,7 +221,7 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
currentConversationMetadata = conversation.metadata
}
conversation.updatedAt = Date.now()
void saveConversation(conversation)
saveConversation(conversation)
}

const updateTitle = (conversationId: string, title?: string) => {
Expand Down Expand Up @@ -258,7 +281,7 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
}
}
currentConversationMetadata = conversation.metadata || {}
void saveConversation(conversation)
saveConversation(conversation)
return currentId
}
}
Expand Down Expand Up @@ -328,7 +351,7 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
const currentTitle = currentConversation.title
if (currentTitle === defaultTitle && currentId) {
const messageContent = getActiveEngine()?.messages.value.find((item) => item.role === 'user')?.content
const contentStr = typeof messageContent === 'string' ? messageContent : JSON.stringify(messageContent)
const contentStr = extractMessageText(messageContent) || JSON.stringify(messageContent)
updateTitle(currentId, contentStr.substring(0, 20))
}
}
Expand Down
Loading
Loading