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
2 changes: 1 addition & 1 deletion ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ Workspace 智能体分页方法只接收分页与查询条件,从当前资源
### 对话接口

Admin 调试接口在 `admin/workspace/conversation.ts`,正式 Chat 接口在 `chat/conversation.ts`,
使用各自请求客户端。面板 `common/get-api.ts` 仅按 ChatType 选择完整 API,不拼 URL。
使用各自请求客户端。对话面板由各视图(`view/debug`、`view/chat`、`view/portal`)选择对应 API,不拼 URL。
Chat 协议及认证接口见 [chat/README.md](chat/README.md)。

历史消息和流式聚合复用 `@/api/types` 的 `ChatContent`,定义在 `types/chat-log.ts`;
Expand Down
4 changes: 3 additions & 1 deletion ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,9 @@ Workspace API 内部使用 `getWorkspaceId()`,System 共享 API 不接收工
正式对话的 `message-list` 根据会话 Store 的 `isShared` 展示分享选择态,同轮提问与回答同步选择,
内容只读且选中时显示背景;无记录 ID 的问答不可选。列表底部提供当前已加载轮次的全选、已选数量、
取消及创建链接,创建时按消息顺序提交记录 ID;成功退出分享,失败保留选择。分享期间主面板隐藏输入框,
消息列表卸载时退出分享态。分享入口仅在 `SHAREABLE_VIEWS`(Chat、Portal)显示,调试、日志等视图不提供分享服务。
消息列表卸载时退出分享态。分享入口按 `view/index.ts` 的 `useShowShare()` 显示:Chat、Portal 视图按智能体的
`show_share` provide `SHOW_SHARE_KEY`(门户还要求已选定智能体),调试、日志等视图不提供,即不显示。
门户侧栏与历史对话抽屉的分享入口跨多个智能体,暂不受 `show_share` 控制。
分享选择放在 `message-list/share/`:`index.ts` 的 `createShareLinkStore({ messages, share, setShared })` 维护
每条消息的记录 ID、已选记录、数量与全选状态、单条切换、全选、进入(`enterShare`)、提交(`copyShareLink`)
及退出(`exitShare`);提交成功后退出,失败保留选择。分享模式开关仍由会话 Store 的 `isShared` 维护,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import { copyText } from '@/utils/clipboard'

import { MESSAGE_LIST_KEY } from '../../../message-list/index'
import { CONVERSATION_LIST_KEY } from '../../../../left-sidebar/conversation-list/index'
import { useViewType } from '../../../../view/index'
import { SHAREABLE_VIEWS, useShareLinkStoreOptional, useShareRecordId } from '../../../message-list/share/index'
import { useShowShare } from '../../../../view/index'
import { useShareLinkStoreOptional, useShareRecordId } from '../../../message-list/share/index'

defineOptions({ name: 'ConversationQuestionContent' })

// 仅提供分享服务的视图(对话、门户)显示分享入口;进入分享时预先勾选所属轮次。
const showShare = SHAREABLE_VIEWS.includes(useViewType())
// 智能体允许分享时显示分享入口;进入分享时预先勾选所属轮次。
const showShare = useShowShare()
const shareLink = useShareLinkStoreOptional()
const shareRecordId = useShareRecordId()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code modifications introduce several logical issues and anti-patterns:

1. Violation of Vue Composition API Rules (Critical)
const showShare = useShowShare() executes useShowShare() immediately during the script setup phase. If useShowShare is a standard composable returning a reactive reference (e.g., Ref<boolean>), assigning it directly to a plain const means:

  • It might return a Ref object, not a boolean value, causing template checks (v-if="showShare") to always be truthy.
  • Even if it returns a primitive, it loses reactivity unless the composable itself manages complex internal state differently than standard patterns.
    Fix: Ensure useShowShare returns a reactive ref and use it correctly in the template, or destructure/compute if necessary. If it's a computed property inside, useShowShare should likely return a ComputedRef.

2. Reactivity Loss
Previously, SHAREABLE_VIEWS.includes(useViewType()) suggests useViewType() is a reactive getter or function call that was potentially evaluated once but relied on imported constants. Now, replacing it with a custom hook useShowShare() isolates logic appropriately, but you must ensure useShowShare properly tracks dependencies (like route params or view types) so that showShare updates dynamically when the user switches views.

