diff --git a/apps/application/serializers/application_chat_link.py b/apps/application/serializers/application_chat_link.py index f295d823406..039b906651a 100644 --- a/apps/application/serializers/application_chat_link.py +++ b/apps/application/serializers/application_chat_link.py @@ -1,10 +1,11 @@ """ - @project: MaxKB - @Author: niu - @file: application_chat_link.py - @date: 2026/2/9 10:50 - @desc: +@project: MaxKB +@Author: niu +@file: application_chat_link.py +@date: 2026/2/9 10:50 +@desc: """ + import re from django.utils.translation import gettext_lazy as _ @@ -18,42 +19,20 @@ class ShareChatRecordModelSerializer(serializers.ModelSerializer): - execution_details = serializers.SerializerMethodField() - class Meta: model = ChatRecord - fields = ['id', 'problem_text', 'answer_text', 'answer_text_list', - 'create_time', 'execution_details'] - - @staticmethod - def get_execution_details(chat_record): - details = chat_record.details or {} - # 分享是公开链接,只挑 start-node 的上传文件列表,避免泄露其它执行详情 - return [ - { - 'type': 'start-node', - 'image_list': v.get('image_list', []), - 'document_list': v.get('document_list', []), - 'audio_list': v.get('audio_list', []), - 'video_list': v.get('video_list', []), - 'other_list': v.get('other_list', []), - } - for v in details.values() if v.get('type') == 'start-node' - ] + fields = ["id", "problem_text", "answer_text", "answer_text_list", "create_time", "question", "messages"] class ChatRecordShareLinkRequestSerializer(serializers.Serializer): chat_record_ids = serializers.ListSerializer( - child=serializers.UUIDField(), - required=False, - allow_empty=False, - label=_("Chat record IDs") + child=serializers.UUIDField(), required=False, allow_empty=False, label=_("Chat record IDs") ) is_current_all = serializers.BooleanField(required=False, default=False) def validate(self, attrs): - if not attrs.get('is_current_all') and not attrs.get('chat_record_ids'): - raise serializers.ValidationError(_('Chat record ids can not be empty')) + if not attrs.get("is_current_all") and not attrs.get("chat_record_ids"): + raise serializers.ValidationError(_("Chat record ids can not be empty")) return attrs @@ -64,8 +43,8 @@ def extract_oss_file_urls(answer_text_list): if not isinstance(answer_group, list): answer_group = [answer_group] for item in answer_group: - content = item.get('content', '') - urls = re.findall(r'\./oss/file/[\w-]+', content) + content = item.get("content", "") + urls = re.findall(r"\./oss/file/[\w-]+", content) file_urls.extend(urls) return file_urls @@ -76,7 +55,7 @@ def save_public_file_access(chat_record_list): for chat_record in chat_record_list: urls = extract_oss_file_urls(chat_record.answer_text_list) for url in urls: - file_id = url.replace('./oss/file/', '') + file_id = url.replace("./oss/file/", "") if file_id: file_ids.add(file_id) @@ -84,19 +63,15 @@ def save_public_file_access(chat_record_list): return existing = set( - PublicFileAccess.objects.filter( - source_type='FILE', - source_id__in=list(file_ids) - ).values_list('source_id', flat=True) + PublicFileAccess.objects.filter(source_type="FILE", source_id__in=list(file_ids)).values_list( + "source_id", flat=True + ) ) new_records = [ - PublicFileAccess( - id=uuid.uuid7(), - source_type='FILE', - source_id=file_id - ) - for file_id in file_ids if file_id not in existing + PublicFileAccess(id=uuid.uuid7(), source_type="FILE", source_id=file_id) + for file_id in file_ids + if file_id not in existing ] if new_records: @@ -110,50 +85,54 @@ class ChatRecordShareLinkSerializer(serializers.Serializer): def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) - chat_id = self.data.get('chat_id') - application_id = self.data.get('application_id') - user_id = self.data.get('user_id') + chat_id = self.data.get("chat_id") + application_id = self.data.get("application_id") + user_id = self.data.get("user_id") chat_query_set = Chat.objects.filter( id=chat_id, application_id=application_id, chat_user_id=user_id, is_deleted=False ) if not chat_query_set.exists(): - raise AppApiException(500, _('Chat id does not exist')) + raise AppApiException(500, _("Chat id does not exist")) def generate_link(self, instance, with_valid=True): if with_valid: request_serializer = ChatRecordShareLinkRequestSerializer(data=instance) request_serializer.is_valid(raise_exception=True) self.is_valid(raise_exception=True) - if not instance.get('is_current_all', False): - chat_record_ids: list[str] = instance.get('chat_record_ids') + if not instance.get("is_current_all", False): + chat_record_ids: list[str] = instance.get("chat_record_ids") - record_count = ChatRecord.objects.filter(id__in=chat_record_ids, - chat_id=self.data.get('chat_id')).count() + record_count = ChatRecord.objects.filter( + id__in=chat_record_ids, chat_id=self.data.get("chat_id") + ).count() if record_count != len(chat_record_ids): - raise AppApiException(500, _('Invalid chat record ids')) - chat_id = self.data.get('chat_id') - application_id = self.data.get('application_id') - user_id = self.data.get('user_id') + raise AppApiException(500, _("Invalid chat record ids")) + chat_id = self.data.get("chat_id") + application_id = self.data.get("application_id") + user_id = self.data.get("user_id") - is_current_all = instance.get('is_current_all', False) + is_current_all = instance.get("is_current_all", False) if is_current_all: sorted_ids = list( - ChatRecord.objects.filter(chat_id=chat_id).order_by('create_time').values_list('id',flat=True) + ChatRecord.objects.filter(chat_id=chat_id).order_by("create_time").values_list("id", flat=True) ) else: - chat_record_ids: list[str] = instance.get('chat_record_ids') - sorted_ids = list(ChatRecord.objects.filter(id__in=chat_record_ids).order_by('create_time').values_list('id',flat=True)) + chat_record_ids: list[str] = instance.get("chat_record_ids") + sorted_ids = list( + ChatRecord.objects.filter(id__in=chat_record_ids).order_by("create_time").values_list("id", flat=True) + ) existing = ChatShareLink.objects.filter( - chat_id=chat_id, application_id=application_id, + chat_id=chat_id, + application_id=application_id, share_type=ShareLinkType.PUBLIC, user_id=user_id, - chat_record_ids=sorted_ids + chat_record_ids=sorted_ids, ).first() if existing: - return {'link': UUIDEncoder.encode(existing.id)} + return {"link": UUIDEncoder.encode(existing.id)} chat_record_list = ChatRecord.objects.filter(id__in=sorted_ids) save_public_file_access(chat_record_list) chat_share_link_model = ChatShareLink( @@ -162,13 +141,13 @@ def generate_link(self, instance, with_valid=True): application_id=application_id, share_type=ShareLinkType.PUBLIC, user_id=user_id, - chat_record_ids=sorted_ids + chat_record_ids=sorted_ids, ) chat_share_link_model.save() link = UUIDEncoder.encode(chat_share_link_model.id) - return {'link': link} + return {"link": link} class ChatShareLinkDetailSerializer(serializers.Serializer): @@ -177,29 +156,25 @@ class ChatShareLinkDetailSerializer(serializers.Serializer): def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) - link = self.data.get('link') + link = self.data.get("link") share_link_id = UUIDEncoder.decode_to_str(link) share_link_query_set = ChatShareLink.objects.filter(id=share_link_id).first() if not share_link_query_set: - raise AppApiException(500, _('Share link does not exist')) + raise AppApiException(500, _("Share link does not exist")) if share_link_query_set.chat.is_deleted: - raise AppApiException(500, _('Chat has been deleted')) + raise AppApiException(500, _("Chat has been deleted")) return share_link_query_set def get_record_list(self): share_link_model = self.is_valid(raise_exception=True) - chat_record_model_list = ChatRecord.objects.filter(id__in=share_link_model.chat_record_ids, - chat_id=share_link_model.chat_id).order_by('create_time') + chat_record_model_list = ChatRecord.objects.filter( + id__in=share_link_model.chat_record_ids, chat_id=share_link_model.chat_id + ).order_by("create_time") - abstract = Chat.objects.filter( - id=share_link_model.chat_id - ).values_list('abstract', flat=True).first() + abstract = Chat.objects.filter(id=share_link_model.chat_id).values_list("abstract", flat=True).first() chat_record_list = ShareChatRecordModelSerializer(chat_record_model_list, many=True).data - return { - 'abstract': abstract, - 'chat_record_list': chat_record_list - } + return {"abstract": abstract, "chat_record_list": chat_record_list} diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 85ac393d39f..8ff79d3c478 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -806,9 +806,9 @@ Workspace 和 System 资源管理的 `getKnowledgeDetail(knowledgeId, section?)` `chat/conversation.ts` 的 `getShareConversation(shareId)` 请求 Chat API 下的 `GET /share/`, 不需要 accessToken 或 applicationId。响应类型 `ChatShareDetail` 经 `@/api/types` 导出,包含 -`abstract` 和按时间正序排列的 `chat_record_list`。当前后端返回旧版 `problem_text`、 -`answer_text`、`answer_text_list` 及仅含上传附件的 `execution_details`,不返回 V3 `question`、`messages`。 -分享页适配旧版字段,不通过原会话接口补查内容。 +`abstract` 和按时间正序排列的 `chat_record_list`。记录包含 V3 `question`、`messages`, +同时保留旧版 `problem_text`、`answer_text`、`answer_text_list`;分享页使用 `question`、`messages`, +不通过原会话接口补查内容。 ### 对话回答反馈 diff --git a/ui/src/api/types/chat-share.ts b/ui/src/api/types/chat-share.ts index adb9691b218..67be7811e08 100644 --- a/ui/src/api/types/chat-share.ts +++ b/ui/src/api/types/chat-share.ts @@ -1,13 +1,16 @@ import type { ChatContent } from './chat-log' -import type { Dict } from './common' -/** 公开分享接口返回的旧版问答记录。 */ +/** 公开分享接口返回的问答记录。 */ export interface ChatShareRecord { id: string + create_time?: string problem_text: string answer_text: string answer_text_list: (ChatContent | ChatContent[])[] - execution_details?: Dict[] + /** 提问内容项 */ + question: ChatContent + /** 回答内容项列表 */ + messages: ChatContent[] } export interface ChatShareDetail { diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 30869b079dc..3eb6a0fc9f3 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -969,10 +969,16 @@ Workspace API 内部使用 `getWorkspaceId()`,System 共享 API 不接收工 正式对话的 `message-list` 根据会话 Store 的 `isShared` 展示分享选择态,同轮提问与回答同步选择, 内容只读且选中时显示背景;无记录 ID 的问答不可选。列表底部提供当前已加载轮次的全选、已选数量、 取消及创建链接,创建时按消息顺序提交记录 ID;成功退出分享,失败保留选择。分享期间主面板隐藏输入框, -消息列表卸载时退出分享态;调试入口不提供分享服务。 -分享组件放在 `message-list/share/`:`ShareMessageRow` 通过插槽承载消息,按记录 ID 切换整轮选择; -`ShareFooter` 负责全选、数量、取消、创建链接及退出清理。两者通过 `v-model` 共享已选记录 ID, -各自在组件内获取分享服务与提交状态;`message-list/index.vue` 仅准备记录 ID、保存已选 ID 并组合组件。 +消息列表卸载时退出分享态。分享入口仅在 `SHAREABLE_VIEWS`(Chat、Portal)显示,调试、日志等视图不提供分享服务。 +分享选择放在 `message-list/share/`:`index.ts` 的 `createShareLinkStore({ messages, share, setShared })` 维护 +每条消息的记录 ID、已选记录、数量与全选状态、单条切换、全选、进入(`enterShare`)、提交(`copyShareLink`) +及退出(`exitShare`);提交成功后退出,失败保留选择。分享模式开关仍由会话 Store 的 `isShared` 维护, +Store 经 `setShared` 开关。所有分享入口调用 `enterShare(recordIds?)` 进入:提问与回答的入口传入所属轮次记录 ID +以预先勾选,标题栏、会话菜单与门户历史入口不传;传入的 ID 只保留当前可分享记录。`ShareMessageRow` 通过 +`SHARE_RECORD_ID_KEY` 向消息内容提供所属轮次记录 ID,内容项以 `useShareRecordId()` 读取。 +Chat、Portal 视图创建并 provide `SHARE_LINK_KEY`,组件通过 `useShareLinkStoreOptional()` 获取,未提供时不进入选择态。 +`ShareMessageRow` 通过插槽承载消息,`ShareFooter` 展示全选、数量、取消与创建链接并在卸载时退出分享态; +两者只绑定 Store 状态与方法,不维护逻辑。`message-list/index.vue` 只组合组件并传入 Store 提供的记录 ID。 消息 Store 的 `loadMore()` 在新增历史记录时返回 `true`,空页、失败或未执行时返回 `false`, 直接传给滚动容器。新会话及重置时 `hasMore` 为 `false`;已有会话主动加载首页,成功后再更新 @@ -987,10 +993,18 @@ Chat、Portal 和 Display 面板直接放置消息列表,并以当前会话 ID ### SharePanel(对话模块) -`conversation-panel/main/share-panel/index.vue` 展示只读问答,通过 `useSharePanelContentStore()` -读取所属视图提供的 `SHARE_PANEL_CONTENT_KEY`。正式对话视图接入当前会话数据,独立分享页 -提供公开接口返回的标题、消息及加载状态,不加载原会话历史。 -回答复制提取内容项正文,不传递内容数组给剪贴板。 +`conversation-panel/main/share-panel/index.vue` 是公开分享页的主面板,展示标题栏并组合 +`ShareMessageList`。`conversation-panel/view/share/index.vue` 接收 `shareId`,以 +`createSharePanelStore({ shareId })` 创建并 provide `SHARE_PANEL_KEY`,提供 `VIEW_TYPE.SHARE` 与设备类型, +在挂载及分享 ID 变化时调用 `load()`;面板把内部的消息列表 Store 提供给 `ShareMessageList`。 +分享视图不注入发送能力,表单内容项按只读视图展示。路由页 `views/chat/share/` 只读取地址中的分享 ID 并渲染该视图。 + +### ShareMessageList(对话模块) + +`conversation-panel/components/share-message-list/` 负责请求公开分享接口并只读展示问答。 +`createShareMessageListStore({ shareId })` 提供标题、消息、加载及失败状态,按记录的 `question`、`messages` +转换消息,不加载原会话历史、不分页;切换分享 ID 后丢弃旧请求结果。组件通过 `SHARE_MESSAGE_LIST_KEY` +注入 Store,失败时在空状态提供重试;回答复制提取内容项正文,不传递内容数组给剪贴板。 ## 文档维护 diff --git a/ui/src/conversation-panel/components/content/items/form.vue b/ui/src/conversation-panel/components/content/items/form.vue index 192334535b0..1d0ae4c910b 100644 --- a/ui/src/conversation-panel/components/content/items/form.vue +++ b/ui/src/conversation-panel/components/content/items/form.vue @@ -56,7 +56,7 @@ const submit = async () => { _submit.value = false } } -const READONLY_VIEWS: readonly ViewType[] = [VIEW_TYPE.LOG] +const READONLY_VIEWS: readonly ViewType[] = [VIEW_TYPE.LOG, VIEW_TYPE.SHARE] diff --git a/ui/src/conversation-panel/components/content/items/question/index.vue b/ui/src/conversation-panel/components/content/items/question/index.vue index 1138b1ff2a5..8448fb0b629 100644 --- a/ui/src/conversation-panel/components/content/items/question/index.vue +++ b/ui/src/conversation-panel/components/content/items/question/index.vue @@ -13,9 +13,16 @@ 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' defineOptions({ name: 'ConversationQuestionContent' }) +// 仅提供分享服务的视图(对话、门户)显示分享入口;进入分享时预先勾选所属轮次。 +const showShare = SHAREABLE_VIEWS.includes(useViewType()) +const shareLink = useShareLinkStoreOptional() +const shareRecordId = useShareRecordId() + // 复用对话的分享选择入口,回答生成中禁用。 const conversationList = inject(CONVERSATION_LIST_KEY, null) const messageList = inject(MESSAGE_LIST_KEY, null) @@ -143,8 +150,8 @@ function getAttachmentDescription(file: QuestionAttachment) { - - + + diff --git a/ui/src/conversation-panel/components/header/chat/index.vue b/ui/src/conversation-panel/components/header/chat/index.vue index 89241cffc32..089bb9d023e 100644 --- a/ui/src/conversation-panel/components/header/chat/index.vue +++ b/ui/src/conversation-panel/components/header/chat/index.vue @@ -2,6 +2,7 @@ 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' const props = defineProps<{ /** 新建对话;不传时在当前应用中新建(门户在没有当前智能体时改为选择智能体) */ @@ -12,6 +13,8 @@ const props = defineProps<{ const list = useConversationListStore() const msgs = useMessageListStore() +// 标题栏分享不针对某一轮,进入后不预先勾选 +const shareLink = useShareLinkStoreOptional() // 回答生成中或没有消息时不可分享 const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLoading.value) @@ -42,7 +45,7 @@ const handleNewConversation = () => (props.newConversation ? props.newConversati - + diff --git a/ui/src/conversation-panel/components/message-footer/index.vue b/ui/src/conversation-panel/components/message-footer/index.vue index 9668ea9dcb9..4ebbd017f44 100644 --- a/ui/src/conversation-panel/components/message-footer/index.vue +++ b/ui/src/conversation-panel/components/message-footer/index.vue @@ -8,12 +8,15 @@ import type { ChatMessage, ChatRecordMeta } from '../../core/types' import { useExecutionDetailStore } from '../../right-sidebar/execution-detail/index' import { MESSAGE_LIST_KEY } from '../message-list/index' -import { CONVERSATION_LIST_KEY } from '../../left-sidebar/conversation-list/index' +import { SHAREABLE_VIEWS, useShareLinkStoreOptional } from '../message-list/share/index' +import { useViewType } from '../../view/index' const props = defineProps<{ message: ChatMessage }>() -// 复用对话的分享选择入口,回答生成中禁用。 -const conversationList = inject(CONVERSATION_LIST_KEY, null) +// 仅提供分享服务的视图(对话、门户)显示分享入口;进入分享时预先勾选当前回答所属轮次。 +const showShare = SHAREABLE_VIEWS.includes(useViewType()) +const shareLink = useShareLinkStoreOptional() +// 回答生成中禁用分享。 const messageList = inject(MESSAGE_LIST_KEY, null) const canShare = computed(() => Boolean(messageList?.messages.value.length && !messageList.streamLoading.value)) @@ -77,9 +80,9 @@ const openExecutionDetail = () => { - + - + diff --git a/ui/src/conversation-panel/components/message-list/index.vue b/ui/src/conversation-panel/components/message-list/index.vue index 6a477c9f2c9..3b9018dfd9a 100644 --- a/ui/src/conversation-panel/components/message-list/index.vue +++ b/ui/src/conversation-panel/components/message-list/index.vue @@ -8,6 +8,7 @@ import ReverseInfiniteScroll from '../../core/reverse-infinite-scroll/index.vue' import { useMessageListStore, SEND_MESSAGE_KEY } from './index' import ShareMessageRow from './share/ShareMessageRow.vue' import ShareFooter from './share/ShareFooter.vue' +import { useShareLinkStoreOptional } from './share/index' import { useConversationListStore } from '../../left-sidebar/conversation-list/index' defineProps<{ appName?: string }>() @@ -15,12 +16,8 @@ const { appInfo, messages, loading, streamLoading, hasMore, loadMore, onStreamPr const { isShared } = useConversationListStore() // 消息内容项(如表单)经此发起续跑;分享时收回发送能力,内容项只读 provide(SEND_MESSAGE_KEY, computed(() => (isShared.value ? null : sendMessage))) -// 分享按完整问答轮次提交,提问与回答共用记录 ID。 -const selectedRecordIds = ref([]) -const messageRecordIds = computed(() => - messages.value.map((message, index) => (message.role === 'ASSISTANT' ? message.recordId : messages.value[index + 1]?.recordId)), -) -const availableRecordIds = computed(() => [...new Set(messageRecordIds.value.filter((id): id is string => Boolean(id)))]) +// 分享选择的记录 ID、勾选与提交由分享链接 Store 维护;未提供时(调试等)不进入分享选择。 +const shareLink = useShareLinkStoreOptional() const scrollRef = ref>() // 开场白随智能体信息变化(debug 保存后为最新草稿) @@ -52,7 +49,7 @@ const showUserAvatar = computed(() => appInfo.value?.show_user_avatar) diff --git a/ui/src/conversation-panel/components/message-list/share/ShareFooter.vue b/ui/src/conversation-panel/components/message-list/share/ShareFooter.vue index d6e95b772b9..014107e1094 100644 --- a/ui/src/conversation-panel/components/message-list/share/ShareFooter.vue +++ b/ui/src/conversation-panel/components/message-list/share/ShareFooter.vue @@ -1,61 +1,33 @@ diff --git a/ui/src/conversation-panel/view/index.ts b/ui/src/conversation-panel/view/index.ts index 144382a7be4..d6b74cdfbbc 100644 --- a/ui/src/conversation-panel/view/index.ts +++ b/ui/src/conversation-panel/view/index.ts @@ -8,6 +8,7 @@ export const VIEW_TYPE = { SIMPLE_DEBUG: 'SIMPLE_DEBUG', PORTAL: 'PORTAL', LOG: 'LOG', + SHARE: 'SHARE', } as const export type ViewType = (typeof VIEW_TYPE)[keyof typeof VIEW_TYPE] diff --git a/ui/src/conversation-panel/view/portal/index.vue b/ui/src/conversation-panel/view/portal/index.vue index 888912045a8..c492eb30314 100644 --- a/ui/src/conversation-panel/view/portal/index.vue +++ b/ui/src/conversation-panel/view/portal/index.vue @@ -14,7 +14,7 @@ import { MESSAGE_LIST_KEY } from '../../components/message-list/index' import { MESSAGE_INPUT_KEY } from '../../components/message-input/index' import { EXECUTION_DETAIL_KEY } from '../../right-sidebar/execution-detail/index' import { PORTAL_CONVERSATION_LIST_KEY } from '../../left-sidebar/portal-conversation-list/index' -import { SHARE_PANEL_KEY } from '../../main/share-panel/index' +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' @@ -41,7 +41,7 @@ provide(CONVERSATION_LIST_KEY, bundle.list) provide(MESSAGE_LIST_KEY, bundle.msgs) provide(MESSAGE_INPUT_KEY, bundle.input) provide(EXECUTION_DETAIL_KEY, bundle.detail) -provide(SHARE_PANEL_KEY, bundle.share) +provide(SHARE_LINK_KEY, bundle.share) provide(PORTAL_CONVERSATION_LIST_KEY, bundle.portalList) provide(VIEW_KEY, VIEW_TYPE.PORTAL) // HTML 内容 iframe 内 chatUserProfile() 取当前对话用户 diff --git a/ui/src/conversation-panel/view/share/index.vue b/ui/src/conversation-panel/view/share/index.vue new file mode 100644 index 00000000000..846ec75dcd3 --- /dev/null +++ b/ui/src/conversation-panel/view/share/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/ui/src/views/chat/share/index.vue b/ui/src/views/chat/share/index.vue index 0f4df42848a..998b89aefac 100644 --- a/ui/src/views/chat/share/index.vue +++ b/ui/src/views/chat/share/index.vue @@ -1,56 +1,16 @@ - -