mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
新增 ChatDto 数据传输对象,更新聊天接口以支持知识库功能,优化聊天服务逻辑,调整前端组件以提升用户体验。
This commit is contained in:
@@ -894,7 +894,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"$ref": "#/components/schemas/ChatDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1580,7 +1580,8 @@
|
||||
"DocUpdateDto": {
|
||||
"required": [
|
||||
"enable",
|
||||
"id"
|
||||
"id",
|
||||
"libId"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1588,6 +1589,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"libId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"enable": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
11
frontend/src/api/types/schema.d.ts
vendored
11
frontend/src/api/types/schema.d.ts
vendored
@@ -783,6 +783,8 @@ export interface components {
|
||||
DocUpdateDto: {
|
||||
/** Format: int64 */
|
||||
id: number;
|
||||
/** Format: int64 */
|
||||
libId: number;
|
||||
enable: boolean;
|
||||
};
|
||||
LlmVm: {
|
||||
@@ -867,6 +869,13 @@ export interface components {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
ChatDto: {
|
||||
/** @enum {string} */
|
||||
mode: "NORMAL" | "WITH_LIBRARY";
|
||||
/** Format: int64 */
|
||||
libraryId?: number;
|
||||
message: string;
|
||||
};
|
||||
PageRequestDto: {
|
||||
/** Format: int64 */
|
||||
page?: number;
|
||||
@@ -1888,7 +1897,7 @@ export interface operations {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": string;
|
||||
"application/json": components["schemas"]["ChatDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
<div class="flex flex-col gap-y-5 flex-1 pb-2">
|
||||
<li v-for="chatElement in messages" :key="chatElement.content"
|
||||
: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头像'" />
|
||||
<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">
|
||||
<span class="text-sm font-semibold text-gray-900 ">{{ chatElement.username }}</span>
|
||||
<LoadingIcon :textColor="'text-gray-900'"
|
||||
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 class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 break-words"
|
||||
@@ -34,14 +38,24 @@
|
||||
</div>
|
||||
|
||||
<form class="sticky">
|
||||
<button @click.prevent="clearConversation"
|
||||
class="relative inline-flex items-center justify-center p-0.5 mb-2 me-2
|
||||
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
|
||||
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>
|
||||
</button>
|
||||
<div class="flex items-center justify-between gap-2 mb-2">
|
||||
<button @click.prevent="clearConversation"
|
||||
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 ">
|
||||
<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">
|
||||
开启新对话
|
||||
</span>
|
||||
</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="px-4 py-2 bg-white rounded-t-lg">
|
||||
<label for="comment" class="sr-only"></label>
|
||||
@@ -51,7 +65,7 @@
|
||||
" required></textarea>
|
||||
</div>
|
||||
<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">
|
||||
<option selected :value="'execute'">指令模式</option>
|
||||
<option :value="'search'">搜索模式</option>
|
||||
@@ -62,7 +76,6 @@
|
||||
{{ isLoading ? '中止' : '发送' }}
|
||||
</TableButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -144,6 +157,9 @@ import PositionDeleteModal from "@/components/modals/ConfirmationDialog.vue";
|
||||
import PositionFormDialog from "@/components/modals/PositionFormDialog.vue";
|
||||
import RoleDeleteModal from "@/components/modals/ConfirmationDialog.vue";
|
||||
import RoleFormDialog from "@/components/modals/RoleFormDialog.vue";
|
||||
import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
|
||||
import { UserFormDialog } from "../modals";
|
||||
import { computed } from "vue";
|
||||
|
||||
const {
|
||||
messages,
|
||||
@@ -190,6 +206,15 @@ const actionExcStore = useActionExcStore();
|
||||
const { availableDepartments, fetchAvailableDepartments } =
|
||||
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> = {
|
||||
chat: "随便聊聊",
|
||||
search: "输入「创建用户、删除部门、创建岗位、创建角色、创建权限」试试看",
|
||||
@@ -235,31 +260,6 @@ const renderMarkdown = (content: string | undefined) => {
|
||||
// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
|
||||
// }, { 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) => {
|
||||
await userUpsert.upsertUser(data);
|
||||
userUpsertModal.value?.hide();
|
||||
@@ -402,7 +402,12 @@ const chatByMode = async (
|
||||
await executeAction(message);
|
||||
actionExcStore.notify(true);
|
||||
} else {
|
||||
await chat(message);
|
||||
// 聊天模式,判断是否使用知识库
|
||||
if (selectedLibraryId.value !== undefined) {
|
||||
await chat(message, selectedLibraryId.value);
|
||||
} else {
|
||||
await chat(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -427,6 +432,9 @@ onUnmounted(() => {
|
||||
|
||||
onMounted(async () => {
|
||||
initFlowbite();
|
||||
// 加载知识库列表
|
||||
await fetchLibraries();
|
||||
|
||||
const $upsertModalElement: HTMLElement | null =
|
||||
document.querySelector("#user-upsert-modal");
|
||||
if ($upsertModalElement) {
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
titleClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
titleClass: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CardBase from "./CardBase.vue";
|
||||
import PromotionBanner from "./PromotionBanner.vue";
|
||||
|
||||
export { CardBase, PromotionBanner };
|
||||
export { CardBase, PromotionBanner };
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
<template #footer-left>
|
||||
<span class="text-xs text-gray-500">
|
||||
上传时间: {{ formatDateString(doc.createTime) }}
|
||||
{{ formatDateString(doc.createTime) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #footer-actions>
|
||||
@@ -20,12 +20,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CardBase from '@/components/common/CardBase.vue';
|
||||
import { KnowledgeStatusBadge } from '@/components/common/knowledge';
|
||||
import CardBase from "@/components/common/CardBase.vue";
|
||||
import { KnowledgeStatusBadge } from "@/components/common/knowledge";
|
||||
import type { LibraryDoc } from "@/types/KnowledgeTypes";
|
||||
import { formatDateString } from '@/utils/dateUtil';
|
||||
import { formatDateString } from "@/utils/dateUtil";
|
||||
|
||||
const props = defineProps<{
|
||||
doc: LibraryDoc;
|
||||
doc: LibraryDoc;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
</template>
|
||||
|
||||
<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 { formatDateString } from '@/utils/dateUtil';
|
||||
import { formatDateString } from "@/utils/dateUtil";
|
||||
|
||||
const props = defineProps<{
|
||||
library: Library;
|
||||
library: Library;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -10,28 +10,28 @@
|
||||
import { DocStatus } from "@/types/KnowledgeTypes";
|
||||
|
||||
const props = defineProps<{
|
||||
status?: string;
|
||||
enabled?: boolean;
|
||||
type: 'status' | 'enabled';
|
||||
status?: string;
|
||||
enabled?: boolean;
|
||||
type: "status" | "enabled";
|
||||
}>();
|
||||
|
||||
const getStatusClass = () => {
|
||||
if (props.type === 'status') {
|
||||
return props.status === DocStatus.SUCCESS
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800';
|
||||
}
|
||||
|
||||
return props.enabled
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: 'bg-gray-100 text-gray-800';
|
||||
if (props.type === "status") {
|
||||
return props.status === DocStatus.SUCCESS
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-yellow-100 text-yellow-800";
|
||||
}
|
||||
|
||||
return props.enabled
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: "bg-gray-100 text-gray-800";
|
||||
};
|
||||
|
||||
const getStatusText = () => {
|
||||
if (props.type === 'status') {
|
||||
return props.status === DocStatus.SUCCESS ? '解析完成' : '解析中';
|
||||
}
|
||||
|
||||
return props.enabled ? '已启用' : '已禁用';
|
||||
if (props.type === "status") {
|
||||
return props.status === DocStatus.SUCCESS ? "解析完成" : "解析中";
|
||||
}
|
||||
|
||||
return props.enabled ? "已启用" : "已禁用";
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CardBase from '@/components/common/CardBase.vue';
|
||||
import CardBase from "@/components/common/CardBase.vue";
|
||||
import type { LibraryDocSegment } from "@/types/KnowledgeTypes";
|
||||
|
||||
const props = defineProps<{
|
||||
segment: LibraryDocSegment;
|
||||
index: number;
|
||||
segment: LibraryDocSegment;
|
||||
index: number;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -8,4 +8,4 @@ export {
|
||||
KnowledgeDocCard,
|
||||
KnowledgeLibraryCard,
|
||||
SegmentCard,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import FormInput from './FormInput.vue';
|
||||
import FormSelect from './FormSelect.vue';
|
||||
import FormInput from "./FormInput.vue";
|
||||
import FormSelect from "./FormSelect.vue";
|
||||
|
||||
export {
|
||||
FormInput,
|
||||
FormSelect
|
||||
};
|
||||
export { FormInput, FormSelect };
|
||||
|
||||
@@ -29,7 +29,18 @@
|
||||
import { Modal, initFlowbite } from "flowbite";
|
||||
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<{
|
||||
/** 对话框标题 */
|
||||
|
||||
@@ -20,4 +20,4 @@ export {
|
||||
RoleFormDialog,
|
||||
SchedulerFormDialog,
|
||||
UserFormDialog,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,4 +12,4 @@ export {
|
||||
TableFilterForm,
|
||||
TableFormLayout,
|
||||
TablePagination,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const sizeClass = computed(() => {
|
||||
sm: "w-8 h-8",
|
||||
md: "w-10 h-10",
|
||||
lg: "w-12 h-12",
|
||||
xl: "w-16 h-16"
|
||||
xl: "w-16 h-16",
|
||||
};
|
||||
return sizes[props.size || "md"];
|
||||
});
|
||||
|
||||
@@ -28,95 +28,95 @@ import { StopIcon } from "@/components/icons";
|
||||
import { computed } from "vue";
|
||||
|
||||
export type ButtonVariant =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "success"
|
||||
| "danger"
|
||||
| "warning"
|
||||
| "info";
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "success"
|
||||
| "danger"
|
||||
| "warning"
|
||||
| "info";
|
||||
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
||||
export type ButtonType = "button" | "submit" | "reset";
|
||||
|
||||
const props = defineProps<{
|
||||
/** 按钮变体类型 */
|
||||
variant?: ButtonVariant;
|
||||
/** 按钮尺寸 */
|
||||
size?: ButtonSize;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
className?: string;
|
||||
/** 是否为移动端尺寸 */
|
||||
isMobile?: boolean;
|
||||
/** 是否处于加载状态 */
|
||||
isLoading?: boolean;
|
||||
/** 是否可中止 */
|
||||
abortable?: boolean;
|
||||
/** 按钮类型 */
|
||||
type?: ButtonType;
|
||||
/** 是否占满宽度 */
|
||||
fullWidth?: boolean;
|
||||
/** 按钮变体类型 */
|
||||
variant?: ButtonVariant;
|
||||
/** 按钮尺寸 */
|
||||
size?: ButtonSize;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
className?: string;
|
||||
/** 是否为移动端尺寸 */
|
||||
isMobile?: boolean;
|
||||
/** 是否处于加载状态 */
|
||||
isLoading?: boolean;
|
||||
/** 是否可中止 */
|
||||
abortable?: boolean;
|
||||
/** 按钮类型 */
|
||||
type?: ButtonType;
|
||||
/** 是否占满宽度 */
|
||||
fullWidth?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent];
|
||||
click: [event: MouseEvent];
|
||||
}>();
|
||||
|
||||
/** 按钮颜色样式映射 */
|
||||
const colorClasses = computed(() => {
|
||||
const variants: Record<ButtonVariant, string> = {
|
||||
primary: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300",
|
||||
secondary:
|
||||
"text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-gray-100",
|
||||
success: "text-white bg-green-700 hover:bg-green-800 focus:ring-green-300",
|
||||
danger: "text-white bg-red-700 hover:bg-red-800 focus:ring-red-300",
|
||||
warning:
|
||||
"text-gray-900 bg-yellow-400 hover:bg-yellow-500 focus:ring-yellow-300",
|
||||
info: "text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-cyan-300",
|
||||
};
|
||||
const variants: Record<ButtonVariant, string> = {
|
||||
primary: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300",
|
||||
secondary:
|
||||
"text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-gray-100",
|
||||
success: "text-white bg-green-700 hover:bg-green-800 focus:ring-green-300",
|
||||
danger: "text-white bg-red-700 hover:bg-red-800 focus:ring-red-300",
|
||||
warning:
|
||||
"text-gray-900 bg-yellow-400 hover:bg-yellow-500 focus:ring-yellow-300",
|
||||
info: "text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-cyan-300",
|
||||
};
|
||||
|
||||
return variants[props.variant || "primary"];
|
||||
return variants[props.variant || "primary"];
|
||||
});
|
||||
|
||||
/** 按钮尺寸样式映射 */
|
||||
const sizeClasses = computed(() => {
|
||||
// 移动端尺寸
|
||||
if (props.isMobile) {
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-2 py-1",
|
||||
sm: "text-xs px-3 py-1.5",
|
||||
md: "text-sm px-3 py-2",
|
||||
lg: "text-sm px-4 py-2.5",
|
||||
};
|
||||
return sizes[props.size || "sm"];
|
||||
}
|
||||
// 移动端尺寸
|
||||
if (props.isMobile) {
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-2 py-1",
|
||||
sm: "text-xs px-3 py-1.5",
|
||||
md: "text-sm px-3 py-2",
|
||||
lg: "text-sm px-4 py-2.5",
|
||||
};
|
||||
return sizes[props.size || "sm"];
|
||||
}
|
||||
|
||||
// PC端尺寸
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-3 py-1.5",
|
||||
sm: "text-sm px-3 py-2",
|
||||
md: "text-sm px-4 py-2.5",
|
||||
lg: "text-base px-5 py-3",
|
||||
};
|
||||
// PC端尺寸
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-3 py-1.5",
|
||||
sm: "text-sm px-3 py-2",
|
||||
md: "text-sm px-4 py-2.5",
|
||||
lg: "text-base px-5 py-3",
|
||||
};
|
||||
|
||||
return sizes[props.size || "md"];
|
||||
return sizes[props.size || "md"];
|
||||
});
|
||||
|
||||
/** 图标尺寸样式映射 */
|
||||
const iconSizeClasses = computed(() => {
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "w-3.5 h-3.5",
|
||||
sm: "w-4 h-4",
|
||||
md: "w-4.5 h-4.5",
|
||||
lg: "w-5 h-5",
|
||||
};
|
||||
return sizes[props.size || "md"];
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "w-3.5 h-3.5",
|
||||
sm: "w-4 h-4",
|
||||
md: "w-4.5 h-4.5",
|
||||
lg: "w-5 h-5",
|
||||
};
|
||||
return sizes[props.size || "md"];
|
||||
});
|
||||
|
||||
/** 处理点击事件 */
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!props.disabled && !(props.isLoading && !props.abortable)) {
|
||||
emit("click", event);
|
||||
}
|
||||
if (!props.disabled && !(props.isLoading && !props.abortable)) {
|
||||
emit("click", event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -4,4 +4,4 @@ import Button from "./Button.vue";
|
||||
import InputButton from "./InputButton.vue";
|
||||
import SortIcon from "./SortIcon.vue";
|
||||
|
||||
export { Alert, Avatar, Button, InputButton, SortIcon };
|
||||
export { Alert, Avatar, Button, InputButton, SortIcon };
|
||||
|
||||
@@ -11,13 +11,19 @@ export const useAiChat = () => {
|
||||
isUser: boolean;
|
||||
username: string;
|
||||
command?: string;
|
||||
withLibrary?: boolean;
|
||||
libraryName?: string;
|
||||
}[]
|
||||
>([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
let currentController: AbortController | null = null;
|
||||
|
||||
const chat = async (message: string) => {
|
||||
const chat = async (
|
||||
message: string,
|
||||
libraryId?: number | null,
|
||||
libraryName?: string,
|
||||
) => {
|
||||
isLoading.value = true;
|
||||
const authStore = useAuthStore();
|
||||
const ctrl = new AbortController();
|
||||
@@ -27,6 +33,8 @@ export const useAiChat = () => {
|
||||
type: "chat",
|
||||
isUser: false,
|
||||
username: "知路智能体",
|
||||
withLibrary: libraryId !== undefined,
|
||||
libraryName: libraryName,
|
||||
});
|
||||
try {
|
||||
const baseUrl = `${import.meta.env.VITE_BASE_URL}`;
|
||||
@@ -36,7 +44,11 @@ export const useAiChat = () => {
|
||||
Authorization: authStore.get(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: message,
|
||||
body: JSON.stringify({
|
||||
mode: libraryId !== undefined ? "WITH_LIBRARY" : "NORMAL",
|
||||
libraryId: libraryId,
|
||||
message: message,
|
||||
}),
|
||||
signal: ctrl.signal,
|
||||
onmessage(ev) {
|
||||
messages.value[messages.value.length - 1].content += ev.data;
|
||||
|
||||
@@ -3,4 +3,4 @@ import { usePagination } from "./usePagination";
|
||||
import { useSorting } from "./useSorting";
|
||||
import { useStyleSystem } from "./useStyleSystem";
|
||||
|
||||
export { useErrorHandling, usePagination, useSorting, useStyleSystem };
|
||||
export { useErrorHandling, usePagination, useSorting, useStyleSystem };
|
||||
|
||||
@@ -26,9 +26,9 @@ export interface DocQueryParams {
|
||||
}
|
||||
|
||||
export interface SegmentQueryParams {
|
||||
libraryDocId?: number;
|
||||
docId?: number;
|
||||
}
|
||||
libraryDocId?: number;
|
||||
docId?: number;
|
||||
}
|
||||
|
||||
export enum DocStatus {
|
||||
SUCCESS = "SUCCESS",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getUserAvatarUrl } from "./avatarUtil";
|
||||
import { dayjs, formatDate, formatDateString } from "./dateUtil";
|
||||
|
||||
export { getUserAvatarUrl, dayjs, formatDate, formatDateString };
|
||||
export { getUserAvatarUrl, dayjs, formatDate, formatDateString };
|
||||
|
||||
@@ -68,7 +68,7 @@ import { useKnowledgeQuery } from "@/composables/knowledge/useKnowledgeQuery";
|
||||
import { useKnowledgeUpsert } from "@/composables/knowledge/useKnowledgeUpsert";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import { Routes } from "@/router/constants";
|
||||
import { formatDateString } from '@/utils/dateUtil';
|
||||
import { formatDateString } from "@/utils/dateUtil";
|
||||
|
||||
import type { Library, LibraryDoc } from "@/types/KnowledgeTypes";
|
||||
import { DocStatus } from "@/types/KnowledgeTypes";
|
||||
|
||||
@@ -28,9 +28,6 @@
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-10">
|
||||
<div class="text-gray-500 text-lg mb-4">暂无分段内容</div>
|
||||
<Button variant="secondary" @click="navigateBack">
|
||||
返回文档列表
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</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 { docs, segments, fetchLibraryDocs, fetchDocSegments } = useKnowledgeQuery();
|
||||
const { docs, segments, fetchLibraryDocs, fetchDocSegments } =
|
||||
useKnowledgeQuery();
|
||||
const currentDoc = ref<LibraryDoc | undefined>();
|
||||
|
||||
// 导航回文档列表
|
||||
|
||||
Reference in New Issue
Block a user