fix ai page

This commit is contained in:
Chuck1sn
2025-05-22 11:04:06 +08:00
parent 224a7a6a30
commit a5781c28d3
5 changed files with 98 additions and 48 deletions

View File

@@ -52,8 +52,6 @@
"vue-tsc": "^2.2.8" "vue-tsc": "^2.2.8"
}, },
"msw": { "msw": {
"workerDirectory": [ "workerDirectory": ["public"]
"public"
]
} }
} }

View File

@@ -1,26 +1,58 @@
<template> <template>
<button :disabled="disabled" @click="handleClick" type="button" :class="[ <button :disabled="disabled" @click="handleClick" type="button" :class="{
'text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 dark:bg-blue-600 inline-flex items-center', 'text-white': true,
disabled ? `${disabledStyle}` : 'bg-blue-700 hover:bg-blue-800 ' 'focus:ring-4': true,
]"> 'focus:outline-none': true,
'focus:ring-blue-300': true,
'font-medium': true,
'rounded-lg': true,
'text-sm': true,
'px-5': true,
'py-2.5': true,
'text-center': true,
'me-2': true,
'dark:bg-blue-600': true,
'inline-flex': true,
'items-center': true,
'bg-blue-700 hover:bg-blue-800': !isLoading,
[loadingStyle]: isLoading
}">
<LoadingIcon v-if="isLoading && !abortable" /> <LoadingIcon v-if="isLoading && !abortable" />
<StopIcon v-if="isLoading && abortable" /> <StopIcon v-else-if="isLoading && abortable" />
{{isLoading ? loadingContent : submitContent}} {{isLoading ? loadingContent : submitContent}}
</button> </button>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue";
import LoadingIcon from "./icons/LoadingIcon.vue"; import LoadingIcon from "./icons/LoadingIcon.vue";
import StopIcon from "./icons/StopIcon.vue"; import StopIcon from "./icons/StopIcon.vue";
const { loadingContent, submitContent, isLoading = false, disabledStyle, disabled = false, abortable = false } = const {
defineProps<{ loadingContent,
loadingContent?: string; submitContent,
submitContent: string; isLoading = false,
isLoading: boolean; abortable = false,
disabledStyle?: string; } = defineProps<{
disabled: boolean; loadingContent?: string;
abortable: boolean; submitContent: string;
handleClick: (event: Event) => void; isLoading: boolean;
}>(); abortable: boolean;
handleClick: (event: Event) => void;
}>();
const loadingStyle = computed<string>(() => {
switch (true) {
case isLoading && !abortable:
return "bg-blue-400 cursor-not-allowed";
case isLoading && abortable:
return "bg-blue-700 hover:bg-blue-800";
default:
return "";
}
});
const disabled = computed(() => {
return !abortable && isLoading;
});
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<svg aria-hidden="true" role="status" :class="`inline ${size} ${textColor} me-3 animate-spin`" viewBox="0 0 100 101" <svg aria-hidden="true" role="status" :class="`inline ${size} ${textColor} animate-spin me-2`" viewBox="0 0 100 101"
fill="none" xmlns="http://www.w3.org/2000/svg"> fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
@@ -11,8 +11,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { size = 'w-4 h-4' , textColor = 'text-white' } = defineProps<{ const { size = "w-4 h-4", textColor = "text-white" } = defineProps<{
size?: string; size?: string;
textColor?: string; textColor?: string;
}>(); }>();
</script> </script>

View File

@@ -1,14 +1,17 @@
<template> <template>
<svg :class="`inline ${textColor} ${size} me-1`" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" <svg :class="{
height="24" fill="currentColor" viewBox="0 0 24 24"> 'inline': true,
[textColor]: true,
[size]: true,
'me-2': true
}" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
<path d="M7 5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H7Z" /> <path d="M7 5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H7Z" />
</svg> </svg>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { size = 'w-6 h-6' , textColor = 'text-white' } = defineProps<{ const { size = "w-5 h-5", textColor = "text-white" } = defineProps<{
size?: string; size?: string;
textColor?: string; textColor?: string;
}>(); }>();
</script> </script>

View File

