Skip to content
Merged
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
10 changes: 6 additions & 4 deletions ui/src/conversation-panel/components/log-message-list/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ref, inject, type InjectionKey } from 'vue'
import { ref, inject, type InjectionKey, type Ref } from 'vue'
import type { ChatLogRecord } from '@/api/types'
import type { ChatMessage, ChatRecordMeta } from '../../core/types'
import type { ChatMessage, ChatRecordMeta, ConversationAppInfo } from '../../core/types'

// 只读会话记录(如对话日志):只分页拉取历史记录,不发送、不续跑、不流式。
// 与 message-list 刻意不共用 store 约定,也不注入 MESSAGE_LIST_KEY,表单等内容项因此自动只读。
export interface LogMessageListDeps {
pageRecords: (chatId: string, page: number, size: number) => Promise<{ records: ChatLogRecord[] }>
getChatId: () => string
/** 当前智能体信息:只读,由 view 加载维护。 */
appInfo: Readonly<Ref<ConversationAppInfo | null>>
}

// 从后端 chat_record(reset_chat_record 结构)提取底部信息栏数据
Expand All @@ -19,7 +21,7 @@ const buildRecordMeta = (raw: ChatLogRecord): ChatRecordMeta => ({
})

export function createLogMessageListStore(deps: LogMessageListDeps) {
const { pageRecords, getChatId } = deps
const { pageRecords, getChatId, appInfo } = deps

const messages = ref<ChatMessage[]>([])
const loading = ref(false)
Expand Down Expand Up @@ -72,7 +74,7 @@ export function createLogMessageListStore(deps: LogMessageListDeps) {
return loadMessages(cid, currentPage.value + 1)
}

return { messages, loading, hasMore, load, loadMore }
return { appInfo, messages, loading, hasMore, load, loadMore }
}

export type LogMessageListStore = ReturnType<typeof createLogMessageListStore>
Expand Down
16 changes: 13 additions & 3 deletions ui/src/conversation-panel/components/log-message-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@
import ContentList from '../content-list/index.vue'
import MessageFooter from '../message-footer/index.vue'
import ReverseInfiniteScroll from '../../core/reverse-infinite-scroll/index.vue'
import { computed } from 'vue'
import { useLogMessageListStore } from './index'

const { messages, loading, hasMore, loadMore } = useLogMessageListStore()
const { appInfo, messages, loading, hasMore, loadMore } = useLogMessageListStore()
const avatar = computed(() => appInfo.value?.avatar)
const userAvatar = computed(() => appInfo.value?.user_avatar)
const showAvatar = computed(() => appInfo.value?.show_avatar)
const showUserAvatar = computed(() => appInfo.value?.show_user_avatar)
</script>

<template>
<ReverseInfiniteScroll content-class="p-4" :loading="loading" :has-more="hasMore" :count="messages.length" :load-more="loadMore">
<template v-for="(msg, i) in messages" :key="msg.id || i">
<!-- msg.role === 'USER' -->
<div>
<!-- 用户提问靠右:头像独占一行在上,提问容器在下方靠右收缩 -->
<div v-if="msg.role === 'USER'" class="flex flex-col items-end">
<ApplicationIcon v-if="showUserAvatar" :icon="userAvatar" :size="24" class="mb-2" />
<ContentList :content-list="msg.content" />
</div>
<div v-else>
<ApplicationIcon v-if="showAvatar" :icon="avatar" :size="24" class="shrink-0 mr-2" />
<ContentList :content-list="msg.content" />
<!-- 助手消息底部信息栏(知识来源/执行详情/tokens/耗时) -->
<MessageFooter v-if="msg.role === 'ASSISTANT' && msg.record" :message="msg" />
Expand Down
31 changes: 30 additions & 1 deletion ui/src/conversation-panel/view/debug/app-info.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
/** debug 模式的智能体信息:由 Admin 智能体详情与显示设置转为对话端 profile 结构。 */
/** Admin 端(调试、对话日志)的智能体信息:由 Admin 智能体详情与显示设置转为对话端 profile 结构。 */

import { computed, ref, watch, type ComputedRef, type Ref } from 'vue'
import { cloneDeep } from 'lodash'
import WorkspaceApplicationApi from '@/api/admin/workspace/application/application'
import SystemResourceApplicationApi from '@/api/admin/system/resource-management/application/application'
import type { ApplicationDetail, ApplicationDisplaySetting, Dict } from '@/api/types'
import { APPLICATION_DISPLAY_SETTING_DEFAULT } from '@/constants/application'
import { useStore } from '@/stores'
import { isSystemResource } from '@/utils/resource-context'
import type { ConversationAppInfo } from '../../core/types'

