Explorar el Código

change layout for client

tycoding hace 1 año
padre
commit
98bff2d13f

+ 2 - 0
langchat-core/src/main/java/cn/tycoding/langchat/core/provider/build/QWenModelBuildHandler.java

@@ -69,6 +69,7 @@ public class QWenModelBuildHandler implements ModelBuildHandler {
                     .apiKey(model.getApiKey())
                     .modelName(model.getModel())
                     .baseUrl(model.getBaseUrl())
+                    .enableSearch(true)
                     .maxTokens(model.getResponseLimit())
                     .temperature(Float.parseFloat(model.getTemperature().toString()))
                     .topP(model.getTopP())
@@ -96,6 +97,7 @@ public class QWenModelBuildHandler implements ModelBuildHandler {
                     .apiKey(model.getApiKey())
                     .modelName(model.getModel())
                     .baseUrl(model.getBaseUrl())
+                    .enableSearch(true)
                     .maxTokens(model.getResponseLimit())
                     .temperature(Float.parseFloat(model.getTemperature().toString()))
                     .topP(model.getTopP())

+ 2 - 1
langchat-server/src/main/java/cn/tycoding/langchat/server/endpoint/ChatEndpoint.java

@@ -36,6 +36,7 @@ import dev.langchain4j.data.message.SystemMessage;
 import dev.langchain4j.data.message.UserMessage;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -56,7 +57,7 @@ public class ChatEndpoint {
 
     @PostMapping("/chat/completions")
     @SaCheckPermission("chat:completions")
-    public Object chat(@RequestBody ChatReq req) {
+    public SseEmitter chat(@RequestBody ChatReq req) {
         StreamEmitter emitter = new StreamEmitter();
         req.setEmitter(emitter);
         req.setUserId(String.valueOf(AuthUtil.getUserId()));

BIN
langchat-ui-client/src/assets/app.png


+ 104 - 69
langchat-ui-client/src/layout/Sider.vue

@@ -45,8 +45,16 @@
   ];
 
   async function onLogout() {
-    await userStore.logout();
-    await router.push({ name: 'Login' });
+    dialog.warning({
+      title: '提示',
+      content: '你确定注销当前账户吗',
+      positiveText: '注销',
+      negativeText: '取消',
+      onPositiveClick: async () => {
+        await userStore.logout();
+        await router.push({ name: 'Login' });
+      },
+    });
   }
   async function onLogin() {
     dialog.warning({
@@ -59,69 +67,85 @@
       },
     });
   }
+
+  function onProfile() {
+    router.push({ name: 'Profile' });
+  }
 </script>
 
 <template>
-  <div class="h-full bg-gray-100 dark:bg-transparent flex flex-col">
-    <n-scrollbar class="flex-1">
-      <div class="flex flex-col gap-3 border-neutral-800 pt-3 pl-3 pr-3">
+  <div
+    class="h-full bg-[#fafafa] dark:bg-transparent flex border dark:border-[#ffffff17] flex-col w-full rounded-lg"
+  >
+    <n-scrollbar class="flex-1 w-full">
+      <div
+        class="flex flex-col gap-3 border-neutral-800 dark:border-[#ffffff17] pt-3 pl-3 pr-3 w-full"
+      >
+        <div class="text-lg gap-2 text-center flex justify-center items-center">
+          <img height="30" src="@/assets/login/logo.png" width="30" />
+          <span class="font-bold">LangChat</span>
+        </div>
+        <n-divider class="!my-0 !py-0" />
         <n-button
           v-for="item in routesConst"
           :key="item.name"
-          :class="router.currentRoute.value.name == item.name ? '!text-[#7fe7c4]' : ''"
+          :class="
+            router.currentRoute.value.name == item.name
+              ? '!text-[#18a058] w-full !bg-[#eff0f0] dark:!bg-[#ffffff1a] dark:border-[#ffffff17] !rounded-[8px]'
+              : 'w-full'
+          "
           text
           @click="router.push({ name: item.name })"
         >
-          <div
-            class="rounded-[10px] bg-white dark:bg-[#34373f] w-[60px] pt-2 pb-1 flex justify-center items-center cursor-pointer flex-col"
-          >
-            <SvgIcon :icon="item.meta?.icon" class="text-2xl" />
-            <div class="mt-1 mb-1 text-[11px]"> {{ item.meta?.label }} </div>
+          <div class="w-full px-4 py-3 flex items-center cursor-pointer gap-3.5">
+            <SvgIcon :icon="item.meta?.icon" class="text-lg" />
+            <div class="mt-1 mb-1 text-[14px]"> {{ item.meta?.label }} </div>
           </div>
         </n-button>
       </div>
     </n-scrollbar>
 
     <div class="m-2 flex flex-col justify-center items-center gap-2 mb-4 bottom-0">
-      <n-space class="mb-2" vertical>
-        <n-popover placement="right" trigger="hover">
-          <template #trigger>
-            <n-button
-              v-if="appStore.theme == 'light'"
-              size="small"
-              text
-              @click="appStore.setTheme('dark')"
-            >
-              <template #icon>
-                <SvgIcon icon="ri:sun-foggy-line" />
-              </template>
-            </n-button>
-            <n-button
-              v-if="appStore.theme == 'dark'"
-              size="small"
-              text
-              @click="appStore.setTheme('light')"
-            >
-              <template #icon>
-                <SvgIcon icon="ri:moon-foggy-line" />
-              </template>
-            </n-button>
-          </template>
-          <span>{{ t('side.theme') }}</span>
-        </n-popover>
+      <n-divider class="!my-0 !py-0" />
+      <!--      <n-space class="mb-2" vertical>
+				<n-popover placement="right" trigger="hover">
+					<template #trigger>
+						<n-button
+							v-if="appStore.theme == 'light'"
+							size="small"
+							text
+							@click="appStore.setTheme('dark')"
+						>
+							<template #icon>
+								<SvgIcon icon="ri:sun-foggy-line" />
+							</template>
+						</n-button>
+						<n-button
+							v-if="appStore.theme == 'dark'"
+							size="small"
+							text
+							@click="appStore.setTheme('light')"
+						>
+							<template #icon>
+								<SvgIcon icon="ri:moon-foggy-line" />
+							</template>
+						</n-button>
+					</template>
+					<span>{{ t('side.theme') }}</span>
+				</n-popover>
 
-        <n-popselect
-          v-model:value="language"
-          :options="languageOptions"
-          placement="right"
-          trigger="click"
-          @update-value="(value) => appStore.setLanguage(value)"
-        >
-          <n-button text>
-            <SvgIcon class="text-xl" icon="ph:translate-bold" />
-          </n-button>
-        </n-popselect>
-      </n-space>
+				<n-popselect
+					v-model:value="language"
+					:options="languageOptions"
+					placement="right"
+					trigger="click"
+					@update-value="(value) => appStore.setLanguage(value)"
+				>
+					<n-button text>
+						<SvgIcon class="text-xl" icon="ph:translate-bold" />
+					</n-button>
+				</n-popselect>
+			</n-space>-->
 
       <template v-if="user == null">
         <n-avatar class="cursor-pointer !text-black" round>
@@ -131,28 +155,39 @@
           {{ t('side.login') }}
         </n-button>
       </template>
-      <template v-else>
-        <n-popover placement="right" trigger="hover">
-          <template #trigger>
-            <n-avatar
-              :fallback-src="defaultAvatar"
-              :src="user.avatar ?? '/avatar.jpg'"
-              class="cursor-pointer"
-              round
-              @click="router.push({ name: 'Profile' })"
-            />
-          </template>
-          <span>{{ t('side.profile') }}</span>
-        </n-popover>
-        <n-ellipsis style="max-width: 70px">
-          {{ user.username }}
-        </n-ellipsis>
-        <n-button secondary size="tiny" type="warning" @click="onLogout()">
-          {{ t('side.logout') }}
+      <div v-else class="flex w-full flex-col gap-3 mb-2 px-2">
+        <div class="flex gap-2 items-center px-2">
+          <n-avatar
+            :fallback-src="defaultAvatar"
+            :src="user.avatar ?? '/avatar.jpg'"
+            class="cursor-pointer w-[30px]"
+            round
+          />
+          <n-ellipsis style="max-width: 85px">
+            {{ user.username }}
+          </n-ellipsis>
+        </div>
+
+        <n-button class="!rounded-lg" size="small" tertiary type="info" @click="onProfile()">
+          <div class="w-full text-center flex justify-center items-center gap-2">
+            <SvgIcon class="text-lg" icon="iconamoon:profile" />
+            <span>个人中心</span>
+          </div>
         </n-button>
-      </template>
+
+        <n-button class="!rounded-lg" secondary size="small" type="warning" @click="onLogout()">
+          <div class="w-full text-center flex justify-center items-center gap-2">
+            <SvgIcon class="text-lg" icon="material-symbols:logout" />
+            <span>注销账户</span>
+          </div>
+        </n-button>
+      </div>
     </div>
   </div>
 </template>
 
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+  ::v-deep(.n-button .n-button__content) {
+    width: 100% !important;
+  }
+</style>

+ 27 - 21
langchat-ui-client/src/layout/index.vue

@@ -31,25 +31,31 @@
 </script>
 
 <template>
-  <n-layout class="h-full" has-sider>
-    <n-layout-sider
-      :collapsed="collapsed"
-      :collapsed-width="0"
-      :width="77"
-      bordered
-      collapse-mode="width"
-      show-trigger="bar"
-      @collapse="collapsed = true"
-      @expand="collapsed = false"
-    >
-      <Sider />
-    </n-layout-sider>
-    <n-layout-content>
-      <RouterView v-slot="{ Component, route }">
-        <keep-alive>
-          <component :is="Component" :key="route.fullPath" />
-        </keep-alive>
-      </RouterView>
-    </n-layout-content>
-  </n-layout>
+  <div class="h-screen w-full grid-mask">
+    <n-layout class="h-full" has-sider>
+      <n-layout-sider
+        :collapsed="collapsed"
+        :collapsed-width="0"
+        :width="200"
+        @collapse="collapsed = true"
+        @expand="collapsed = false"
+      >
+        <div class="m-4 mr-2" style="height: calc(100vh - 33px)">
+          <Sider />
+        </div>
+      </n-layout-sider>
+      <n-layout-content>
+        <RouterView v-slot="{ Component, route }">
+          <keep-alive>
+            <div
+              class="h-full m-4 ml-2 border rounded-lg bg-white dark:bg-transparent dark:border-[#ffffff17]"
+              style="height: calc(100vh - 33px)"
+            >
+              <component :is="Component" :key="route.fullPath" />
+            </div>
+          </keep-alive>
+        </RouterView>
+      </n-layout-content>
+    </n-layout>
+  </div>
 </template>

+ 7 - 8
langchat-ui-client/src/router/index.ts

@@ -19,7 +19,6 @@ import type { RouteRecordRaw } from 'vue-router';
 import { createRouter, createWebHashHistory } from 'vue-router';
 import { Layout } from '@/layout';
 import { setupPageGuard } from '@/router/permission';
-import { t } from '@/locales';
 
 const routes: RouteRecordRaw[] = [
   {
@@ -32,7 +31,7 @@ const routes: RouteRecordRaw[] = [
         path: '/home',
         name: 'Home',
         meta: {
-          label: t('menu.home'),
+          label: '首页',
           icon: 'tabler:smart-home',
         },
         component: () => import('@/views/modules/home/index.vue'),
@@ -41,7 +40,7 @@ const routes: RouteRecordRaw[] = [
         path: '/chat/:uuid?',
         name: 'Chat',
         meta: {
-          label: t('menu.chat'),
+          label: '聊天助手',
           icon: 'bx:chat',
         },
         component: () => import('@/views/modules/chat/index.vue'),
@@ -50,7 +49,7 @@ const routes: RouteRecordRaw[] = [
         path: '/doc',
         name: 'Doc',
         meta: {
-          label: t('menu.doc'),
+          label: '文档分析',
           icon: 'mingcute:doc-line',
         },
         component: () => import('@/views/modules/doc/index.vue'),
@@ -59,7 +58,7 @@ const routes: RouteRecordRaw[] = [
         path: '/write',
         name: 'Write',
         meta: {
-          label: t('menu.write'),
+          label: '文本撰写',
           icon: 'solar:document-add-linear',
         },
         component: () => import('@/views/modules/write/index.vue'),
@@ -68,7 +67,7 @@ const routes: RouteRecordRaw[] = [
         path: '/image',
         name: 'Image',
         meta: {
-          label: t('menu.image'),
+          label: 'AI 文生图',
           icon: 'radix-icons:image',
         },
         component: () => import('@/views/modules/image/index.vue'),
@@ -77,7 +76,7 @@ const routes: RouteRecordRaw[] = [
         path: '/mindmap',
         name: 'MindMap',
         meta: {
-          label: t('menu.mindmap'),
+          label: '思维导图',
           icon: 'ri:mind-map',
         },
         component: () => import('@/views/modules/mindmap/index.vue'),
@@ -95,7 +94,7 @@ const baseRoutes: RouteRecordRaw[] = [
         path: 'index',
         name: 'Profile',
         meta: {
-          label: t('menu.user'),
+          label: '个人中心',
         },
         component: () => import('@/views/profile/index.vue'),
       },

+ 10 - 0
langchat-ui-client/src/styles/global.less

@@ -71,3 +71,13 @@ body {
 .n-avatar {
 	background: transparent !important;
 }
+
+.grid-mask {
+	height: 100%;
+	background-image: radial-gradient(circle at center, rgba(0, 0, 0, 0.13) 0.8px, transparent 0);
+	background-size: 16px 16px;
+	background-repeat: round;
+}
+.n-layout-sider, .n-layout {
+	background-color: transparent !important;
+}

+ 9 - 9
langchat-ui-client/src/views/modules/home/components/CardList.vue

@@ -36,11 +36,12 @@
 <template>
   <n-grid :x-gap="12" :y-gap="12" class="mt-4" cols="1 400:2 1200:3 1300:4">
     <n-grid-item v-for="item in props.list" :key="item">
-      <n-card content-style="padding:0px" hoverable>
+      <n-card class="!rounded-lg" content-style="padding:0px" hoverable>
         <div>
-          <n-thing class="inline-block bg-white dark:bg-[#34373f] p-4 rounded-[2px] cursor-pointer">
+          <n-thing class="inline-block bg-white dark:bg-[#34373f] p-3 rounded-[6px] cursor-pointer">
             <template #avatar>
-              <n-avatar :size="80" :src="item.cover" />
+              <n-avatar v-if="item.cover != null" :size="80" :src="item.cover" />
+              <img v-else alt="" height="80" src="@/assets/app.png" width="80" />
             </template>
             <template #header>
               <n-ellipsis class="text-[18px]" style="max-width: 200px">
@@ -48,13 +49,12 @@
               </n-ellipsis>
             </template>
             <template #description>
-              <n-ellipsis :line-clamp="3" class="text-[14px] text-gray-400 h-[68px]">
+              <n-ellipsis
+                :line-clamp="3"
+                :tooltip="false"
+                class="text-[14px] text-gray-400 h-[68px]"
+              >
                 {{ item.prompt }}
-                <template #tooltip>
-                  <div style="width: 400px">
-                    {{ item.prompt }}
-                  </div>
-                </template>
               </n-ellipsis>
             </template>
             <template #footer>

+ 4 - 10
langchat-ui-client/src/views/modules/home/index.vue

@@ -47,10 +47,10 @@
 </script>
 
 <template>
-  <div class="h-full w-full flex grid-mask">
-    <div class="w-full max-w-screen-2xl m-auto">
-      <div class="flex h-screen justify-center items-center flex-col p-2">
-        <div class="flex flex-col justify-center w-full items-center mt-14">
+  <div class="w-full flex">
+    <div class="w-full max-w-screen-xl m-auto">
+      <div class="flex justify-center items-center flex-col p-2">
+        <div class="flex flex-col justify-center w-full items-center mt-12">
           <NGradientText :size="37" type="success"> {{ $t('home.title') }} </NGradientText>
           <div class="text-[18px] h-[28px] text-gray-600">
             <span id="typed"></span>
@@ -98,12 +98,6 @@
 </template>
 
 <style lang="less" scoped>
-  .grid-mask {
-    height: 100%;
-    background-image: radial-gradient(circle at center, rgba(0, 0, 0, 0.13) 0.8px, transparent 0);
-    background-size: 16px 16px;
-    background-repeat: round;
-  }
   ::v-deep(.n-spin-container) {
     width: 100%;
     height: 100%;