3. Comment Alignment
The comment text updated from "views sharing service" to "agent allows sharing". Ensure the business logic inside useShowShare matches this new description.

4. Redundant Imports Check
Verify that useViewType is truly not used elsewhere in this file since it was removed. Similarly, ensure SHAREABLE_VIEWS is no longer needed.

Optimization Suggestion:
If useShowShare is simply a boolean check based on current route/view, consider if it could be derived directly via computed within this component for better locality, unless it is reused across multiple components (which justifies a separate hook).

Expand Down
8 changes: 3 additions & 5 deletions ui/src/conversation-panel/components/header/chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { computed } from 'vue'
import { useConversationListStore } from '../../../left-sidebar/conversation-list/index'
import { useMessageListStore } from '../../message-list/index'
import { useShareLinkStoreOptional } from '../../message-list/share/index'
import { useShowShare } from '../../../view/index'

const props = defineProps<{
/** 新建对话;不传时在当前应用中新建(门户在没有当前智能体时改为选择智能体) */
newConversation?: () => void
/** 显示分享入口;仅对话端传入 */
shareable?: boolean
}>()
const list = useConversationListStore()

Expand All @@ -17,7 +16,7 @@ const msgs = useMessageListStore()
const shareLink = useShareLinkStoreOptional()
// 回答生成中或没有消息时不可分享
const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLoading.value)

const showShare = useShowShare()
const handleNewConversation = () => (props.newConversation ? props.newConversation() : list.newConversation())
</script>

Expand All @@ -44,12 +43,11 @@ const handleNewConversation = () => (props.newConversation ? props.newConversati
</el-button>
</MkTooltip>
<!-- 进入分享模式,勾选要分享的对话 -->
<MkTooltip v-if="shareable" content="分享">
<MkTooltip v-if="showShare" content="分享">
<el-button text :disabled="!canShare" @click="shareLink?.enterShare()">
<MkIcon name="icon_share_outlined" :size="18" :class="canShare ? 'text-N900!' : 'text-N400!'" />
</el-button>
</MkTooltip>

<slot />
</template>
</header>
Expand Down
6 changes: 3 additions & 3 deletions ui/src/conversation-panel/components/header/portal/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
import { computed } from 'vue'
import { useConversationListStore } from '../../../left-sidebar/conversation-list/index'
import { useMessageListStore } from '../../message-list/index'
import { useShowShare } from '../../../view/index'

const props = defineProps<{
/** 新建对话;不传时在当前应用中新建(门户在没有当前智能体时改为选择智能体) */
newConversation?: () => void
/** 显示分享入口;仅对话端传入 */
shareable?: boolean
}>()

const emit = defineEmits<{ share: [] }>()
Expand All @@ -16,6 +15,7 @@ const list = useConversationListStore()
const msgs = useMessageListStore()
// 回答生成中或没有消息时不可分享
const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLoading.value && !msgs.loading.value)
const showShare = useShowShare()

