1
0
forked from Leaf/amber-ui
This commit is contained in:
ivamp 2024-09-12 13:49:24 +08:00
parent a0fa982cfd
commit 20574c967e
13 changed files with 230 additions and 97 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

3
src/components.d.ts vendored
View File

@ -11,10 +11,9 @@ declare module 'vue' {
Chat: typeof import('./components/chat/chat.vue')['default']
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
Container: typeof import('./components/Container.vue')['default']
copy: typeof import('./components/AssistantMenu.vue')['default']
LeftSetting: typeof import('./components/LeftSetting.vue')['default']
LeftSettings: typeof import('./components/LeftSettings.vue')['default']
Menu: typeof import('./components/Menu.vue')['default']
MessageList: typeof import('./components/chat/MessageList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
UserMenu: typeof import('./components/UserMenu.vue')['default']

View File

@ -15,13 +15,16 @@ const userStore = useUserStore();
></Header>
<div class="p-4">
<router-view v-slot="{ Component }" :key="route.path">
<n-scrollbar >
<router-view v-slot="{ Component }" :key="route.path">
<transition mode="out-in" name="fade">
<div>
<component :is="Component" />
</div>
</transition>
</router-view>
</n-scrollbar>
</div>
<!-- <router-view></router-view> -->

View File

@ -1,11 +1,13 @@
<template>
<n-tabs type="segment" animated>
<n-tabs type="segment" animated class="select-none">
<n-tab-pane name="chap1" tab="对话">
<n-list hoverable clickable>
<n-list-item
v-for="c in chatStore.chats"
:key="c.id"
:class="c.id == chatStore.currentChatId ? 'text-primary' : ''"
:class="
c.id === chatStore.currentChatId ? ' bg-gray-100' : 'text-red-50'
"
@click="viewChat(c.id ?? 0)"
>
<n-thing> {{ c.name }} </n-thing>

View File

@ -0,0 +1,60 @@
<template>
<!-- flex 布局 -->
<n-flex> </n-flex>
<n-divider> <small>这是聊天的开头</small> </n-divider>
<div v-for="(message, index) in chat_messages" :key="index">
<div v-if="message.role === 'user'">
<!-- 用户消息 -->
<n-flex justify="end">
<div class="align-middle">
<v-md-preview :text="message.content" height="500px"></v-md-preview>
</div>
<n-avatar round size="large" :src="userStore.user.avatar" />
</n-flex>
</div>
<div v-else-if="message.role === 'assistant'">
<!-- 助理消息 -->
<n-flex justify="start">
<n-avatar round size="large" :src="leaflowPng" />
<div class="align-middle">
<v-md-preview :text="message.content" height="500px"></v-md-preview>
</div>
</n-flex>
</div>
</div>
<!-- 这里要加上输入框占的部分但是那个占了很多所以设置大一点 -->
<div style="margin-bottom: 10rem;"></div>
</template>
<script lang="ts" setup>
import { Ref } from "vue";
import { EntityChatMessage } from "@/api";
import { useUserStore } from "@/stores/user";
import VMdPreview from "@kangc/v-md-editor/lib/preview";
import "@kangc/v-md-editor/lib/style/preview.css";
import githubTheme from "@kangc/v-md-editor/lib/theme/github.js";
import "@kangc/v-md-editor/lib/theme/style/github.css";
import leaflowPng from "@/assets/images/leaflow.png";
// highlightjs
import hljs from "highlight.js";
VMdPreview.use(githubTheme, {
Hljs: hljs,
});
const userStore = useUserStore();
const props = defineProps({
chat_messages: {
required: true,
type: Array as () => EntityChatMessage[],
},
});
const chat_messages = toRef(props, "chat_messages") as Ref<EntityChatMessage[]>;
</script>

View File

@ -1,80 +1,109 @@
<template>
<div class="relative flex flex-col h-[calc(100vh-var(--header-height)*1.9)]">
<div class="flex-grow mt-3 mb-1 text-5xl">
<n-gradient-text type="info" class="pr-3 pb-2 pt-2">
你好{{ userStore.user.name }}
</n-gradient-text>
<br />
<div class="pr-3 mt-8 text-2xl">
<n-text depth="3"> 有什么我可以帮您的吗 </n-text>
</div>
</div>
<div class="absolute bottom-0 left-0 right-0 pb-5">
<div
ref="inputContainer"
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
@focusin="onFocused"
@focusout="onBlurred"
>
<div class="overflow-x-hidden h-full w-full flex items-center">
<n-scrollbar class="max-h-96">
<div
ref="inputText"
:class="{ 'has-placeholder': isPlaceholderVisible }"
contenteditable="true"
placeholder="请输入文本..."
class="input-text max-w-full outline-none text-lg text-pretty pl-2"
@input="updateInputHeight"
></div>
</n-scrollbar>
<div
class="relative flex flex-col h-[calc(100vh-var(--header-height)*1.9)] lg:items-center"
>
<div class="w-4/5">
<div>
<div class="flex-grow mt-3 mb-1 text-5xl" v-if="!chatMessages?.length">
<n-gradient-text type="info" class="pr-3 pb-2 pt-2">
你好{{ userStore.user.name }}
</n-gradient-text>
<br />
<div class="pr-3 mt-8 text-2xl">
<n-text depth="3"> 有什么我可以帮您的吗 </n-text>
</div>
</div>
<div v-else>
<MessageList :chat_messages="chatMessages" />
</div>
</div>
<div class="fixed bottom-0 left-0 right-0 pb-10">
<div
ref="actionContainer"
class="flex [&>button]:ml-2 pr-4 justify-end"
ref="inputContainer"
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
@keyup.enter="sendText"
>
<n-button tertiary circle size="large">
<template #icon>
<n-icon><DocumentAttachOutline /></n-icon>
</template>
</n-button>
<n-button tertiary circle size="large">
<template #icon>
<n-icon><MicOutline /></n-icon>
</template>
</n-button>
<n-button tertiary circle size="large" v-show="showSendBtn">
<template #icon>
<n-icon><SendOutline /></n-icon>
</template>
</n-button>
<div class="overflow-x-hidden h-full w-full flex items-center">
<n-scrollbar class="max-h-96">
<div
ref="inputText"
:class="{ 'has-placeholder': isPlaceholderVisible }"
contenteditable="true"
placeholder="请输入文本..."
class="input-text max-w-full outline-none text-lg text-pretty pl-2 min-h-6"
@input="updateInputHeight"
></div>
</n-scrollbar>
</div>
<div
ref="actionContainer"
class="flex [&>button]:ml-2 pr-4 justify-end"
>
<n-button tertiary circle size="large">
<template #icon>
<n-icon><DocumentAttachOutline /></n-icon>
</template>
</n-button>
<n-button tertiary circle size="large">
<template #icon>
<n-icon><MicOutline /></n-icon>
</template>
</n-button>
<n-button tertiary circle size="large" v-show="showSendBtn">
<template #icon>
<n-icon><SendOutline /></n-icon>
</template>
</n-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMessage } from "naive-ui";
import { useUserStore } from "../../stores/user";
import { InputHTMLAttributes, onMounted, ref } from "vue";
import { onMounted, ref } from "vue";
import {
SendOutline,
MicOutline,
DocumentAttachOutline,
} from "@vicons/ionicons5";
import { EntityChatMessage } from "@/api";
import getApi from "@/plugins/api";
// chatId
const chatId: Ref<string | number | undefined | null> = ref(null);
// const props = defineProps<{
// chatId: string | number | undefined | null;
// }>();
// prop, chatId
const props = defineProps({
chatId: {
type: [String, Number],
required: false,
default: null,
},
});
onMounted(() => {
chatId.value = props.chatId;
});
const message = useMessage();
const userStore = useUserStore();
const inputMessage = ref("");
const compositionStart = ref(false);
const inputContainer: any = ref(null);
const inputText: InputHTMLAttributes | null = ref(null);
const inputText: any = ref(null);
const actionContainer: any = ref(null);
const isPlaceholderVisible = ref(true);
const triggerTimes = ref(0);
const showSendBtn = ref(false);
const content = ref("");
const chatMessages: Ref<EntityChatMessage[] | undefined> = ref([]);
function updateInputHeight() {
if (!inputText?.value || !inputContainer.value || !actionContainer.value) {
@ -137,6 +166,45 @@ function updateInputHeight() {
}
}
function handleCompositionStart() {
compositionStart.value = true;
}
function handleCompositionEnd() {
compositionStart.value = false;
}
function sendText() {
if (!inputText?.value) {
return;
}
const input = inputText.value;
const textContent = input.innerText.trim();
if (textContent === "") {
return;
}
//
sendMessage(textContent);
//
input.innerText = "";
updateInputHeight();
chatMessages.value?.push({
content: textContent,
role: "user",
});
}
function sendMessage(text: string) {
console.log("发送文本:", text);
//
}
function onFocused() {
if (!inputContainer.value) {
return;
@ -157,8 +225,20 @@ function onBlurred() {
container.classList.add("max-w-2xl");
}
async function getChatMessages() {
//
if (chatId.value) {
const cm = await getApi().ChatMessage.apiV1ChatsIdMessagesGet(
Number(chatId.value)
);
chatMessages.value = cm.data.data;
}
}
onMounted(() => {
updateInputHeight();
getChatMessages();
});
</script>

View File

@ -16,8 +16,6 @@ if (process.env.NODE_ENV === "production") {
config.oauth_client_id = "16";
}
config.backend = "https://amber-api.leaflow.cn";
// config.backend = "https://amber-api.leaflow.cn";
// console.log("api endpoint: " + config.backend);

View File

@ -6,6 +6,7 @@ import Menu from "../components/Menu.vue";
import { useUserStore } from "../stores/user";
import Guest from "../pages/guest/index.vue";
import router from "../router";
import Header from "./Header.vue";
const currentRoute = computed(() => router.currentRoute.value.name);
const userStore = useUserStore();
@ -19,7 +20,7 @@ const menuCollapsed = ref({
</script>
<template>
<n-layout position="absolute" :has-sider="true">
<n-layout position="absolute" :has-sider="false">
<!-- <n-layout-sider
v-if="userStore.logined && !isMobile"
:collapsed-width="0"
@ -35,7 +36,7 @@ const menuCollapsed = ref({
>
<Menu v-show="!isMobile"></Menu>
</n-layout-sider> -->
<n-layout-content>
<n-layout-content :native-scrollbar="false">
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
<Container v-else /> -->
<Guest v-if="!userStore.logined && !currentRoute?.startsWith('/auth')" />

View File

@ -24,14 +24,15 @@
:default-width="width"
placement="left"
>
<n-drawer-content>
<n-drawer-content title="有什么我可以帮您的吗" closable>
<LeftSettings></LeftSettings>
</n-drawer-content>
</n-drawer>
<n-icon
size="20"
size="24"
style="margin-left: 12px"
@click="showDrawer = true"
class="cursor-pointer"
>
<menu-outline />
</n-icon>
@ -42,6 +43,10 @@
<n-grid-item class="flex items-center justify-end mr-1.5">
<!-- 右侧 -->
<!-- 新对话 -->
<n-icon class="text-2xl mr-4 cursor-pointer" @click="backToHome">
<AddOutline />
</n-icon>
<!-- 助理选择 -->
<n-popover
:placement="userPlacement"
@ -50,7 +55,7 @@
style="padding: 0"
>
<template #trigger>
<n-icon class="text-2xl mr-4">
<n-icon class="text-2xl mr-4 cursor-pointer">
<PersonOutline />
</n-icon>
</template>
@ -85,7 +90,10 @@ import AssistantMenu from "../components/AssistantMenu.vue";
import { useAppStore } from "../stores/app";
import { useUserStore } from "../stores/user";
import { useIsMobile, useIsTablet } from "../utils/composables";
import { MenuOutline, PersonOutline } from "@vicons/ionicons5";
import { MenuOutline, PersonOutline, AddOutline } from "@vicons/ionicons5";
import router from "@/router";
const userStore = useUserStore();
const isMobile = useIsMobile();
const isTablet = useIsTablet();
@ -99,11 +107,17 @@ if (isMobile.value) {
width.value = window.innerWidth - 100;
} else {
// 40%
width.value = window.innerWidth * 0.4
width.value = window.innerWidth * 0.4;
}
const userPlacement = ref("bottom");
if (isMobile.value) {
userPlacement.value = "bottom";
}
const backToHome = () => {
router.push("/");
};
</script>

View File

@ -1,4 +1,3 @@
console.log("load")
const meta = document.createElement("meta");
meta.name = "naive-ui-style";
document.head.appendChild(meta);

View File

@ -1,9 +1,14 @@
<template>
<div>聊天记录 {{ chatId }}</div>
<div>
<Chat :chat-id="chatId" />
</div>
</template>
<script lang="ts" setup>
import { useChatStore } from "@/stores/chat";
const chatStore = useChatStore();
// @ts-ignore
const chatId = useRoute().params.id as number;
// chatStore.currentChatId = chatId;
chatStore.currentChatId = chatId;
</script>

View File

@ -5,34 +5,6 @@ import chat from "../components/chat/chat.vue";
const message = useMessage();
const userStore = useUserStore();
const options = [
{
label: "滨海湾金沙,新加坡",
key: "marina bay sands",
disabled: true,
},
{
label: "布朗酒店,伦敦",
key: "brown's hotel, london",
},
{
label: "亚特兰蒂斯巴哈马,拿骚",
key: "atlantis nahamas, nassau",
},
{
label: "比佛利山庄酒店,洛杉矶",
key: "the beverly hills hotel, los angeles",
},
];
function handleSelect(key: string | number) {
message.info(String(key));
}
function logout() {
userStore.logout();
}
</script>
<template>