const BASE_NODE_ID = 'base-node'
Expand Down Expand Up @@ -71,3 +76,27 @@ export function toDebugAppInfo(detail: ApplicationDetail, displayConfig?: Partia
chat_background: display.chat_background,
}
}

/**
* Admin 端智能体信息:随传入的详情更新;显示设置只有许可证有效的专业版、企业版提供,加载一次,
* 社区版或加载失败时使用默认值。显示设置不在调试、日志页编辑,无需重新加载。
*/
export function useAdminAppInfo(
application: Readonly<Ref<ApplicationDetail | null | undefined>>,
applicationId: Readonly<Ref<string>>,
): ComputedRef<ConversationAppInfo | null> {
const ApplicationApi = isSystemResource() ? SystemResourceApplicationApi : WorkspaceApplicationApi
const { auth } = useStore()
const displaySetting = ref<Partial<ApplicationDisplaySetting> | null>(null)
watch(
() => auth.isPE || auth.isEE,
(licensed) => {
if (!licensed || displaySetting.value || !applicationId.value) return
ApplicationApi.getDisplaySetting(applicationId.value)
.then((setting) => (displaySetting.value = setting))
.catch(() => {})
},
{ immediate: true },
)
return computed(() => (application.value ? toDebugAppInfo(application.value, displaySetting.value) : null))
}
31 changes: 6 additions & 25 deletions ui/src/conversation-panel/view/debug/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { computed, ref, watch, type Ref } from 'vue'
import { computed, type Ref } from 'vue'
import { useRoute } from 'vue-router'
import WorkspaceConversationApi from '@/api/admin/workspace/conversation'
import SystemResourceConversationApi from '@/api/admin/system/resource-management/application/conversation'
import WorkspaceApplicationApi from '@/api/admin/workspace/application/application'
import SystemResourceApplicationApi from '@/api/admin/system/resource-management/application/application'
import { isSystemResource } from '@/utils/resource-context'
import { useStore } from '@/stores'
import FileApi from '@/api/admin/file'
import { FILE_SOURCE_TYPE } from '@/api/enums'
import type { ApplicationDetail, ApplicationDisplaySetting } from '@/api/types'
import type { ApplicationDetail } from '@/api/types'
import type { ConversationApiAdapter } from '../../core/types'
import { createConversationListStore } from '../../left-sidebar/conversation-list/index'
import { createMessageListStore } from '../../components/message-list/index'
import { createMessageInputStore } from '../../components/message-input/index'
import { createExecutionDetailStore } from '../../right-sidebar/execution-detail/index'
import type { ConversationBundle } from '../types'
import { toDebugAppInfo } from './app-info'
import { useAdminAppInfo } from './app-info'

/**
* debug 模式:装配各组件 store。adapter 走 admin(带 applicationId)。
Expand All @@ -24,7 +22,6 @@ import { toDebugAppInfo } from './app-info'
export function createDebugConversation(application: Readonly<Ref<ApplicationDetail | null | undefined>>): ConversationBundle {
const route = useRoute()
const ConversationApi = isSystemResource() ? SystemResourceConversationApi : WorkspaceConversationApi
const ApplicationApi = isSystemResource() ? SystemResourceApplicationApi : WorkspaceApplicationApi
const appId = computed(
() => (route.params.id as string) || (route.params.applicationId as string) || '',
)
Expand All @@ -39,25 +36,9 @@ export function createDebugConversation(application: Readonly<Ref<ApplicationDet
uploadFile: (file, cid, onProgress) => FileApi.postUploadFile(file, cid, FILE_SOURCE_TYPE.CHAT, onProgress).request,
cancel: (cid) => ConversationApi.postCancelConversationMessage(cid, appId.value),
}
// 显示设置只有许可证有效的专业版、企业版提供;社区版或加载失败时使用默认值。
// 知识来源、执行详情在调试中固定显示,不需要访问配置。显示设置不在工作流页编辑,加载一次即可。
const { auth, user } = useStore()
const displaySetting = ref<Partial<ApplicationDisplaySetting> | null>(null)
watch(
() => auth.isPE || auth.isEE,
(licensed) => {
if (!licensed || displaySetting.value || !appId.value) return
ApplicationApi.getDisplaySetting(appId.value)
.then((setting) => (displaySetting.value = setting))
.catch(() => {})
},
{ immediate: true },
)

// 智能体信息随工作流页保存同步,转为 profile 结构后注入各组件 store
const appInfo = computed(() =>
application.value ? toDebugAppInfo(application.value, displaySetting.value) : null,
)
const { user } = useStore()
// 智能体信息随工作流页保存同步,转为 profile 结构后注入各组件 store;知识来源、执行详情在调试中固定显示
const appInfo = useAdminAppInfo(application, appId)

// currentChatId 单一数据源在 list;msgs 只读借用,list 通过 bindLoader 主动驱动加载
const list = createConversationListStore({
Expand Down
24 changes: 24 additions & 0 deletions ui/src/conversation-panel/view/log/app-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** 对话日志的智能体信息:由 Admin 智能体详情转为对话端 profile 结构。 */