const handleNewConversation = () => (props.newConversation ? props.newConversation() : list.newConversation())
</script>
Expand Down Expand Up @@ -48,7 +48,7 @@ const handleNewConversation = () => (props.newConversation ? props.newConversati
</el-button>
</MkTooltip>
<!-- 进入分享模式,勾选要分享的对话 -->
<MkTooltip v-if="shareable" content="分享">
<MkTooltip v-if="showShare" content="分享">
<el-button text :disabled="!canShare" @click="emit('share')">
<MkIcon name="icon_share_outlined" :size="18" class="text-N900!" />
</el-button>
Expand Down
8 changes: 4 additions & 4 deletions ui/src/conversation-panel/components/message-footer/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type { ChatMessage, ChatRecordMeta } from '../../core/types'
import { useExecutionDetailStore } from '../../right-sidebar/execution-detail/index'

import { MESSAGE_LIST_KEY } from '../message-list/index'
import { SHAREABLE_VIEWS, useShareLinkStoreOptional } from '../message-list/share/index'
import { useViewType } from '../../view/index'
import { useShareLinkStoreOptional } from '../message-list/share/index'
import { useShowShare } from '../../view/index'

const props = defineProps<{ message: ChatMessage }>()
const emit = defineEmits<{ resize: [] }>()
Expand All @@ -25,8 +25,8 @@ onMounted(() => {
})
onBeforeUnmount(() => footerObserver?.disconnect())

// 仅提供分享服务的视图(对话、门户)显示分享入口;进入分享时预先勾选当前回答所属轮次。
const showShare = SHAREABLE_VIEWS.includes(useViewType())
// 智能体允许分享时显示分享入口;进入分享时预先勾选当前回答所属轮次。
const showShare = useShowShare()
const shareLink = useShareLinkStoreOptional()
// 回答生成中禁用分享。
const messageList = inject(MESSAGE_LIST_KEY, null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { computed, inject, ref, type InjectionKey, type Ref } from 'vue'
import type { ChatMessage } from '../../../core/types'
import { VIEW_TYPE, type ViewType } from '../../../view/index'

/** 提供分享服务的视图;其他视图不显示进入分享选择的入口。 */
export const SHAREABLE_VIEWS: readonly ViewType[] = [VIEW_TYPE.CHAT, VIEW_TYPE.PORTAL]

export interface ShareLinkDeps {
/** 当前会话消息:只读,由消息列表 Store 维护 */
Expand Down
5 changes: 0 additions & 5 deletions ui/src/conversation-panel/core/enums.ts

This file was deleted.

3 changes: 0 additions & 3 deletions ui/src/conversation-panel/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { CHAT_TYPE } from './enums'
import type { ChatFeedback, ChatApplicationProfile, ChatContent, ChatLogRecord, ChatUserProfile, Dict } from '@/api/types'

/**
Expand Down Expand Up @@ -53,8 +52,6 @@ interface StreamContentBatch {
/** SSE 支持批量内容分片,也支持直接发送单个内容项。 */
export type StreamChunk = StreamContentBatch | ChatContent

export type ChatType = (typeof CHAT_TYPE)[keyof typeof CHAT_TYPE]

// 通用「发起/续跑一次对话」的参数。message-input、表单节点等任何能发起对话的地方共用。
export interface SendMessageOptions {
chatId?: string // 目标会话 id;新建会话时由调用方(message-input)惰性生成后传入,缺省沿用当前已加载会话
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import AccountMenu from '../account-menu/index.vue'

import { useMessageListStore } from '../../components/message-list/index'
import { useShareLinkStoreOptional } from '../../components/message-list/share/index'
import { useViewType, VIEW_TYPE, type ViewType } from '../../view/index'
import { useShowShare, useViewType, VIEW_TYPE, type ViewType } from '../../view/index'

defineOptions({ name: 'ConversationList' })

Expand All @@ -22,6 +22,7 @@ const SHOW_PROFILE: readonly ViewType[] = [VIEW_TYPE.CHAT, VIEW_TYPE.PORTAL]
const msgs = useMessageListStore()
// 会话菜单分享默认全选,内容分享由内容入口指定记录。
const shareLink = useShareLinkStoreOptional()
const showShare = useShowShare()
const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLoading.value && !msgs.loading.value)

const { appInfo, conversations, currentChatId, selectConversation, newConversation, deleteChat, toggleLeftSide, hasMore, loadMore } =
Expand Down Expand Up @@ -123,7 +124,7 @@ function handleClearConversations() {
<MkDropdownMenu>
<!-- 激活所选对话并全选分享 -->
<MkDropdownItem
v-if="viewType === VIEW_TYPE.CHAT"
v-if="showShare"
:disabled="msgs.loading.value || (currentChatId === conversation.id && !canShare)"
@click="handleShareConversation(conversation)"
>
Expand Down
9 changes: 1 addition & 8 deletions ui/src/conversation-panel/main/chat-panel/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ import MessageInput from '../../components/message-input/index.vue'
import { useConversationListStore } from '../../left-sidebar/conversation-list/index'
import { useMessageInputStore } from '../../components/message-input/index'

defineProps<{
/** 标题栏显示分享入口;仅对话端传入 */
shareable?: boolean
}>()

const emit = defineEmits<{ share: [] }>()

const list = useConversationListStore()
const input = useMessageInputStore()
// 拖拽到面板任意处 → 交给 message-input 接收文件
Expand All @@ -25,7 +18,7 @@ const disclaimerText = computed(() => (list.appInfo.value?.disclaimer ? list.app
<main class="mk-conversation-main" @drop.prevent="handleDrop" @dragover.prevent>
<!-- 头部可被替换(如 debug 用 debug header);默认普通 chat header -->
<slot name="header">
<ChatHeader :shareable="shareable" @share="emit('share')" />
<ChatHeader />
</slot>
<!-- 聊天背景铺满标题栏以下的消息区与输入区,与显示设置预览一致 -->
<div

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an analysis of the provided code diff:

1. Irregularities & Potential Issues

  • Breaking Change (API Reduction): The shareable prop and share event have been completely removed from this component's interface. If any parent components are still passing :shareable="true" or listening for @share, they will likely cause Vue warnings (unknown props/events) or fail to trigger sharing functionality.
  • Lost Functionality: Unless the ChatHeader component now handles shareability logic internally independently of external props, the ability to control share visibility from outside this component is lost. Verify if ChatHeader has hard-coded default behavior for sharing that satisfies all previous use cases.

2. Optimization Suggestions

  • Check Parent Components: Ensure all callsites of this component have been updated to remove references to shareable and @share.
  • Review ChatHeader: Confirm that ChatHeader still supports sharing in scenarios where it was previously explicitly enabled/disabled by the parent. If sharing should always be hidden by default, this change simplifies the API. If it should be contextual, consider restoring the prop prop or moving the state to a Pinia store/Vuex module if global access is needed.

Summary

The code cleanup simplifies the component signature but is a breaking change. It is safe only if no external consumer relies on the removed shareable prop or share event.

Expand Down
2 changes: 1 addition & 1 deletion ui/src/conversation-panel/main/portal-chat-panel/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const disclaimerText = computed(() =>
<!-- 门户对话主面板:header + message-list + message-input -->
<main class="mk-conversation-main" @drop.prevent="handleDrop" @dragover.prevent>
<!-- 收起侧栏时标题栏的新建对话与侧栏一致:没有当前智能体时选择智能体 -->
<PortalHeader :new-conversation="portalList.newConversation" shareable @share="shareLink?.enterShare()" />
<PortalHeader :new-conversation="portalList.newConversation" @share="shareLink?.enterShare()" />
<MessageList :key="list.currentChatId.value" v-if="portalList.currentApplicationId.value" :app-name="list.appInfo.value?.name || 'AI 助手'" />
<!-- 未确定智能体时不显示欢迎语:分组加载完成且没有可用智能体时显示空状态 -->
<div v-else class="flex-center min-h-0 flex-1">
Expand Down
9 changes: 7 additions & 2 deletions ui/src/conversation-panel/view/chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { MESSAGE_INPUT_KEY } from '../../components/message-input/index'
import { EXECUTION_DETAIL_KEY } from '../../right-sidebar/execution-detail/index'
import { SHARE_LINK_KEY } from '../../components/message-list/share/index'
import { createChatConversation, createChatUserProfileGetter } from './index'
import { VIEW_KEY, VIEW_TYPE, DEVICE_KEY, CHAT_USER_PROFILE_KEY, createDeviceType } from '../index.ts'
import { VIEW_KEY, VIEW_TYPE, DEVICE_KEY, CHAT_USER_PROFILE_KEY, SHOW_SHARE_KEY, createDeviceType } from '../index.ts'

const bundle = createChatConversation()

Expand All @@ -38,6 +38,11 @@ provide(MESSAGE_INPUT_KEY, bundle.input)
provide(EXECUTION_DETAIL_KEY, bundle.detail)
provide(SHARE_LINK_KEY, bundle.share)
provide(VIEW_KEY, VIEW_TYPE.CHAT)
// 智能体开启「允许分享对话记录」时显示标题栏、会话菜单、提问与回答上的分享入口
provide(
SHOW_SHARE_KEY,
computed(() => Boolean(bundle.list.appInfo.value?.show_share)),
)
// HTML 内容 iframe 内 chatUserProfile() 取当前对话用户
provide(CHAT_USER_PROFILE_KEY, createChatUserProfileGetter())

Expand Down Expand Up @@ -83,7 +88,7 @@ watch([() => bundle.list.currentChatId.value, () => bundle.list.composerResetSig
@mask-click="closeDrawers"
>
<template #left><ConversationList /></template>
<template #main><ChatPanel shareable /></template>
<template #main><ChatPanel /></template>
<template #right><ExecutionDetail /></template>
</ConversationLayout>
</template>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the review of the code changes:

1. Potential Issues

  • Removal of Static Prop: You removed shareable from <ChatPanel />. Ensure that ChatPanel and its child components (title bar, menu, etc.) are now strictly relying on the injected SHOW_SHARE_KEY computed property for their visibility logic. If any part of ChatPanel still expects the shareable prop or fails to fall back correctly when it's missing, UI elements might disappear unexpectedly.
  • Reactivity Dependency: The computed property depends on bundle.list.appInfo.value?.show_share. Ensure that:
    1. bundle.list.appInfo is a reactive ref.
    2. Updating appInfo triggers the re-computation of this provided value.
    3. Consumers of SHOW_SHARE_KEY use inject and access .value correctly if injecting as a ref/computed.

2. Optimization Suggestions

  • Null Check Consistency: The code uses optional chaining (?.) which is good. However, Boolean(...) is generally preferred over !! for readability, which you have done. No change needed here, just confirming it's a best practice.
  • Type Safety: Ensure show_share is expected to be a boolean or numeric/falsy truthy value in your type definition for appInfo. If it's strictly boolean | undefined, Boolean() handles the undefined case correctly (returns false).

3. Style & Best Practices

  • Comments: The added Chinese comment is clear and helpful for context maintenance. Consider translating it to English if your codebase requires English comments for consistency.
  • Formatting: The provide call is well-formatted across multiple lines. This is clean.

4. Summary

The change effectively shifts the "shareability" logic from a static, per-component prop to a dynamic, app-level contextual state via Vue's provide/inject. This is a superior architectural choice if share permissions depend on global configuration (appInfo) rather than local component instance states.

Action Required: Verify that all components previously relying on the shareable prop on ChatPanel have been updated to inject(SHOW_SHARE_KEY).

Expand Down
7 changes: 7 additions & 0 deletions ui/src/conversation-panel/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export const CHAT_USER_PROFILE_KEY: InjectionKey<GetChatUserProfile> = Symbol('c
export function useChatUserProfileGetter(): GetChatUserProfile {
return inject(CHAT_USER_PROFILE_KEY, () => Promise.resolve(null))
}

/** 是否显示进入分享选择的入口;Chat、Portal 视图按智能体的 show_share 提供,调试、日志等视图不提供即不显示。 */
export const SHOW_SHARE_KEY: InjectionKey<Readonly<Ref<boolean>>> = Symbol('conversation-show-share')

export function useShowShare(): Readonly<Ref<boolean>> {
return inject(SHOW_SHARE_KEY, () => computed(() => false), true)
}
7 changes: 6 additions & 1 deletion ui/src/conversation-panel/view/portal/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { PORTAL_CONVERSATION_LIST_KEY } from '../../left-sidebar/portal-conversa
import { SHARE_LINK_KEY } from '../../components/message-list/share/index'
import { createPortalConversation } from './index'
import { createChatUserProfileGetter } from '../chat/index'
import { VIEW_KEY, VIEW_TYPE, DEVICE_KEY, CHAT_USER_PROFILE_KEY, createDeviceType } from '../index.ts'
import { VIEW_KEY, VIEW_TYPE, DEVICE_KEY, CHAT_USER_PROFILE_KEY, SHOW_SHARE_KEY, createDeviceType } from '../index.ts'

const applicationSelectorRef = ref<InstanceType<typeof SelectApplicationDrawer>>()
const bundle = createPortalConversation({ openApplicationSelector: () => applicationSelectorRef.value?.open() })
Expand All @@ -44,6 +44,11 @@ provide(EXECUTION_DETAIL_KEY, bundle.detail)
provide(SHARE_LINK_KEY, bundle.share)
provide(PORTAL_CONVERSATION_LIST_KEY, bundle.portalList)
provide(VIEW_KEY, VIEW_TYPE.PORTAL)
// 当前智能体开启「允许分享对话记录」时显示标题栏、提问与回答上的分享入口
provide(
SHOW_SHARE_KEY,
computed(() => Boolean(bundle.portalList.currentApplicationId.value && bundle.list.appInfo.value?.show_share)),
)
// HTML 内容 iframe 内 chatUserProfile() 取当前对话用户
provide(CHAT_USER_PROFILE_KEY, createChatUserProfileGetter())
// 响应式:窄屏 drawer,宽屏 push
Expand Down
Loading