mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-14 13:43:42 +08:00
fix command type
This commit is contained in:
@@ -65,7 +65,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
@PostMapping("/action/chat")
|
||||
public Map<String, Object> actionChat(@RequestBody String message) {
|
||||
public Map<String, String> actionChat(@RequestBody String message) {
|
||||
AiLlmConfig aiLlmConfig = llmService.loadConfig(LlmCodeEnum.ZHI_PU);
|
||||
if (!aiLlmConfig.getEnable()) {
|
||||
throw new BusinessException("命令模型未启用,请开启后再试。");
|
||||
|
||||
@@ -30,8 +30,8 @@ public class EmbeddingService {
|
||||
|
||||
private final ZhiPuEmbeddingModelConfig zhiPuEmbeddingModelConfig;
|
||||
|
||||
public Map<String, Object> searchAction(String message) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
public Map<String, String> searchAction(String message) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
EmbeddingSearchRequest embeddingSearchRequest =
|
||||
EmbeddingSearchRequest.builder()
|
||||
.queryEmbedding(zhipuEmbeddingModel.embed(message).content())
|
||||
@@ -39,7 +39,8 @@ public class EmbeddingService {
|
||||
EmbeddingSearchResult<TextSegment> embeddingSearchResult =
|
||||
zhiPuEmbeddingStore.search(embeddingSearchRequest);
|
||||
if (!embeddingSearchResult.matches().isEmpty()) {
|
||||
result = embeddingSearchResult.matches().getFirst().embedded().metadata().toMap();
|
||||
Metadata metadata = embeddingSearchResult.matches().getFirst().embedded().metadata();
|
||||
result.put(Actions.INDEX_KEY, metadata.getString(Actions.INDEX_KEY));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -771,7 +771,7 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
frontend/src/api/types/schema.d.ts
vendored
2
frontend/src/api/types/schema.d.ts
vendored
@@ -1484,7 +1484,7 @@ export interface operations {
|
||||
};
|
||||
content: {
|
||||
"*/*": {
|
||||
[key: string]: Record<string, never>;
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -130,19 +130,10 @@ const handleSubmit = async () => {
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const validatedData = userSchema.parse(formData.value);
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
alertStore.showAlert({
|
||||
level: "error",
|
||||
content: error.errors[0].message,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
||||
import { ref } from "vue";
|
||||
import useAuthStore from "../store/useAuthStore";
|
||||
import client from "../../api/client";
|
||||
import useAuthStore from "../store/useAuthStore";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
export const useAiChat = () => {
|
||||
const messages = ref<string[]>([]);
|
||||
const messages = ref<
|
||||
{
|
||||
content: string;
|
||||
type: "chat" | "action";
|
||||
isUser: boolean;
|
||||
username: string;
|
||||
command?: string;
|
||||
}[]
|
||||
>([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
let currentController: AbortController | null = null;
|
||||
|
||||
const chat = async (message: string) => {
|
||||
isLoading.value = true;
|
||||
messages.value.push(message);
|
||||
messages.value.push("");
|
||||
const ctrl = new AbortController();
|
||||
currentController = ctrl;
|
||||
|
||||
messages.value.push({
|
||||
content: "",
|
||||
type: "chat",
|
||||
isUser: false,
|
||||
username: "知路智能体",
|
||||
});
|
||||
try {
|
||||
const baseUrl = `${import.meta.env.VITE_BASE_URL}`;
|
||||
await fetchEventSource(`${baseUrl}/ai/chat`, {
|
||||
@@ -29,7 +40,7 @@ export const useAiChat = () => {
|
||||
body: message,
|
||||
signal: ctrl.signal,
|
||||
onmessage(ev) {
|
||||
messages.value[messages.value.length - 1] += ev.data;
|
||||
messages.value[messages.value.length - 1].content += ev.data;
|
||||
},
|
||||
onclose() {
|
||||
console.log("onclose");
|
||||
@@ -38,20 +49,26 @@ export const useAiChat = () => {
|
||||
throw err;
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
messages.value.pop();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const actionChat = async (message: string) => {
|
||||
messages.value.push(message);
|
||||
messages.value.push("");
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const { data } = await client.POST("/ai/action/chat", {
|
||||
body: message,
|
||||
});
|
||||
messages.value[messages.value.length - 1] += "接收到指令,请您执行。";
|
||||
messages.value.push({
|
||||
content: "接收到指令,请您执行。",
|
||||
type: "action",
|
||||
isUser: false,
|
||||
username: "知路智能体",
|
||||
command: data?.action,
|
||||
});
|
||||
return data;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
RequestError,
|
||||
UnAuthError,
|
||||
} from "../types/error";
|
||||
import { z } from "zod";
|
||||
|
||||
const makeErrorHandler =
|
||||
(
|
||||
@@ -44,6 +45,11 @@ const makeErrorHandler =
|
||||
level: "error",
|
||||
content: err.detail ?? err.message,
|
||||
});
|
||||
} else if (err instanceof z.ZodError) {
|
||||
showAlert({
|
||||
level: "error",
|
||||
content: err.errors[0].message,
|
||||
});
|
||||
} else {
|
||||
showAlert({
|
||||
level: "error",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col px-96 box-border pt-14 min-h-screen max-h-screen overflow-auto" ref="chatContainer">
|
||||
<div class="flex flex-col gap-y-5 flex-1 pt-14">
|
||||
<li v-for="chatElement in chatElements" :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']">
|
||||
<img class="w-8 h-8 rounded-full" src="/trump.jpg" alt="Jese image">
|
||||
<div
|
||||
@@ -11,15 +11,23 @@
|
||||
<LoadingIcon :textColor="'text-gray-900'"
|
||||
v-if="isLoading && !chatElement.isUser && chatElement.content === ''" />
|
||||
</div>
|
||||
<div class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 "
|
||||
v-html="renderMarkdown(chatElement.content)">
|
||||
<div>
|
||||
<div class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 "
|
||||
v-html="renderMarkdown(chatElement.content)">
|
||||
</div>
|
||||
<button v-if="chatElement.type === 'action' && chatElement.command" type="button"
|
||||
@click="commandActionMap[chatElement.command!]"
|
||||
class="px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||
{{
|
||||
commandContentMap[chatElement.command!]
|
||||
}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<form class="sticky bottom-4 mt-14">
|
||||
<button @click.prevent="toggleCommandMode"
|
||||
<button @click.prevent="toggleMode"
|
||||
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 focus:ring-4 focus:outline-none focus:ring-lime-200"
|
||||
:class="[
|
||||
isCommandMode
|
||||
@@ -67,27 +75,47 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<UserUpsertModal :id="'user-upsert-modal'" :onSubmit="handleUpsertUserSubmit" :closeModal="() => {
|
||||
userUpsertModal!.hide();
|
||||
}">
|
||||
</UserUpsertModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import DOMPurify from "dompurify";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { marked } from "marked";
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import Button from "../components/Button.vue";
|
||||
import UserUpsertModal from "../components/UserUpsertModal.vue";
|
||||
import { useAiChat } from "../composables/ai/useAiChat";
|
||||
import useUserStore from "../composables/store/useUserStore";
|
||||
import { useUserUpsert } from "../composables/user/useUserUpsert";
|
||||
import type { UserUpsertSubmitModel } from "../types/user";
|
||||
|
||||
const { messages, chat, isLoading, cancel, actionChat } = useAiChat();
|
||||
const { user } = useUserStore();
|
||||
const userUpsertModal = ref<ModalInterface>();
|
||||
const inputMessage = ref("");
|
||||
const chatContainer = ref<HTMLElement | null>(null);
|
||||
const alertStore = useAlertStore();
|
||||
const isCommandMode = ref(false);
|
||||
const userUpsert = useUserUpsert();
|
||||
|
||||
const toggleCommandMode = () => {
|
||||
const commandActionMap: Record<string, () => void> = {
|
||||
CREATE_USER: () => {
|
||||
userUpsertModal.value?.show();
|
||||
},
|
||||
};
|
||||
|
||||
const commandContentMap: Record<string, string> = {
|
||||
CREATE_USER: "创建新用户",
|
||||
};
|
||||
|
||||
const toggleMode = () => {
|
||||
isCommandMode.value = !isCommandMode.value;
|
||||
};
|
||||
|
||||
@@ -96,7 +124,7 @@ marked.setOptions({
|
||||
breaks: true,
|
||||
});
|
||||
|
||||
const renderMarkdown = (content: string) => {
|
||||
const renderMarkdown = (content: string | undefined) => {
|
||||
if (!content) return "";
|
||||
|
||||
const restoredContent = content
|
||||
@@ -111,23 +139,23 @@ const renderMarkdown = (content: string) => {
|
||||
const rawHtml = marked(processedContent);
|
||||
return DOMPurify.sanitize(rawHtml as string);
|
||||
};
|
||||
const chatElements = computed(() => {
|
||||
return messages.value.map((message, index) => {
|
||||
return {
|
||||
content: message,
|
||||
username: index % 2 === 0 ? user.username : "知路智能体",
|
||||
isUser: index % 2 === 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// watch(messages, (newVal) => {
|
||||
// console.log('原始消息:', newVal[newVal.length - 1]);
|
||||
// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
|
||||
// }, { deep: true });
|
||||
|
||||
const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => {
|
||||
await userUpsert.upsertUser(data);
|
||||
userUpsertModal.value?.hide();
|
||||
alertStore.showAlert({
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
chatElements,
|
||||
messages,
|
||||
async () => {
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
@@ -146,6 +174,13 @@ const abortChat = () => {
|
||||
};
|
||||
|
||||
const chatByMode = async (message: string) => {
|
||||
inputMessage.value = "";
|
||||
messages.value.push({
|
||||
content: message,
|
||||
type: "chat",
|
||||
isUser: true,
|
||||
username: user.username!,
|
||||
});
|
||||
if (isCommandMode.value) {
|
||||
await actionChat(message);
|
||||
} else {
|
||||
@@ -159,12 +194,11 @@ const chatByMode = async (message: string) => {
|
||||
|
||||
const handleSendClick = async () => {
|
||||
try {
|
||||
scrollToBottom();
|
||||
const validInputMessage = z
|
||||
.string({ message: "消息不能为空" })
|
||||
.min(1, "消息不能为空")
|
||||
.parse(inputMessage.value);
|
||||
scrollToBottom();
|
||||
inputMessage.value = "";
|
||||
await chatByMode(validInputMessage);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
@@ -181,6 +215,19 @@ const handleSendClick = async () => {
|
||||
onUnmounted(() => {
|
||||
cancel();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
initFlowbite();
|
||||
const $upsertModalElement: HTMLElement | null =
|
||||
document.querySelector("#user-upsert-modal");
|
||||
userUpsertModal.value = new Modal(
|
||||
$upsertModalElement,
|
||||
{},
|
||||
{
|
||||
id: "user-upsert-modal",
|
||||
},
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
Reference in New Issue
Block a user