改进 首页和输入框

This commit is contained in:
ivamp 2024-10-10 11:27:36 +08:00
parent 97574df592
commit c2a5d6340b
9 changed files with 258 additions and 11 deletions

1
src/components.d.ts vendored
View File

@ -14,6 +14,7 @@ declare module 'vue' {
Chat: typeof import('./components/chat/Chat.vue')['default']
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
ChatSettings: typeof import('./components/settings/ChatSettings.vue')['default']
Layout: typeof import('./components/chat/Layout.vue')['default']
LeftSettings: typeof import('./components/settings/LeftSettings.vue')['default']
LibrarySettings: typeof import('./components/settings/LibrarySettings.vue')['default']
Lottie: typeof import('./components/Lottie.vue')['default']

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col items-center justify-between">
<div class="flex flex-col items-center justify-between overflow-hidden">
<div class="min-w-full md:w-4/5">
<n-scrollbar
style="max-height: calc(100vh - (var(--header-height) * 3.5))"
@ -11,9 +11,12 @@
v-if="!chatMessages?.length"
>
<div class="text-5xl">
<n-gradient-text type="success" class="pr-3 pb-2 pt-2 max-w-96 lg:max-w-full overflow-ellipsis overflow-hidden whitespace-nowrap">
<n-gradient-text
type="success"
class="pr-3 pb-2 pt-2 max-w-96 lg:max-w-full overflow-ellipsis overflow-hidden whitespace-nowrap"
>
<!-- 你好{{ userStore.user.name }} -->
你好
你好
</n-gradient-text>
<br />
<div class="pr-3 mt-1 text-2xl">
@ -57,8 +60,8 @@
</n-scrollbar>
</div>
<div class="w-full pt-2 relative">
<div class="fixed bottom-0 left-0 right-0 pr-2 pl-2">
<div class="min-w-full pt-2 relative " style="max-height: 120px">
<div class="relative bottom-0 pr-2 pl-2 min-w-full" ref="textContainer">
<div
class="mx-auto w-2xl max-w-2xl text-center mb-3 animate__animated animate__pulse text-lg"
v-if="isMobile && chatStore.toolName != ''"
@ -87,6 +90,8 @@
contenteditable="true"
placeholder="请输入文本..."
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
@change="updateInputPosition"
@keyup="updateInputPosition"
@keydown="onKeydown"
@input="updateInputHeight"
@compositionstart="handleCompositionStart"
@ -204,7 +209,6 @@
</n-modal>
</template>
<script setup lang="ts">
import { useUserStore } from "../../stores/user";
import { onMounted, onUnmounted, Ref, ref } from "vue";
import {
SendOutline,
@ -260,6 +264,7 @@ const compositionStart = ref(false);
const inputContainer: any = ref(null);
const inputText: any = ref(null);
const actionContainer: any = ref(null);
const textContainer: any = ref(null);
const isPlaceholderVisible = ref(true);
const triggerTimes = ref(0);
const showSendBtn = ref(false);
@ -301,7 +306,16 @@ const updateInputContent = (content: string) => {
updateInputHeight();
};
const updateInputPosition = () => {
const container = inputContainer.value;
const text = textContainer.value;
// bottom
text.style.bottom = `${container.offsetHeight - 80}px`;
};
function onKeydown(e: KeyboardEvent) {
updateInputPosition();
// Esc
if (e.code === "Escape") {
assistantStore.selectMenu = false;
@ -336,6 +350,8 @@ function updateInputHeight() {
return;
}
updateInputPosition();
const input = inputText.value;
const container = inputContainer.value;
const action = actionContainer.value;
@ -398,10 +414,14 @@ function updateInputHeight() {
}
function handleCompositionStart() {
updateInputPosition();
compositionStart.value = true;
}
function handleCompositionEnd() {
updateInputPosition();
compositionStart.value = false;
}

View File

@ -0,0 +1,144 @@
<template>
<n-layout :has-sider="!isMobile">
<n-layout-sider :native-scrollbar="false" v-if="!isMobile">
<n-scrollbar :style="'max-height: ' + clientHeight + 'px'">
<n-list hoverable clickable v-if="chatStore.chats?.length">
<n-list-item
v-for="c in chatStore.chats"
:key="c.id"
:class="
c.id === chatStore.currentChat?.id
? ' bg-gray-100 dark:bg-gray-700'
: ''
"
@click="viewChat(c.id ?? 0)"
>
<n-thing>
<div class="flex justify-between items-center">
<div>
{{ c.name }}
</div>
<div>
<n-button
quaternary
circle
type="warning"
@click.stop="deleteChat(c.id ?? 0)"
>
<template #icon>
<n-icon size="16" class="cursor-pointer">
<TrashBinOutline />
</n-icon>
</template>
</n-button>
</div>
</div>
</n-thing>
</n-list-item>
</n-list>
<div v-else>
<n-result
status="404"
title="你还没有对话"
description="不如现在就开始?"
>
</n-result>
</div>
</n-scrollbar>
</n-layout-sider>
<n-layout>
<n-layout-content>
<slot />
</n-layout-content>
</n-layout>
</n-layout>
</template>
<script setup lang="ts">
import { useUserStore } from "@/stores/user";
import { useDialog } from "naive-ui";
import { TrashBinOutline } from "@vicons/ionicons5";
import getApi from "../../plugins/api";
import { useChatStore } from "../../stores/chat";
import router from "@/router";
import { ref } from "vue";
import { EntityAssistant, EntityChat } from "@/api";
import { useIsMobile } from "@/utils/composables";
import { useAppStore } from "@/stores/app";
const clientHeight = ref(100);
const isMobile = useIsMobile();
const dialog = useDialog();
const chatStore = useChatStore();
const showSettingsDialog = ref(false);
const currentChatId = ref();
const currentChat: Ref<EntityChat> = ref({});
const assistants: Ref<EntityAssistant[]> = ref([]);
const assistantSelects = ref([
{
label: "不使用",
value: null,
},
]);
async function getChats() {
chatStore.chats = (await getApi().Chat.apiV1ChatsGet()).data.data;
}
const viewChat = (chatId: number) => {
router.push("/chat/" + chatId);
};
const deleteChat = async (chatId: number) => {
dialog.warning({
title: "删除对话",
content: "删除后,将不能恢复",
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
await getApi().Chat.apiV1ChatsIdDelete(chatId);
await getChats();
},
});
};
const getAssistants = async () => {
assistants.value =
(await getApi().Assistant.apiV1AssistantsGet()).data.data ?? [];
assistantSelects.value = [];
assistantSelects.value.push({
label: "不使用",
value: null,
});
assistants.value.forEach((a) => {
if (a.name !== undefined && a.id !== undefined) {
return assistantSelects.value.push({
label: a.name,
// @ts-ignore
value: a.id,
});
}
});
};
const appStore = useAppStore();
const updateClientHeight = () => {
clientHeight.value = window.innerHeight - appStore.headerHeight;
};
onMounted(() => {
updateClientHeight();
window.addEventListener("resize", updateClientHeight);
});
onUnmounted(() => {
window.removeEventListener("resize", updateClientHeight);
});
getChats();
getAssistants();
</script>

View File

@ -19,6 +19,7 @@ if (process.env.NODE_ENV === "production") {
config.backend = "https://amber-api.leaflow.cn";
// console.log("api endpoint: " + config.backend);
export default config;

View File

@ -1,15 +1,16 @@
<script setup lang="ts">
import { NLayout } from "naive-ui";
// import { useUserStore } from "../stores/user";
import { useUserStore } from "../stores/user";
// import Guest from "../pages/guest/index.vue";
// import router from "../router";
import Header from "./Header.vue";
import element from "@/config/element";
import { useIsMobile } from "@/utils/composables";
import { useAppStore } from "@/stores/app";
import ChatLayout from "@/components/chat/Layout.vue";
// const currentRoute = computed(() => router.currentRoute.value.name);
// const userStore = useUserStore();
const userStore = useUserStore();
const appStore = useAppStore();
const route = useRoute();
@ -82,7 +83,14 @@ const onScroll = (e: Event) => {
<n-layout :native-scrollbar="isMobile">
<div class="!pt-2">
<router-view :key="route.path"> </router-view>
<div v-if="userStore.logined">
<ChatLayout>
<router-view :key="route.path"> </router-view>
</ChatLayout>
</div>
<div v-else>
<router-view :key="route.path"> </router-view>
</div>
</div>
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />

70
src/pages/chat/Chat.vue Normal file
View File

@ -0,0 +1,70 @@
<template>
<ChatComponent />
</template>
<script setup lang="ts">
import ChatComponent from "@/components/chat/Chat.vue";
import { useDialog } from "naive-ui";
import getApi from "../../plugins/api";
import { useChatStore } from "../../stores/chat";
import router from "@/router";
import { ref } from "vue";
import { EntityAssistant, EntityChat } from "@/api";
import { useAppStore } from "@/stores/app";
const clientHeight = ref(100);
const dialog = useDialog();
const chatStore = useChatStore();
const showSettingsDialog = ref(false);
const currentChatId = ref();
const currentChat: Ref<EntityChat> = ref({});
const assistants: Ref<EntityAssistant[]> = ref([]);
const assistantSelects = ref([
{
label: "不使用",
value: null,
},
]);
async function getChats() {
chatStore.chats = (await getApi().Chat.apiV1ChatsGet()).data.data;
}
const getAssistants = async () => {
assistants.value =
(await getApi().Assistant.apiV1AssistantsGet()).data.data ?? [];
assistantSelects.value = [];
assistantSelects.value.push({
label: "不使用",
value: null,
});
assistants.value.forEach((a) => {
if (a.name !== undefined && a.id !== undefined) {
return assistantSelects.value.push({
label: a.name,
// @ts-ignore
value: a.id,
});
}
});
};
const appStore = useAppStore();
const updateClientHeight = () => {
clientHeight.value = window.innerHeight - appStore.headerHeight;
};
onMounted(() => {
updateClientHeight();
window.addEventListener("resize", updateClientHeight);
});
onUnmounted(() => {
window.removeEventListener("resize", updateClientHeight);
});
getChats();
getAssistants();
</script>

View File

@ -1,11 +1,12 @@
<template>
<Chat v-if="userStore.logined" />
<Guest v-else />
<Home v-else />
</template>
<script setup lang="ts">
import Guest from "@/pages/Guest.vue";
import Home from "@/pages/home/index.vue";
import { useUserStore } from "@/stores/user";
import Chat from "@/pages/chat/Chat.vue";
const userStore = useUserStore();
</script>

View File

@ -4,6 +4,7 @@ import { defineStore } from "pinia";
export const useAppStore = defineStore("app", {
persist: false,
state: () => ({
headerHeight: 64,
updating: false,
contentScrollHeight: 0,
contentScrollPosition: 0,

View File

@ -24,6 +24,7 @@ declare module 'vue-router/auto-routes' {
'/auth/login': RouteRecordInfo<'/auth/login', '/auth/login', Record<never, never>, Record<never, never>>,
'/auth/logout': RouteRecordInfo<'/auth/logout', '/auth/logout', Record<never, never>, Record<never, never>>,
'/chat/[id]/': RouteRecordInfo<'/chat/[id]/', '/chat/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/chat/Chat': RouteRecordInfo<'/chat/Chat', '/chat/Chat', Record<never, never>, Record<never, never>>,
'/errors/400': RouteRecordInfo<'/errors/400', '/errors/400', Record<never, never>, Record<never, never>>,
'/errors/401': RouteRecordInfo<'/errors/401', '/errors/401', Record<never, never>, Record<never, never>>,
'/errors/404': RouteRecordInfo<'/errors/404', '/errors/404', Record<never, never>, Record<never, never>>,