新增 ChatDto 数据传输对象,更新聊天接口以支持知识库功能,优化聊天服务逻辑,调整前端组件以提升用户体验。

This commit is contained in:
ccmjga
2025-06-28 22:31:20 +08:00
parent 3e1d7e6fee
commit b6ecc929b0
30 changed files with 268 additions and 176 deletions

View File

@@ -1,5 +1,7 @@
package com.zl.mjga.config.ai; package com.zl.mjga.config.ai;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
import com.zl.mjga.component.PromptConfiguration; import com.zl.mjga.component.PromptConfiguration;
import com.zl.mjga.service.LlmService; import com.zl.mjga.service.LlmService;
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel; import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
@@ -72,6 +74,11 @@ public class ChatModelInitializer {
.embeddingModel(zhipuEmbeddingModel) .embeddingModel(zhipuEmbeddingModel)
.minScore(0.75) .minScore(0.75)
.maxResults(5) .maxResults(5)
.dynamicFilter(
query -> {
String libraryId = (String) query.metadata().chatMemoryId();
return metadataKey("libraryId").isEqualTo(libraryId);
})
.build()) .build())
.build(); .build();
} }

View File

@@ -2,6 +2,7 @@ package com.zl.mjga.controller;
import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageRequestDto;
import com.zl.mjga.dto.PageResponseDto; import com.zl.mjga.dto.PageResponseDto;
import com.zl.mjga.dto.ai.ChatDto;
import com.zl.mjga.dto.ai.LlmQueryDto; import com.zl.mjga.dto.ai.LlmQueryDto;
import com.zl.mjga.dto.ai.LlmVm; import com.zl.mjga.dto.ai.LlmVm;
import com.zl.mjga.exception.BusinessException; import com.zl.mjga.exception.BusinessException;
@@ -72,9 +73,9 @@ public class AiController {
} }
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(Principal principal, @RequestBody String userMessage) { public Flux<String> chat(Principal principal, @RequestBody ChatDto chatDto) {
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer(); Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
TokenStream chat = aiChatService.chatPrecedenceLlmWith(principal.getName(), userMessage); TokenStream chat = aiChatService.chat(principal.getName(), chatDto);
chat.onPartialResponse( chat.onPartialResponse(
text -> text ->
sink.tryEmitNext( sink.tryEmitNext(

View File

@@ -8,7 +8,6 @@ import com.zl.mjga.repository.LibraryRepository;
import com.zl.mjga.service.RagService; import com.zl.mjga.service.RagService;
import com.zl.mjga.service.UploadService; import com.zl.mjga.service.UploadService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -34,16 +33,16 @@ public class LibraryController {
@GetMapping("/libraries") @GetMapping("/libraries")
public List<Library> queryLibraries() { public List<Library> queryLibraries() {
return libraryRepository.findAll().stream().sorted( return libraryRepository.findAll().stream()
Comparator.comparing(Library::getId).reversed() .sorted(Comparator.comparing(Library::getId).reversed())
).toList(); .toList();
} }
@GetMapping("/docs") @GetMapping("/docs")
public List<LibraryDoc> queryLibraryDocs(@RequestParam Long libraryId) { public List<LibraryDoc> queryLibraryDocs(@RequestParam Long libraryId) {
return libraryDocRepository.fetchByLibId(libraryId).stream().sorted( return libraryDocRepository.fetchByLibId(libraryId).stream()
Comparator.comparing(LibraryDoc::getId).reversed() .sorted(Comparator.comparing(LibraryDoc::getId).reversed())
).toList(); .toList();
} }
@GetMapping("/segments") @GetMapping("/segments")

View File

@@ -0,0 +1,7 @@
package com.zl.mjga.dto.ai;
import com.zl.mjga.model.urp.ChatMode;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
public record ChatDto(@NotNull ChatMode mode, Long libraryId, @NotEmpty String message) {}

View File

@@ -0,0 +1,6 @@
package com.zl.mjga.model.urp;
public enum ChatMode {
NORMAL,
WITH_LIBRARY
}

View File

@@ -2,6 +2,7 @@ package com.zl.mjga.service;
import com.zl.mjga.config.ai.AiChatAssistant; import com.zl.mjga.config.ai.AiChatAssistant;
import com.zl.mjga.config.ai.SystemToolAssistant; import com.zl.mjga.config.ai.SystemToolAssistant;
import com.zl.mjga.dto.ai.ChatDto;
import com.zl.mjga.exception.BusinessException; import com.zl.mjga.exception.BusinessException;
import dev.langchain4j.service.TokenStream; import dev.langchain4j.service.TokenStream;
import java.util.Optional; import java.util.Optional;
@@ -39,8 +40,20 @@ public class AiChatService {
}; };
} }
public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) { public TokenStream chat(String sessionIdentifier, ChatDto chatDto) {
return switch (chatDto.mode()) {
case NORMAL -> chatWithPrecedenceLlm(sessionIdentifier, chatDto);
case WITH_LIBRARY -> chatWithLibrary(chatDto.libraryId(), chatDto);
};
}
public TokenStream chatWithLibrary(Long libraryId, ChatDto chatDto) {
return zhiPuChatAssistant.chat(String.valueOf(libraryId), chatDto.message());
}
public TokenStream chatWithPrecedenceLlm(String sessionIdentifier, ChatDto chatDto) {
LlmCodeEnum code = getPrecedenceLlmCode(); LlmCodeEnum code = getPrecedenceLlmCode();
String userMessage = chatDto.message();
return switch (code) { return switch (code) {
case ZHI_PU -> zhiPuChatAssistant.chat(sessionIdentifier, userMessage); case ZHI_PU -> zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
case DEEP_SEEK -> deepSeekChatAssistant.chat(sessionIdentifier, userMessage); case DEEP_SEEK -> deepSeekChatAssistant.chat(sessionIdentifier, userMessage);

View File

@@ -71,10 +71,6 @@ public class UploadService {
if (size > 1024 * 1024) { if (size > 1024 * 1024) {
throw new BusinessException("知识库文档大小不能超过1MB"); throw new BusinessException("知识库文档大小不能超过1MB");
} }
String contentType = multipartFile.getContentType();
if (!StringUtils.startsWith(contentType, "text/")) {
throw new BusinessException("非法的上传文件");
}
minioClient.putObject( minioClient.putObject(
PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream( PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream(
multipartFile.getInputStream(), size, -1) multipartFile.getInputStream(), size, -1)

View File

@@ -894,7 +894,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "string" "$ref": "#/components/schemas/ChatDto"
} }
} }
}, },
@@ -1580,7 +1580,8 @@
"DocUpdateDto": { "DocUpdateDto": {
"required": [ "required": [
"enable", "enable",
"id" "id",
"libId"
], ],
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1588,6 +1589,10 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"libId": {
"type": "integer",
"format": "int64"
},
"enable": { "enable": {
"type": "boolean" "type": "boolean"
} }
@@ -1868,6 +1873,29 @@
} }
} }
}, },
"ChatDto": {
"required": [
"message",
"mode"
],
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"NORMAL",
"WITH_LIBRARY"
]
},
"libraryId": {
"type": "integer",
"format": "int64"
},
"message": {
"type": "string"
}
}
},
"PageRequestDto": { "PageRequestDto": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -783,6 +783,8 @@ export interface components {
DocUpdateDto: { DocUpdateDto: {
/** Format: int64 */ /** Format: int64 */
id: number; id: number;
/** Format: int64 */
libId: number;
enable: boolean; enable: boolean;
}; };
LlmVm: { LlmVm: {
@@ -867,6 +869,13 @@ export interface components {
username: string; username: string;
password: string; password: string;
}; };
ChatDto: {
/** @enum {string} */
mode: "NORMAL" | "WITH_LIBRARY";
/** Format: int64 */
libraryId?: number;
message: string;
};
PageRequestDto: { PageRequestDto: {
/** Format: int64 */ /** Format: int64 */
page?: number; page?: number;
@@ -1888,7 +1897,7 @@ export interface operations {
}; };
requestBody: { requestBody: {
content: { content: {
"application/json": string; "application/json": components["schemas"]["ChatDto"];
}; };
}; };
responses: { responses: {

View File

@@ -5,14 +5,18 @@
<div class="flex flex-col gap-y-5 flex-1 pb-2"> <div class="flex flex-col gap-y-5 flex-1 pb-2">
<li v-for="chatElement in messages" :key="chatElement.content" <li v-for="chatElement in messages" :key="chatElement.content"
:class="['flex items-start gap-2.5', chatElement.isUser ? 'flex-row-reverse' : 'flex-row']"> :class="['flex items-start gap-2.5', chatElement.isUser ? 'flex-row-reverse' : 'flex-row']">
<Avatar :src="chatElement.isUser ? user.avatar : '/trump.jpg'" size="sm" <Avatar :src="chatElement.isUser ? user.avatar : undefined" size="sm"
:alt="chatElement.isUser ? '用户头像' : 'AI头像'" /> :alt="chatElement.isUser ? '用户头像' : 'AI头像'" />
<div <div
:class="['flex flex-col leading-1.5 p-4 border-gray-200 rounded-e-xl rounded-es-xl max-w-[calc(100%-40px)]', chatElement.isUser ? 'bg-blue-100' : 'bg-gray-100']"> :class="['flex flex-col leading-1.5 p-4 border-gray-200 max-w-[calc(100%-40px)]', chatElement.isUser ? 'bg-blue-100 rounded-tl-xl rounded-bl-xl rounded-br-xl' : 'bg-gray-100 rounded-e-xl rounded-es-xl']">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<span class="text-sm font-semibold text-gray-900 ">{{ chatElement.username }}</span> <span class="text-sm font-semibold text-gray-900 ">{{ chatElement.username }}</span>
<LoadingIcon :textColor="'text-gray-900'" <LoadingIcon :textColor="'text-gray-900'"
v-if="isLoading && !chatElement.isUser && chatElement.content === ''" /> v-if="isLoading && !chatElement.isUser && chatElement.content === ''" />
<span v-if="!chatElement.isUser && chatElement.withLibrary"
class="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full">
{{ chatElement.libraryName }}
</span>
</div> </div>
<div> <div>
<div class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 break-words" <div class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 break-words"
@@ -34,14 +38,24 @@
</div> </div>
<form class="sticky"> <form class="sticky">
<div class="flex items-center justify-between gap-2 mb-2">
<button @click.prevent="clearConversation" <button @click.prevent="clearConversation"
class="relative inline-flex items-center justify-center p-0.5 mb-2 me-2 class="relative inline-flex items-center justify-center p-0.5
overflow-hidden text-sm font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-purple-600 to-blue-500 group-hover:from-purple-600 group-hover:to-blue-500 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 "> overflow-hidden text-sm font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-purple-600 to-blue-500 group-hover:from-purple-600 group-hover:to-blue-500 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 ">
<span <span
class="relative px-3 py-2 text-xs font-medium transition-all ease-in duration-75 bg-white rounded-md group-hover:bg-transparent"> class="relative px-3 py-2 text-xs font-medium transition-all ease-in duration-75 bg-white rounded-md group-hover:bg-transparent">
开启新对话 开启新对话
</span> </span>
</button> </button>
<select v-if="commandMode === 'chat'" v-model="selectedLibraryId"
class="bg-white border border-gray-300 text-gray-900 text-xs rounded-lg py-2 px-2 flex-1 max-w-48">
<option :value="undefined">不使用知识库</option>
<option v-for="library in libraries" :key="library.id" :value="library.id">
{{ library.name }}
</option>
</select>
</div>
<div class="w-full border border-gray-200 rounded-lg bg-gray-50"> <div class="w-full border border-gray-200 rounded-lg bg-gray-50">
<div class="px-4 py-2 bg-white rounded-t-lg"> <div class="px-4 py-2 bg-white rounded-t-lg">
<label for="comment" class="sr-only"></label> <label for="comment" class="sr-only"></label>
@@ -51,7 +65,7 @@
" required></textarea> " required></textarea>
</div> </div>
<div class="flex justify-between px-2 py-2 border-t border-gray-200"> <div class="flex justify-between px-2 py-2 border-t border-gray-200">
<select id="countries" v-model="commandMode" <select id="commandMode" v-model="commandMode"
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg block"> class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg block">
<option selected :value="'execute'">指令模式</option> <option selected :value="'execute'">指令模式</option>
<option :value="'search'">搜索模式</option> <option :value="'search'">搜索模式</option>
@@ -62,7 +76,6 @@
{{ isLoading ? '中止' : '发送' }} {{ isLoading ? '中止' : '发送' }}
</TableButton> </TableButton>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
@@ -144,6 +157,9 @@ import PositionDeleteModal from "@/components/modals/ConfirmationDialog.vue";
import PositionFormDialog from "@/components/modals/PositionFormDialog.vue"; import PositionFormDialog from "@/components/modals/PositionFormDialog.vue";
import RoleDeleteModal from "@/components/modals/ConfirmationDialog.vue"; import RoleDeleteModal from "@/components/modals/ConfirmationDialog.vue";
import RoleFormDialog from "@/components/modals/RoleFormDialog.vue"; import RoleFormDialog from "@/components/modals/RoleFormDialog.vue";
import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
import { UserFormDialog } from "../modals";
import { computed } from "vue";
const { const {
messages, messages,
@@ -190,6 +206,15 @@ const actionExcStore = useActionExcStore();
const { availableDepartments, fetchAvailableDepartments } = const { availableDepartments, fetchAvailableDepartments } =
useDepartmentQuery(); useDepartmentQuery();
// 知识库相关
const { libraries, fetchLibraries } = useKnowledgeQuery();
const selectedLibraryId = ref<number | null | undefined>(undefined);
const selectedLibraryName = computed(() => {
return libraries.value.find(
(library) => library.id === selectedLibraryId.value,
)?.name;
});
const commandPlaceholderMap: Record<string, string> = { const commandPlaceholderMap: Record<string, string> = {
chat: "随便聊聊", chat: "随便聊聊",
search: "输入「创建用户、删除部门、创建岗位、创建角色、创建权限」试试看", search: "输入「创建用户、删除部门、创建岗位、创建角色、创建权限」试试看",
@@ -235,31 +260,6 @@ const renderMarkdown = (content: string | undefined) => {
// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1])); // console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
// }, { deep: true }); // }, { deep: true });
const handleDeleteUserClick = (input: string) => {
currentDeleteUsername.value = input;
userDeleteModal.value?.show();
};
const handleDeleteDepartmentClick = (input: string) => {
currentDeleteDepartmentName.value = input;
departmentDeleteModal.value?.show();
};
const handleDeletePositionClick = (input: string) => {
currentDeletePositionName.value = input;
positionDeleteModal.value?.show();
};
const handleDeleteRoleClick = (input: string) => {
currentDeleteRoleName.value = input;
roleDeleteModal.value?.show();
};
const handleDeletePermissionClick = (input: string) => {
currentDeletePermissionName.value = input;
permissionDeleteModal.value?.show();
};
const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => { const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => {
await userUpsert.upsertUser(data); await userUpsert.upsertUser(data);
userUpsertModal.value?.hide(); userUpsertModal.value?.hide();
@@ -401,9 +401,14 @@ const chatByMode = async (
} else if (mode === "execute") { } else if (mode === "execute") {
await executeAction(message); await executeAction(message);
actionExcStore.notify(true); actionExcStore.notify(true);
} else {
// 聊天模式,判断是否使用知识库
if (selectedLibraryId.value !== undefined) {
await chat(message, selectedLibraryId.value);
} else { } else {
await chat(message); await chat(message);
} }
}
}; };
const handleSendClick = async ( const handleSendClick = async (
@@ -427,6 +432,9 @@ onUnmounted(() => {
onMounted(async () => { onMounted(async () => {
initFlowbite(); initFlowbite();
// 加载知识库列表
await fetchLibraries();
const $upsertModalElement: HTMLElement | null = const $upsertModalElement: HTMLElement | null =
document.querySelector("#user-upsert-modal"); document.querySelector("#user-upsert-modal");
if ($upsertModalElement) { if ($upsertModalElement) {

View File

@@ -38,7 +38,7 @@
defineProps({ defineProps({
titleClass: { titleClass: {
type: String, type: String,
default: '' default: "",
} },
}); });
</script> </script>

View File

@@ -10,7 +10,7 @@
</template> </template>
<template #footer-left> <template #footer-left>
<span class="text-xs text-gray-500"> <span class="text-xs text-gray-500">
上传时间: {{ formatDateString(doc.createTime) }} {{ formatDateString(doc.createTime) }}
</span> </span>
</template> </template>
<template #footer-actions> <template #footer-actions>
@@ -20,10 +20,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CardBase from '@/components/common/CardBase.vue'; import CardBase from "@/components/common/CardBase.vue";
import { KnowledgeStatusBadge } from '@/components/common/knowledge'; import { KnowledgeStatusBadge } from "@/components/common/knowledge";
import type { LibraryDoc } from "@/types/KnowledgeTypes"; import type { LibraryDoc } from "@/types/KnowledgeTypes";
import { formatDateString } from '@/utils/dateUtil'; import { formatDateString } from "@/utils/dateUtil";
const props = defineProps<{ const props = defineProps<{
doc: LibraryDoc; doc: LibraryDoc;

View File

@@ -21,9 +21,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CardBase from '@/components/common/CardBase.vue'; import CardBase from "@/components/common/CardBase.vue";
import type { Library } from "@/types/KnowledgeTypes"; import type { Library } from "@/types/KnowledgeTypes";
import { formatDateString } from '@/utils/dateUtil'; import { formatDateString } from "@/utils/dateUtil";
const props = defineProps<{ const props = defineProps<{
library: Library; library: Library;

View File

@@ -12,26 +12,26 @@ import { DocStatus } from "@/types/KnowledgeTypes";
const props = defineProps<{ const props = defineProps<{
status?: string; status?: string;
enabled?: boolean; enabled?: boolean;
type: 'status' | 'enabled'; type: "status" | "enabled";
}>(); }>();
const getStatusClass = () => { const getStatusClass = () => {
if (props.type === 'status') { if (props.type === "status") {
return props.status === DocStatus.SUCCESS return props.status === DocStatus.SUCCESS
? 'bg-green-100 text-green-800' ? "bg-green-100 text-green-800"
: 'bg-yellow-100 text-yellow-800'; : "bg-yellow-100 text-yellow-800";
} }
return props.enabled return props.enabled
? 'bg-blue-100 text-blue-800' ? "bg-blue-100 text-blue-800"
: 'bg-gray-100 text-gray-800'; : "bg-gray-100 text-gray-800";
}; };
const getStatusText = () => { const getStatusText = () => {
if (props.type === 'status') { if (props.type === "status") {
return props.status === DocStatus.SUCCESS ? '解析完成' : '解析中'; return props.status === DocStatus.SUCCESS ? "解析完成" : "解析中";
} }
return props.enabled ? '已启用' : '已禁用'; return props.enabled ? "已启用" : "已禁用";
}; };
</script> </script>

View File

@@ -27,7 +27,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CardBase from '@/components/common/CardBase.vue'; import CardBase from "@/components/common/CardBase.vue";
import type { LibraryDocSegment } from "@/types/KnowledgeTypes"; import type { LibraryDocSegment } from "@/types/KnowledgeTypes";
const props = defineProps<{ const props = defineProps<{

View File

@@ -1,7 +1,4 @@
import FormInput from './FormInput.vue'; import FormInput from "./FormInput.vue";
import FormSelect from './FormSelect.vue'; import FormSelect from "./FormSelect.vue";
export { export { FormInput, FormSelect };
FormInput,
FormSelect
};

View File

@@ -29,7 +29,18 @@
import { Modal, initFlowbite } from "flowbite"; import { Modal, initFlowbite } from "flowbite";
import { computed, onMounted } from "vue"; import { computed, onMounted } from "vue";
export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl"; export type ModalSize =
| "xs"
| "sm"
| "md"
| "lg"
| "xl"
| "2xl"
| "3xl"
| "4xl"
| "5xl"
| "6xl"
| "7xl";
const props = defineProps<{ const props = defineProps<{
/** 对话框标题 */ /** 对话框标题 */

View File

@@ -25,7 +25,7 @@ const sizeClass = computed(() => {
sm: "w-8 h-8", sm: "w-8 h-8",
md: "w-10 h-10", md: "w-10 h-10",
lg: "w-12 h-12", lg: "w-12 h-12",
xl: "w-16 h-16" xl: "w-16 h-16",
}; };
return sizes[props.size || "md"]; return sizes[props.size || "md"];
}); });

View File

@@ -11,13 +11,19 @@ export const useAiChat = () => {
isUser: boolean; isUser: boolean;
username: string; username: string;
command?: string; command?: string;
withLibrary?: boolean;
libraryName?: string;
}[] }[]
>([]); >([]);
const isLoading = ref(false); const isLoading = ref(false);
let currentController: AbortController | null = null; let currentController: AbortController | null = null;
const chat = async (message: string) => { const chat = async (
message: string,
libraryId?: number | null,
libraryName?: string,
) => {
isLoading.value = true; isLoading.value = true;
const authStore = useAuthStore(); const authStore = useAuthStore();
const ctrl = new AbortController(); const ctrl = new AbortController();
@@ -27,6 +33,8 @@ export const useAiChat = () => {
type: "chat", type: "chat",
isUser: false, isUser: false,
username: "知路智能体", username: "知路智能体",
withLibrary: libraryId !== undefined,
libraryName: libraryName,
}); });
try { try {
const baseUrl = `${import.meta.env.VITE_BASE_URL}`; const baseUrl = `${import.meta.env.VITE_BASE_URL}`;
@@ -36,7 +44,11 @@ export const useAiChat = () => {
Authorization: authStore.get(), Authorization: authStore.get(),
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: message, body: JSON.stringify({
mode: libraryId !== undefined ? "WITH_LIBRARY" : "NORMAL",
libraryId: libraryId,
message: message,
}),
signal: ctrl.signal, signal: ctrl.signal,
onmessage(ev) { onmessage(ev) {
messages.value[messages.value.length - 1].content += ev.data; messages.value[messages.value.length - 1].content += ev.data;

View File

@@ -68,7 +68,7 @@ import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
import { useKnowledgeUpsert } from "@/composables/knowledge/useKnowledgeUpsert"; import { useKnowledgeUpsert } from "@/composables/knowledge/useKnowledgeUpsert";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import { Routes } from "@/router/constants"; import { Routes } from "@/router/constants";
import { formatDateString } from '@/utils/dateUtil'; import { formatDateString } from "@/utils/dateUtil";
import type { Library, LibraryDoc } from "@/types/KnowledgeTypes"; import type { Library, LibraryDoc } from "@/types/KnowledgeTypes";
import { DocStatus } from "@/types/KnowledgeTypes"; import { DocStatus } from "@/types/KnowledgeTypes";

View File

@@ -28,9 +28,6 @@
<!-- 空状态 --> <!-- 空状态 -->
<div v-else class="flex flex-col items-center justify-center py-10"> <div v-else class="flex flex-col items-center justify-center py-10">
<div class="text-gray-500 text-lg mb-4">暂无分段内容</div> <div class="text-gray-500 text-lg mb-4">暂无分段内容</div>
<Button variant="secondary" @click="navigateBack">
返回文档列表
</Button>
</div> </div>
</div> </div>
</template> </template>
@@ -55,7 +52,8 @@ const libraryId = Number.parseInt(route.params.libraryId as string, 10);
const docId = Number.parseInt(route.params.docId as string, 10); const docId = Number.parseInt(route.params.docId as string, 10);
// 获取文档信息和分段列表 // 获取文档信息和分段列表
const { docs, segments, fetchLibraryDocs, fetchDocSegments } = useKnowledgeQuery(); const { docs, segments, fetchLibraryDocs, fetchDocSegments } =
useKnowledgeQuery();
const currentDoc = ref<LibraryDoc | undefined>(); const currentDoc = ref<LibraryDoc | undefined>();
// 导航回文档列表 // 导航回文档列表