@@ -8,7 +8,7 @@
:class="['flex flex-col leading-1.5 p-4 border-gray-200 rounded-e-xl rounded-es-xl dark:bg-gray-700', chatElement.isUser ? 'bg-blue-100' : 'bg-gray-100']"> :class="['flex flex-col leading-1.5 p-4 border-gray-200 rounded-e-xl rounded-es-xl dark:bg-gray-700', chatElement.isUser ? 'bg-blue-100' : 'bg-gray-100']">
<div class="flex items-center space-x-2 rtl:space-x-reverse"> <div class="flex items-center space-x-2 rtl:space-x-reverse">
<span class="text-sm font-semibold text-gray-900 dark:text-white">{{ chatElement.username }}</span> <span class="text-sm font-semibold text-gray-900 dark:text-white">{{ 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 === ''" />
</div> </div>
<p class="text-base font-normal py-2.5 text-gray-900 dark:text-white"> <p class="text-base font-normal py-2.5 text-gray-900 dark:text-white">
@@ -24,10 +24,10 @@
<label for="comment" class="sr-only"></label> <label for="comment" class="sr-only"></label>
<textarea id="comment" rows="3" v-model="inputMessage" <textarea id="comment" rows="3" v-model="inputMessage"
class="w-full px-0 text-gray-900 bg-white border-0 dark:bg-gray-800 focus:ring-0 dark:text-white dark:placeholder-gray-400" class="w-full px-0 text-gray-900 bg-white border-0 dark:bg-gray-800 focus:ring-0 dark:text-white dark:placeholder-gray-400"
placeholder="给知路智能体发送消息" required></textarea> placeholder="发送消息" required></textarea>
</div> </div>
<div class="flex items-center justify-between px-3 py-2 border-t dark:border-gray-600 border-gray-200"> <div class="flex items-center justify-between px-3 py-2 border-t dark:border-gray-600 border-gray-200">
<Button :abortable="true" :disabled="isLoading" :isLoading="isLoading" :submitContent="'发送'" <Button :abortable="true" :isLoading="isLoading" :loadingContent="'中止'" :submitContent="'发送'"
:handleClick="handleSendClick" /> :handleClick="handleSendClick" />
<div class="flex ps-0 space-x-1 rtl:space-x-reverse sm:ps-2"> <div class="flex ps-0 space-x-1 rtl:space-x-reverse sm:ps-2">
<button type="button" <button type="button"
@@ -39,15 +39,6 @@
</svg> </svg>
<span class="sr-only">Attach file</span> <span class="sr-only">Attach file</span>
</button> </button>
<button type="button"
class="inline-flex justify-center items-center p-2 text-gray-500 rounded-sm cursor-pointer hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600">
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 16 20">
<path
d="M8 0a7.992 7.992 0 0 0-6.583 12.535 1 1 0 0 0 .12.183l.12.146c.112.145.227.285.326.4l5.245 6.374a1 1 0 0 0 1.545-.003l5.092-6.205c.206-.222.4-.455.578-.7l.127-.155a.934.934 0 0 0 .122-.192A8.001 8.001 0 0 0 8 0Zm0 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" />
</svg>
<span class="sr-only">Set location</span>
</button>
<button type="button" <button type="button"
class="inline-flex justify-center items-center p-2 text-gray-500 rounded-sm cursor-pointer hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600"> class="inline-flex justify-center items-center p-2 text-gray-500 rounded-sm cursor-pointer hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600">
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
@@ -70,11 +61,14 @@ import Button from "../components/Button.vue";
import { useAiChat } from "../composables/ai/useAiChat"; import { useAiChat } from "../composables/ai/useAiChat";
import useUserStore from "../composables/store/useUserStore"; import useUserStore from "../composables/store/useUserStore";
import LoadingIcon from "@/components/icons/LoadingIcon.vue"; import LoadingIcon from "@/components/icons/LoadingIcon.vue";
import { z } from "zod";
import useAlertStore from "@/composables/store/useAlertStore";
const { messages, chat, isLoading, cancel } = useAiChat(); const { messages, chat, isLoading, cancel } = useAiChat();
const { user } = useUserStore(); const { user } = useUserStore();
const inputMessage = ref(""); const inputMessage = ref("");
const chatContainer = ref<HTMLElement | null>(null); const chatContainer = ref<HTMLElement | null>(null);
const alertStore = useAlertStore();
const chatElements = computed(() => { const chatElements = computed(() => {
return messages.value.map((message, index) => { return messages.value.map((message, index) => {
@@ -101,17 +95,40 @@ const scrollToBottom = () => {
} }
}; };
const handleSendClick = async (event: Event) => { const abortChat = () => {
await chat(inputMessage.value); cancel();
inputMessage.value = ""; };
scrollToBottom();
const sendMessage = async () => {
try {
const validInputMessage = z
.string({ message: "消息不能为空" })
.min(1, "消息不能为空")
.parse(inputMessage.value);
scrollToBottom();
inputMessage.value = "";
await chat(validInputMessage);
} catch (error) {
if (error instanceof z.ZodError) {
alertStore.showAlert({
level: "error",
content: error.errors[0].message,
});
} else {
throw error;
}
}
};
const handleSendClick = async () => {
if (isLoading.value) {
abortChat();
} else {
sendMessage();
}
}; };
onUnmounted(() => { onUnmounted(() => {
cancel(); cancel();
}); });
const handleStopClick = () => {
cancel();
}
</script> </script>