快乐的梦鱼 2 hete
szülő
commit
c1224bf42b

+ 5 - 0
src/components/nav/NavBar.vue

@@ -12,6 +12,7 @@
       v-if="align !== 'left'"
       class="nana-nav-button-wrapper" :style="{
         marginRight: rightPillSpace ? `${menuButtonInfo.width}px` : undefined,
+        width: theme.resolveThemeSize(leftWidth),
       }"
     >
       <slot v-if="showLeftButton && leftButton" name="left">
@@ -132,6 +133,8 @@ export interface NavBarProps {
    * @default true
    */
   showLeftButton?: boolean;
+
+  leftWidth?: number|string;
   /**
    * 自定义背景颜色
    */
@@ -244,6 +247,7 @@ function handleButtonNavBack(button: NavBarButtonTypes|string, callback: () => v
 }
 .nana-nav-button-wrapper {
   position: relative;
+  display: flex;
   flex-direction: row;
   justify-content: flex-start;
   height: 100%;
@@ -251,6 +255,7 @@ function handleButtonNavBack(button: NavBarButtonTypes|string, callback: () => v
 }
 .nana-nav-button-wrapper-end {
   position: relative;
+  display: flex;
   flex-direction: row;
   justify-content: flex-end;
   height: 100%;

+ 17 - 4
src/pages.json

@@ -174,17 +174,30 @@
       "root": "pages/home/post",
       "pages": [
         {
-          "path": "publish",
+          "path": "detail",
+          "style": {
+            "navigationBarTitleText": "微信贴图详情",
+            "navigationStyle": "custom",
+            "enablePullDownRefresh": false
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/chat",
+      "pages": [
+        {
+          "path": "index",
           "style": {
-            "navigationBarTitleText": "发布微信贴图",
+            "navigationBarTitleText": "AI助手",
             "navigationStyle": "custom",
             "enablePullDownRefresh": false
           }
         },
         {
-          "path": "detail",
+          "path": "dependent/post/publish",
           "style": {
-            "navigationBarTitleText": "微信贴图详情",
+            "navigationBarTitleText": "发表贴图",
             "navigationStyle": "custom",
             "enablePullDownRefresh": false
           }

+ 2 - 1
src/pages/chat/components/ChatFooter.vue

@@ -46,13 +46,14 @@
         >
         </textarea>
         <IconButton 
+          v-if="props.chatInterfaceManager.uploadAttachment"
           icon="picture"
           :loading="props.chatManager.isLoading.value"
           :innerStyle="{
             borderRadius: '50%',
             padding: '10rpx',
           }"
-          @click="props.chatInterfaceManager.uploadAttachment?.()"
+          @click="props.chatInterfaceManager.uploadAttachment()"
         />
         <IconButton 
           icon="navigation"

+ 6 - 3
src/pages/chat/components/ChatMessageContainer.vue

@@ -21,7 +21,7 @@
       <scroll-view
         ref="scrollRectRef"
         scroll-y 
-        :style="{ flex: 1, height: '100%', maxHeight: '40vh' }"
+        :style="{ flex: 1, height, minHeight: height, maxHeight: height }"
         :scroll-top="scrollY"
       >
         <FlexCol gap="gap.sm">
@@ -69,12 +69,15 @@ export interface ChatMessageContainerExpose {
   stopMessageEditing: () => void;
 }
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
   chatManager: ChatManager;
   sessionManager: ChatSessionManager;
   historyItemsPagerManager?: ChatHistoryItemsPagerManager;
   chatInterfaceManager: ChatInterfaceManager;
-}>();
+  height?: string;
+}>(), {
+  height: '40vh',
+});
 
 const emit = defineEmits<{
   (e: 'intoSelectMode', messageIds: number[]): void;

+ 16 - 11
src/pages/home/post/agent.vue

@@ -30,7 +30,12 @@
         :chatInterfaceManager="interfaceManager"
       >
         <template #mulitSelectMode>
-          <ChatMulitSelectBar :selectedCount="selectedCount" :messages="messages" :sessionManager="sessionManager" />
+          <ChatMulitSelectBar 
+            :selectedCount="selectedCount" 
+            :messages="messages" 
+            :sessionManager="sessionManager" 
+            @cancel="exitSelectMode"
+          />
         </template>
         <template #header>
 
@@ -42,19 +47,19 @@
 
 <script setup lang="ts">  
 import { onMounted, ref, type Ref } from 'vue';
-import { useChatSession } from '@/pages/chat/composables/useChatSession';
-import { useChat, type ChatInterfaceManager } from '@/pages/chat/core/Chat';
-import { useChatHistoryItemsPager } from '@/pages/chat/composables/useChatHistoryItemsPager';
-import { useChatSelection } from '@/pages/chat/composables/useChatSelection';
+import { useChatSession } from '../../composables/useChatSession';
+import { useChat, type ChatInterfaceManager } from '../../core/Chat';
+import { useChatHistoryItemsPager } from '../../composables/useChatHistoryItemsPager';
+import { useChatSelection } from '../../composables/useChatSelection';
 import { useAgentTools } from './composables/agentTools';
-import { ChatMessage as ChatMessageModel } from '@/pages/chat/model/Message';
-import { ChatGroups } from '@/pages/chat/core/Groups';
-import ChatFooter from '../../chat/components/ChatFooter.vue';
-import ChatMessageContainer from '../../chat/components/ChatMessageContainer.vue';
+import { ChatMessage as ChatMessageModel } from '../../model/Message';
+import { ChatGroups } from '../../core/Groups';
+import ChatFooter from '../../components/ChatFooter.vue';
+import ChatMessageContainer from '../../components/ChatMessageContainer.vue';
+import ChatSessionSidebar from '../../components/Session/ChatSessionSidebar.vue';
+import ChatMulitSelectBar from '../../components/Footer/ChatMulitSelectBar.vue';
 import NavBar from '@/components/nav/NavBar.vue';
-import ChatSessionSidebar from '@/pages/chat/components/Session/ChatSessionSidebar.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
-import ChatMulitSelectBar from '@/pages/chat/components/Footer/ChatMulitSelectBar.vue';
 
 const messages = ref<ChatMessageModel[]>([]) as Ref<ChatMessageModel[]>;
 const interfaceManager: ChatInterfaceManager = {

src/pages/home/post/composables/agentTools.ts → src/pages/chat/dependent/post/composables/agentTools.ts


+ 1 - 1
src/pages/home/post/publish.vue

@@ -32,7 +32,7 @@
               :upload="uploadImage"
               @updateList="onUpdateList"
             />
-            <Field v-model="title" type="text" placeholder="输入标题(可选)" :maxLength="30" showWordLimit bac />
+            <Field v-model="title" type="text" placeholder="输入标题(可选)" :maxLength="30" showWordLimit />
             <Field 
               v-model="content" 
               type="text" 

+ 149 - 0
src/pages/chat/index.vue

@@ -0,0 +1,149 @@
+<template>
+  <CommonRoot>
+    <FlexCol :innerStyle="{
+      backgroundImage: 'url(https://xy.wenlvti.net/app_static/images/dig/TopBanner.png)',
+      backgroundSize: '100% auto',
+      backgroundRepeat: 'no-repeat',
+      backgroundPosition: 'top center',
+      minHeight: '100vh',
+    }">
+      <StatusBarSpace />
+      <FlexCol v-if="showSessionSidebar" position="relative" width="100%" height="100%">
+        <NavBar 
+          title="历史会话" 
+          leftButton="close-bold" 
+          @leftButtonPressed="showSessionSidebar=false"
+        />
+        <ChatSessionSidebar :sessionManager="sessionManager" @close="showSessionSidebar = false" />
+      </FlexCol>
+      <ChatMessageContainer
+        v-else
+        :chatManager="chatManager"
+        :sessionManager="sessionManager"
+        :historyItemsPagerManager="historyItemsPagerManager"
+        :chatInterfaceManager="interfaceManager"
+        height="77vh"
+        @intoSelectMode="intoSelectMode"
+      >
+        <template #header>
+          <NavBar 
+            title="AI助手" 
+            leftButton="back"
+          >
+            <template #left-custom>
+              <FlexRow align="center">
+                <IconButton icon="menu" shape="square-full" @click="showSessionSidebar = true" />
+              </FlexRow>
+            </template>
+          </NavBar>
+        </template>
+        <template #footer>
+          <ChatFooter
+            :chatManager="chatManager" 
+            :chatInterfaceManager="interfaceManager"
+          >
+            <template #mulitSelectMode>
+              <ChatMulitSelectBar 
+                :selectedCount="selectedCount" 
+                :messages="messages" 
+                :sessionManager="sessionManager" 
+                @cancel="exitSelectMode"
+              />
+            </template>
+            <template #header>
+
+            </template>
+          </ChatFooter>
+        </template>
+      </ChatMessageContainer>
+    </FlexCol>
+  </CommonRoot>
+</template>
+
+<script setup lang="ts">  
+import { onMounted, ref, type Ref } from 'vue';
+import { useChatSession } from '@/pages/chat/composables/useChatSession';
+import { useChat, type ChatInterfaceManager } from '@/pages/chat/core/Chat';
+import { useChatHistoryItemsPager } from '@/pages/chat/composables/useChatHistoryItemsPager';
+import { useChatSelection } from '@/pages/chat/composables/useChatSelection';
+import { ChatMessage as ChatMessageModel } from '@/pages/chat/model/Message';
+import { ChatGroups } from '@/pages/chat/core/Groups';
+import ChatFooter from './components/ChatFooter.vue';
+import ChatMessageContainer from './components/ChatMessageContainer.vue';
+import NavBar from '@/components/nav/NavBar.vue';
+import ChatSessionSidebar from '@/pages/chat/components/Session/ChatSessionSidebar.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import ChatMulitSelectBar from '@/pages/chat/components/Footer/ChatMulitSelectBar.vue';
+import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
+import CommonRoot from '@/components/dialog/CommonRoot';
+import IconButton from '@/components/basic/IconButton.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+
+const messages = ref<ChatMessageModel[]>([]) as Ref<ChatMessageModel[]>;
+const interfaceManager: ChatInterfaceManager = {
+  messages,
+  getAttachmentList: async () => {
+    return [];
+  },
+};
+
+const showSessionSidebar = ref(false);
+
+const sessionManager = useChatSession({
+  messages,
+  enableSession: true,
+  onStop: () => chatManager.stop(),
+  onScrollToBottom: () => interfaceManager.scrollToBottom?.(),
+});
+const historyItemsPagerManager = useChatHistoryItemsPager({
+  messages,
+  sessionManager,
+});
+
+const chatManager = useChat({
+  interfaceManager,
+  sessionManager,
+  config: {
+    onGetSendOptions: () => ({
+      enableThinking: false,
+      enableSearch: false,
+      modelInfo: {
+        name: 'hunyuan-2.0-thinking-20251109',
+        value: 'hunyuan-2.0-thinking-20251109',
+        max_tokens: 10000,
+      },
+      model: 'hunyuan-2.0-thinking-20251109',
+      chatOptions: {
+        temperature: 0.5,
+        top_p: 1,
+        top_k: 40,
+        presence_penalty: 0,
+      },
+      customSystemPrompt: '',
+    }),
+    defaultSystemPrompt: `你是一个“乡村文化挖掘”智能知识助理,负责与用户聊天,回答用户问题。请尽量使用简洁明了的语言,避免使用专业术语。`,
+    onBuildWelcome: () => {
+      return {
+        welcomeMessage: '你好!欢迎使用亮乡源AI助手。可以与我讨论乡村文化挖掘相关的问题。',
+        welcomeActions: [
+          '乡源情怀是什么',
+          '乡源文化的传承与发展',
+          '乡源文化的保护与利用',
+          '乡村振兴的现状与未来', 
+        ],
+      }
+    },
+  }
+});
+
+const {
+  intoSelectMode,
+  exitSelectMode,
+  selectedCount,
+  isSelectMode,
+} = useChatSelection();
+
+onMounted(() => {
+  interfaceManager.scrollToBottom?.();
+});
+</script>

+ 18 - 9
src/pages/home/index.vue

@@ -19,14 +19,17 @@
     </FlexCol>
     <Height height="200px" />
     <FlexCol :gap="20" align="center">
-      <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
-        backgroundColor: 'white',
-        borderRadius: '20rpx',
-        borderWidth: '1px',
-        borderStyle: 'solid',
-        borderColor: themeContext.resolveThemeColor('primary'),
-        width: '650rpx',
-      }" />
+      <FlexRow justify="space-between" align="center" width="100%">
+        <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
+          backgroundColor: 'white',
+          borderRadius: '20rpx',
+          borderWidth: '1px',
+          borderStyle: 'solid',
+          borderColor: themeContext.resolveThemeColor('primary'),
+          width: '490rpx',
+        }" />
+        <Button icon="ai-thinking" @click="handleGoAI" text="问AI" />
+      </FlexRow>
       <Image 
         src="https://xy.wenlvti.net/app_static/images/home/BannerIndex.png" 
         width="700rpx" 
@@ -161,7 +164,6 @@ import { ArrayUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 import { toast } from '@/components/utils/DialogAction';
 import { navTo } from '@/components/utils/PageAction';
 import Image from '@/components/basic/Image.vue';
-import Loadmore from '@/components/display/loading/Loadmore.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Height from '@/components/layout/space/Height.vue';
@@ -185,12 +187,14 @@ import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
 import type { CityItem } from '@/api/map/MapApi';
 import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import { useAuthStore } from '@/store/auth';
+import { useReqireLogin } from '@/common/composeabe/RequireLogin';
 
 const emit = defineEmits(['goVillage']);
 
 const authStore = useAuthStore();
 const villageStore = useVillageStore();
 const themeContext = useTheme();
+const { requireLogin } = useReqireLogin();
 const searchKeywords = ref('');
 const showCityPopup = ref(false);
 const showMyFollowPopup = ref(false);
@@ -270,6 +274,11 @@ function handleSelectCity(city: CityItem) {
   showCityPopup.value = false;
   handleChangedCity(city.name);
 }
+function handleGoAI() {
+  requireLogin(async () => {
+    navTo('/pages/chat/index');
+  }, '暂时需要登录后才能使用AI助手');
+}
 
 async function loadInfo() {
   const res = await FollowVillageApi.getFollowVillageList({ page: 1, pageSize: 200 });

+ 1 - 1
src/pages/home/village/introd/card.vue

@@ -380,7 +380,7 @@ function handleGoPublish() {
     });
     return;
   }
-  navTo('/pages/home/post/publish', {
+  navTo('/pages/chat/dependent/post/publish', {
     tag: listActiveTag.value,
     villageId: villageStore.currentVillage?.id ?? undefined,
   });