import type { ApplicationDetail } from '@/api/types'
import type { ConversationAppInfo } from '../../core/types'

/**
* 只读日志只回放历史记录:不显示开场白、不发送消息,也不受显示设置影响。
* 只保留智能体基础信息,访问与展示配置按日志场景固定:显示知识来源与执行详情,不显示头像,没有分享。
*/
export function toLogAppInfo(detail: ApplicationDetail): ConversationAppInfo {
return {
id: detail.id,
name: detail.name,
icon: detail.icon ?? '',
desc: detail.desc,
type: detail.type,
prologue: detail.prologue,
show_source: true,
show_exec: true,
show_share: false,
show_avatar: false,
show_user_avatar: false,
}
}
25 changes: 19 additions & 6 deletions ui/src/conversation-panel/view/log/index.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { provide, watch } from 'vue'
import { computed, provide, watch } from 'vue'
import ChatLogApi from '@/api/admin/workspace/application/chat-log'
import type { ChatLogRecord } from '@/api/types'
import type { ApplicationDetail, ChatLogRecord } from '@/api/types'
import ConversationLayout from '../../core/layout/index.vue'
import LogMessageList from '../../components/log-message-list/index.vue'
import ExecutionDetail from '../../right-sidebar/execution-detail/index.vue'
import { LOG_MESSAGE_LIST_KEY, createLogMessageListStore } from '../../components/log-message-list/index'
import { EXECUTION_DETAIL_KEY, createExecutionDetailStore } from '../../right-sidebar/execution-detail/index'
import { toLogAppInfo } from './app-info'

defineOptions({ name: 'LogConversation' })

Expand All @@ -20,16 +21,28 @@ type ChatRecordApi = {
) => Promise<{ records: ChatLogRecord[] }>
}

const props = withDefaults(defineProps<{ chatId: string; applicationId?: string; chatLogApi?: ChatRecordApi; rightDrawerTarget?: HTMLElement }>(), {
chatLogApi: () => ChatLogApi,
})

const props = withDefaults(
defineProps<{
chatId: string
applicationId?: string
/** 智能体详情,由详情页上下文提供,组装只读日志的智能体信息。 */
application?: ApplicationDetail | null
chatLogApi?: ChatRecordApi
rightDrawerTarget?: HTMLElement
}>(),
{
chatLogApi: () => ChatLogApi,
},
)
// 智能体信息由详情组装为 profile 结构,注入消息列表 store;日志不受显示设置影响,不加载显示设置
const appInfo = computed(() => (props.application ? toLogAppInfo(props.application) : null))
// 只读日志:只读消息列表 + 执行详情,无会话列表、无输入框。
// 不注入 MESSAGE_LIST_KEY,表单等内容项拿不到发送能力即自动只读。
const msgs = createLogMessageListStore({
// 降序取(最新在前),store 页内 reverse 成正序、上滑加载更旧
pageRecords: (cid, p, s) => props.chatLogApi.getChatRecordLog(props.applicationId || '', cid, { currentPage: p, pageSize: s }, false),
getChatId: () => props.chatId,
appInfo,
})
const detail = createExecutionDetailStore()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const props = defineProps<{
nextDisabled: boolean
}>()

const { applicationId, resourceApi } = useApplicationDetailContext()
const { application, applicationId, resourceApi } = useApplicationDetailContext()
const emit = defineEmits<{ previous: []; next: []; closed: [] }>()

// 对话详情抽屉的打开与关闭
Expand Down Expand Up @@ -52,6 +52,7 @@ defineExpose({ open, close })
:key="props.chatLog.id"
:chat-id="props.chatLog.id"
:application-id="applicationId"
:application="application"
:chat-log-api="resourceApi.chatLog"
:right-drawer-target="rightDrawerTarget"
/>
Expand Down
Loading