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
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"screenfull": "^6.0.2",
"uuid": "^14.0.2",
"vue": "^3.5.38",
"vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1",
"vue-demi": "^0.14.10",
"vue-draggable-plus": "^0.6.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { messages, loading, hasMore, loadMore } = useLogMessageListStore()
</script>

<template>
<ReverseInfiniteScroll :loading="loading" :has-more="hasMore" :count="messages.length" :load-more="loadMore">
<ReverseInfiniteScroll class="p-6" :loading="loading" :has-more="hasMore" :count="messages.length" :load-more="loadMore">
<template v-for="(msg, i) in messages" :key="msg.id || i">
<div :class="[msg.role === 'USER' ? 'user' : 'assistant']">
<ContentList :content-list="msg.content" />
Expand Down
28 changes: 16 additions & 12 deletions ui/src/conversation-panel/core/layout/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, useSlots } from 'vue'
import { computed, Teleport as SidePanelTeleport, useSlots } from 'vue'
import type { SidePanelMode } from './types'

const props = withDefaults(
Expand All @@ -10,6 +10,7 @@ const props = withDefaults(
rightMode?: SidePanelMode
leftWidth?: number
rightWidth?: number
rightDrawerTarget?: HTMLElement
}>(),
{
leftMode: 'push',
Expand All @@ -23,6 +24,7 @@ defineEmits<{ 'mask-click': [] }>()

const slots = useSlots()
const hasRight = computed(() => !!slots.right)
const teleportRightDrawer = computed(() => props.rightMode === 'drawer' && !!props.rightDrawerTarget)

const showMask = computed(() => (props.leftMode === 'drawer' && props.leftOpen) || (props.rightMode === 'drawer' && props.rightOpen))
</script>
Expand All @@ -40,16 +42,18 @@ const showMask = computed(() => (props.leftMode === 'drawer' && props.leftOpen)

<slot name="main" />

<aside
v-if="hasRight"
class="mk-conversation-side-panel right shrink-0 overflow-hidden"
:class="[rightMode, { open: rightOpen }]"
:style="{ '--mk-chat-right-width': rightWidth + 'px' }"
>
<div class="mk-conversation-side-panel__inner"><slot name="right" /></div>
</aside>

<!-- 点击遮罩收起抽屉侧栏 -->
<div v-if="showMask" class="mk-conversation-mask" @click="$emit('mask-click')" />
<SidePanelTeleport :to="props.rightDrawerTarget || 'body'" :disabled="!teleportRightDrawer">
<aside
v-if="hasRight"
class="mk-conversation-side-panel right shrink-0 overflow-hidden"
:class="[rightMode, { open: rightOpen }]"
:style="{ '--mk-chat-right-width': rightWidth + 'px' }"
>
<div class="mk-conversation-side-panel__inner"><slot name="right" /></div>
</aside>

<!-- 点击遮罩收起抽屉侧栏 -->
<div v-if="showMask" class="mk-conversation-mask" @click="$emit('mask-click')" />
</SidePanelTeleport>
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ defineExpose({ scrollToBottom, followBottom })
<div class="min-h-full flex-1 flex-column">
<template v-if="count > 0">
<!-- 固定预留状态行,加载提示切换时不改变消息位置。 -->
<div class="flex-center py-4 shrink-0">
<span v-if="loadingMore" class="mk-conversation-loading" />
<div v-if="loadingMore" class="flex-center py-4 shrink-0">
<span class="mk-conversation-loading" />
</div>
<slot />
</template>
Expand Down
2 changes: 2 additions & 0 deletions ui/src/conversation-panel/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@

&.right {
right: 0;
--mk-conversation-side-panel-width: 100%;
border-left: none !important;

&:not(.open) {
transform: translateX(100%);
Expand Down
78 changes: 17 additions & 61 deletions ui/src/conversation-panel/right-sidebar/execution-detail/index.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
<template>
<!-- 右侧内容区:执行详情。数据来自 view 层(消息区点击「执行详情」写入) -->
<div class="execution-detail-panel">
<div class="execution-detail-panel__header">
<span>执行详情</span>
<!-- 关闭执行详情 -->
<button type="button" class="execution-detail-panel__close" @click="closeRightSide()">
<MkIcon :icon="Close" />
</button>
</div>
<div class="execution-detail-panel__body">
<ExecutionDetailContent
v-if="detail && detail.length"
:detail="detail"
:workflow-mode="workflowMode"
/>
<el-empty v-else description="暂无执行详情" :image-size="80" />
</div>
</div>
</template>

<script setup lang="ts">
import { Close } from '@element-plus/icons-vue'
import ExecutionDetailContent from '@/workflow-canvas/execution-details/index.vue'
import { WorkflowMode } from '@/workflow-canvas/types'
import { useExecutionDetailStore } from './index'
Expand All @@ -32,42 +10,20 @@ withDefaults(defineProps<{ workflowMode?: WorkflowMode }>(), {
const { detail, closeRightSide } = useExecutionDetailStore()
</script>

<style scoped lang="scss">
.execution-detail-panel {
display: flex;
flex-direction: column;
height: 100%;
}
.execution-detail-panel__header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.execution-detail-panel__close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
background: transparent;
color: var(--el-text-color-secondary, #909399);
cursor: pointer;
}
.execution-detail-panel__close:hover {
background: rgba(0, 0, 0, 0.05);
}
.execution-detail-panel__body {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 12px;
}
</style>
<template>
<!-- 右侧内容区:执行详情。数据来自 view 层(消息区点击「执行详情」写入) -->
<div class="execution-detail-panel flex-column h-full">
<div class="flex-between shrink-0 px-6 py-4 border-b">
<h4>执行详情</h4>
<!-- 关闭执行详情 -->
<el-button text @click="closeRightSide()">
<MkIcon name="icon_close_outlined" size="18" />
</el-button>
</div>
<el-scrollbar class="min-h-0 flex-1" view-class="p-6">
<ExecutionDetailContent v-if="detail && detail.length" :detail="detail" :workflow-mode="workflowMode" />
<el-empty v-else description="暂无执行详情" :image-size="80" />
</el-scrollbar>
</div>
</template>
<style scoped lang="scss"></style>
10 changes: 8 additions & 2 deletions ui/src/conversation-panel/view/log/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ChatRecordApi = {
) => Promise<{ records: ChatLogRecord[] }>
}

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

Expand Down Expand Up @@ -48,7 +48,13 @@ watch(
</script>

<template>
<ConversationLayout :left-open="false" :right-open="detail.rightSideOpen.value" right-mode="drawer" @mask-click="detail.closeRightSide()">
<ConversationLayout
:left-open="false"
:right-open="detail.rightSideOpen.value"
right-mode="drawer"
:right-drawer-target="props.rightDrawerTarget"
@mask-click="detail.closeRightSide()"
>
<template #main>
<main class="mk-conversation-main">
<LogMessageList :key="props.chatId" />
Expand Down
3 changes: 3 additions & 0 deletions ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Element Plus 覆盖、主题变量和应用样式。Chat 入口还显式引入 E
`mk-conversation-side-panel__inner`。`.left` 引用 `--mk-chat-sidebar-width`,`.right`
引用 `--mk-chat-right-width`,通过侧栏内部变量统一内容固定宽度、push 展开宽度和 drawer
宽度。push 收起时外层宽度为零,drawer 通过位移收起;内容宽度不随开合动画变化。
`ConversationLayout` 可通过 `rightDrawerTarget` 传入 HTMLElement,仅在右侧为 drawer 模式时,
将右侧面板和遮罩一起 Teleport 到该定位容器;不传时保留布局内定位。目标容器需负责定位、
尺寸和溢出裁剪,并与会话共用生命周期。

## 共享输入容器

Expand Down
5 changes: 4 additions & 1 deletion ui/src/utils/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/** 提供跨页面复用的剪贴板操作。 */

import useClipboard from 'vue-clipboard3'
import { MsgSuccess } from '@/utils/message'

const { toClipboard } = useClipboard()

/** 将非空文本写入剪贴板,并在成功后显示提示。 */
export async function copyText(text: string | null | undefined, successMessage = '已复制') {
if (!text) return

await navigator.clipboard.writeText(text)
await toClipboard(text)
MsgSuccess(successMessage)
}
2 changes: 2 additions & 0 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ View 负责路由、资源与模式、图加载、保存/发布/调试编排
`ChatLogDetailDrawer` 使用现有 `LogConversation` 展示只读历史对话与执行详情,正文查询仍使用 Workspace API;上一条/下一条与跨页由页面管理。
抽屉通过 `content-class` 提供全高内容区,消息列表内部滚动;切换日志时以日志 ID 重建
`LogConversation`,隔离分页状态和未结束的请求,重置阅读位置与执行详情。
日志通过 `LogConversation.rightDrawerTarget` 将当前抽屉外壳传给会话布局,执行详情及遮罩
覆盖整个抽屉,包括标题与翻页区;关闭执行详情后返回原日志,详情内容独立滚动。
- 外部检索页从 context 读取四范围 ExternalRetrieval API;Workspace 共享只读,禁用开关并阻止提交。
查询失败展示重新加载入口,不展示默认开关;保存期间使用 loading 与 inert,失败保留已保存配置。
知识库或资源范围变化时重新查询;示例与地址来自响应,只读展示/复制,不在前端拼接。
Expand Down
19 changes: 14 additions & 5 deletions ui/src/views/application-detail/chat-log/ChatLogDetailDrawer.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { useApplicationDetailContext } from '../context'
import type { ChatLog } from '@/api/types'
import LogConversation from '@/conversation-panel/view/log/index.vue'
Expand All @@ -16,6 +16,9 @@ const emit = defineEmits<{ previous: []; next: []; closed: [] }>()

// 对话详情抽屉的打开与关闭
const visible = ref(false)
const conversationContainer = ref<HTMLElement>()
// 执行详情挂到当前抽屉外壳,覆盖标题和翻页区,并随抽屉一起销毁。
const rightDrawerTarget = computed(() => conversationContainer.value?.closest<HTMLElement>('.el-drawer') ?? undefined)

function open() {
visible.value = true
Expand All @@ -39,13 +42,19 @@ defineExpose({ open, close })
</script>

<template>
<MkDrawer v-model="visible" size="840" content-class="h-full min-h-0 flex-column" @close="close" @closed="emit('closed')">
<MkDrawer v-model="visible" size="840" content-class="h-full min-h-0 flex-column p-0!" @close="close" @closed="emit('closed')">
<template #header>
<h4 class="min-w-0 truncate" :title="props.chatLog.abstract">{{ props.chatLog.abstract || '对话详情' }}</h4>
<h4 class="min-w-0 truncate" :title="props.chatLog.abstract">{{ props.chatLog.abstract || '-' }}</h4>
</template>
<div v-loading="props.loading" class="min-h-0 flex-1">
<div ref="conversationContainer" v-loading="props.loading" class="min-h-0 flex-1">
<!-- 只读会话面板:展示该日志的历史对话与执行详情,无输入框 -->
<LogConversation :key="props.chatLog.id" :chat-id="props.chatLog.id" :application-id="applicationId" :chat-log-api="resourceApi.chatLog" />
<LogConversation
:key="props.chatLog.id"
:chat-id="props.chatLog.id"
:application-id="applicationId"
:chat-log-api="resourceApi.chatLog"
:right-drawer-target="rightDrawerTarget"
/>
</div>
<template #footer>
<!-- 浏览上一个对话日志 -->
Expand Down
11 changes: 0 additions & 11 deletions ui/src/views/application/components/ButtonCreateApplication.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,6 @@ function handleRefresh() {
<p class="text-sm text-N500 whitespace-normal">使用低代码拖拉拽方式,灵活编排复杂逻辑、功能丰富的智能体</p>
</div>
</MkDropdownItem>
<MkDropdownItem class="py-2!" @click="handleOpenAdvancedApplicationCreate">
<template #icon>
<el-avatar class="ai-avatar-gradient" shape="square" :size="24">
<img src="@/assets/workflow/icon_ai_chat.svg" style="width: 75%" alt="" />
</el-avatar>
</template>
<div class="min-w-0">
<p class="leading-5">AI 创建</p>
<p class="text-sm text-N500 whitespace-normal">根据描述,AI 自动生成工作流</p>
</div>
</MkDropdownItem>

<el-upload
ref="elUploadRef"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ defineExpose({ open })

<template #footer>
<div class="flex-between gap-3">
<el-button :disabled="disabled" :loading="applying" @click="handleApplyToAll">应用到所有节点</el-button>
<el-button plain :disabled="disabled" :loading="applying" @click="handleApplyToAll">应用到所有节点</el-button>
<div class="flex-align-center">
<el-button :disabled="applying" @click="visible = false">取消</el-button>
<el-button plain :disabled="applying" @click="visible = false">取消</el-button>
<el-button type="primary" :disabled="disabled || !hasChanges || applying" @click="handleSave">保存</el-button>
</div>
</div>
Expand Down
Loading