forked from Leaf/amber-ui
改进 首页和输入框
This commit is contained in:
parent
97574df592
commit
c2a5d6340b
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
Chat: typeof import('./components/chat/Chat.vue')['default']
|
Chat: typeof import('./components/chat/Chat.vue')['default']
|
||||||
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
|
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
|
||||||
ChatSettings: typeof import('./components/settings/ChatSettings.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']
|
LeftSettings: typeof import('./components/settings/LeftSettings.vue')['default']
|
||||||
LibrarySettings: typeof import('./components/settings/LibrarySettings.vue')['default']
|
LibrarySettings: typeof import('./components/settings/LibrarySettings.vue')['default']
|
||||||
Lottie: typeof import('./components/Lottie.vue')['default']
|
Lottie: typeof import('./components/Lottie.vue')['default']
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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">
|
<div class="min-w-full md:w-4/5">
|
||||||
<n-scrollbar
|
<n-scrollbar
|
||||||
style="max-height: calc(100vh - (var(--header-height) * 3.5))"
|
style="max-height: calc(100vh - (var(--header-height) * 3.5))"
|
||||||
@ -11,7 +11,10 @@
|
|||||||
v-if="!chatMessages?.length"
|
v-if="!chatMessages?.length"
|
||||||
>
|
>
|
||||||
<div class="text-5xl">
|
<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 }} -->
|
<!-- 你好,{{ userStore.user.name }} -->
|
||||||
你好
|
你好
|
||||||
</n-gradient-text>
|
</n-gradient-text>
|
||||||
@ -57,8 +60,8 @@
|
|||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full pt-2 relative">
|
<div class="min-w-full pt-2 relative " style="max-height: 120px">
|
||||||
<div class="fixed bottom-0 left-0 right-0 pr-2 pl-2">
|
<div class="relative bottom-0 pr-2 pl-2 min-w-full" ref="textContainer">
|
||||||
<div
|
<div
|
||||||
class="mx-auto w-2xl max-w-2xl text-center mb-3 animate__animated animate__pulse text-lg"
|
class="mx-auto w-2xl max-w-2xl text-center mb-3 animate__animated animate__pulse text-lg"
|
||||||
v-if="isMobile && chatStore.toolName != ''"
|
v-if="isMobile && chatStore.toolName != ''"
|
||||||
@ -87,6 +90,8 @@
|
|||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
placeholder="请输入文本..."
|
placeholder="请输入文本..."
|
||||||
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
|
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
|
||||||
|
@change="updateInputPosition"
|
||||||
|
@keyup="updateInputPosition"
|
||||||
@keydown="onKeydown"
|
@keydown="onKeydown"
|
||||||
@input="updateInputHeight"
|
@input="updateInputHeight"
|
||||||
@compositionstart="handleCompositionStart"
|
@compositionstart="handleCompositionStart"
|
||||||
@ -204,7 +209,6 @@
|
|||||||
</n-modal>
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserStore } from "../../stores/user";
|
|
||||||
import { onMounted, onUnmounted, Ref, ref } from "vue";
|
import { onMounted, onUnmounted, Ref, ref } from "vue";
|
||||||
import {
|
import {
|
||||||
SendOutline,
|
SendOutline,
|
||||||
@ -260,6 +264,7 @@ const compositionStart = ref(false);
|
|||||||
const inputContainer: any = ref(null);
|
const inputContainer: any = ref(null);
|
||||||
const inputText: any = ref(null);
|
const inputText: any = ref(null);
|
||||||
const actionContainer: any = ref(null);
|
const actionContainer: any = ref(null);
|
||||||
|
const textContainer: any = ref(null);
|
||||||
const isPlaceholderVisible = ref(true);
|
const isPlaceholderVisible = ref(true);
|
||||||
const triggerTimes = ref(0);
|
const triggerTimes = ref(0);
|
||||||
const showSendBtn = ref(false);
|
const showSendBtn = ref(false);
|
||||||
@ -301,7 +306,16 @@ const updateInputContent = (content: string) => {
|
|||||||
updateInputHeight();
|
updateInputHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateInputPosition = () => {
|
||||||
|
const container = inputContainer.value;
|
||||||
|
const text = textContainer.value;
|
||||||
|
|
||||||
|
// 设置 bottom
|
||||||
|
text.style.bottom = `${container.offsetHeight - 80}px`;
|
||||||
|
};
|
||||||
|
|
||||||
function onKeydown(e: KeyboardEvent) {
|
function onKeydown(e: KeyboardEvent) {
|
||||||
|
updateInputPosition();
|
||||||
// 如果是 Esc
|
// 如果是 Esc
|
||||||
if (e.code === "Escape") {
|
if (e.code === "Escape") {
|
||||||
assistantStore.selectMenu = false;
|
assistantStore.selectMenu = false;
|
||||||
@ -336,6 +350,8 @@ function updateInputHeight() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateInputPosition();
|
||||||
|
|
||||||
const input = inputText.value;
|
const input = inputText.value;
|
||||||
const container = inputContainer.value;
|
const container = inputContainer.value;
|
||||||
const action = actionContainer.value;
|
const action = actionContainer.value;
|
||||||
@ -398,10 +414,14 @@ function updateInputHeight() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCompositionStart() {
|
function handleCompositionStart() {
|
||||||
|
updateInputPosition();
|
||||||
|
|
||||||
compositionStart.value = true;
|
compositionStart.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCompositionEnd() {
|
function handleCompositionEnd() {
|
||||||
|
updateInputPosition();
|
||||||
|
|
||||||
compositionStart.value = false;
|
compositionStart.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
144
src/components/chat/Layout.vue
Normal file
144
src/components/chat/Layout.vue
Normal 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>
|
@ -19,6 +19,7 @@ if (process.env.NODE_ENV === "production") {
|
|||||||
|
|
||||||
config.backend = "https://amber-api.leaflow.cn";
|
config.backend = "https://amber-api.leaflow.cn";
|
||||||
|
|
||||||
|
|
||||||
// console.log("api endpoint: " + config.backend);
|
// console.log("api endpoint: " + config.backend);
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NLayout } from "naive-ui";
|
import { NLayout } from "naive-ui";
|
||||||
// import { useUserStore } from "../stores/user";
|
import { useUserStore } from "../stores/user";
|
||||||
// import Guest from "../pages/guest/index.vue";
|
// import Guest from "../pages/guest/index.vue";
|
||||||
// import router from "../router";
|
// import router from "../router";
|
||||||
import Header from "./Header.vue";
|
import Header from "./Header.vue";
|
||||||
import element from "@/config/element";
|
import element from "@/config/element";
|
||||||
import { useIsMobile } from "@/utils/composables";
|
import { useIsMobile } from "@/utils/composables";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
import ChatLayout from "@/components/chat/Layout.vue";
|
||||||
// const currentRoute = computed(() => router.currentRoute.value.name);
|
// const currentRoute = computed(() => router.currentRoute.value.name);
|
||||||
|
|
||||||
// const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@ -82,7 +83,14 @@ const onScroll = (e: Event) => {
|
|||||||
|
|
||||||
<n-layout :native-scrollbar="isMobile">
|
<n-layout :native-scrollbar="isMobile">
|
||||||
<div class="!pt-2">
|
<div class="!pt-2">
|
||||||
|
<div v-if="userStore.logined">
|
||||||
|
<ChatLayout>
|
||||||
<router-view :key="route.path"> </router-view>
|
<router-view :key="route.path"> </router-view>
|
||||||
|
</ChatLayout>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<router-view :key="route.path"> </router-view>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
||||||
|
70
src/pages/chat/Chat.vue
Normal file
70
src/pages/chat/Chat.vue
Normal 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>
|
@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<Chat v-if="userStore.logined" />
|
<Chat v-if="userStore.logined" />
|
||||||
<Guest v-else />
|
<Home v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Guest from "@/pages/Guest.vue";
|
import Home from "@/pages/home/index.vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import Chat from "@/pages/chat/Chat.vue";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,6 +4,7 @@ import { defineStore } from "pinia";
|
|||||||
export const useAppStore = defineStore("app", {
|
export const useAppStore = defineStore("app", {
|
||||||
persist: false,
|
persist: false,
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
headerHeight: 64,
|
||||||
updating: false,
|
updating: false,
|
||||||
contentScrollHeight: 0,
|
contentScrollHeight: 0,
|
||||||
contentScrollPosition: 0,
|
contentScrollPosition: 0,
|
||||||
|
1
src/typed-router.d.ts
vendored
1
src/typed-router.d.ts
vendored
@ -24,6 +24,7 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'/auth/login': RouteRecordInfo<'/auth/login', '/auth/login', Record<never, never>, Record<never, never>>,
|
'/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>>,
|
'/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/[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/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/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>>,
|
'/errors/404': RouteRecordInfo<'/errors/404', '/errors/404', Record<never, never>, Record<never, never>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user