Kaynağa Gözat

✨ feat(ai): 集成AI智能体功能

- 新增AI智能体模块,包含银龄助手、健康顾问、生活管家等角色
- 添加OpenAI API集成,支持智能对话功能
- 实现移动端AI助手页面,采用中国水墨风格设计
- 配置环境变量模板,包含AI服务相关配置
- 创建AI智能体数据表和相关API端点
- 优化路由结构,支持懒加载和错误边界处理

📝 docs(config): 更新环境配置文档

- 新增MY_CUSTOM_AI_BASE_URL和MY_CUSTOM_AI_API_KEY配置项
- 完善数据库、MinIO、邮件等服务配置说明
yourname 7 ay önce
ebeveyn
işleme
74802b857e

+ 37 - 0
.env.example

@@ -0,0 +1,37 @@
+# 数据库配置
+DB_HOST=localhost
+DB_PORT=3306
+DB_USERNAME=root
+DB_PASSWORD=
+DB_DATABASE=d8dai
+DB_SYNCHRONIZE=true
+DB_LOGGING=false
+
+# AI服务配置
+MY_CUSTOM_AI_BASE_URL=https://www.d8d.fun/api/v1/ai
+MY_CUSTOM_AI_API_KEY=your_api_key_here
+
+# MinIO配置
+MINIO_ENDPOINT=localhost
+MINIO_PORT=9000
+MINIO_ACCESS_KEY=minioadmin
+MINIO_SECRET_KEY=minioadmin
+MINIO_BUCKET_NAME=d8d-bucket
+MINIO_USE_SSL=false
+
+# JWT配置
+JWT_SECRET=your_jwt_secret_here
+
+# 邮件配置
+SMTP_HOST=smtp.gmail.com
+SMTP_PORT=587
+SMTP_USER=
+SMTP_PASS=
+
+# 文件上传配置
+MAX_FILE_SIZE=50mb
+UPLOAD_DIR=uploads
+
+# 环境配置
+NODE_ENV=development
+PORT=3000

+ 1 - 0
package.json

@@ -38,6 +38,7 @@
     "minio": "^8.0.5",
     "mysql2": "^3.14.1",
     "node-fetch": "^3.3.2",
+    "openai": "^5.11.0",
     "rc-upload": "^4.9.2",
     "react": "^19.1.0",
     "react-dom": "^19.1.0",

+ 20 - 0
pnpm-lock.yaml

@@ -92,6 +92,9 @@ importers:
       node-fetch:
         specifier: ^3.3.2
         version: 3.3.2
+      openai:
+        specifier: ^5.11.0
+        version: 5.11.0(ws@8.18.0)(zod@3.24.2)
       rc-upload:
         specifier: ^4.9.2
         version: 4.9.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -2545,6 +2548,18 @@ packages:
     resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==}
     engines: {node: '>= 0.8'}
 
+  openai@5.11.0:
+    resolution: {integrity: sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg==}
+    hasBin: true
+    peerDependencies:
+      ws: ^8.18.0
+      zod: ^3.23.8
+    peerDependenciesMeta:
+      ws:
+        optional: true
+      zod:
+        optional: true
+
   openapi3-ts@4.4.0:
     resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==}
 
@@ -5710,6 +5725,11 @@ snapshots:
 
   on-headers@1.1.0: {}
 
+  openai@5.11.0(ws@8.18.0)(zod@3.24.2):
+    optionalDependencies:
+      ws: 8.18.0
+      zod: 3.24.2
+
   openapi3-ts@4.4.0:
     dependencies:
       yaml: 2.8.0

+ 81 - 80
src/client/api.ts

@@ -1,10 +1,11 @@
-import { hc } from 'hono/client';
-import type {
+import { hc } from 'hono/client'
+import type { 
   AuthRoutes,
   UserRoutes,
   RoleRoutes,
   FileRoutes,
   CompanyRoutes,
+  MyCompanyRoutes,
   JobRoutes,
   ApplicationRoutes,
   FavoriteRoutes,
@@ -21,143 +22,143 @@ import type {
   SilverUsersKnowledgeStatsRoutes,
   SilverUsersKnowledgeInteractionsRoutes,
   SilverUsersKnowledgeRankingsRoutes,
+  SilverUserProfileRoutes,
   SilverTalentsAdminRoutes,
-  MyCompanyRoutes,
   SilverJobRoutes,
-  CompanyCertificationRoutes,
   SilverCompaniesRoutes,
   HomeIconRoutes,
-  SilverKnowledgeRoutes
-} from '@/server/api';
-import { axiosFetch } from './utils/axios'
-
-// 客户端实例 - 严格按照RPC规范命名
+  SilverKnowledgeRoutes,
+  AIAgentRoutes
+} from '@/server/api'
+import axios from 'axios'
+
+// 创建自定义fetch函数
+const axiosFetch = async (url: string, options?: RequestInit) => {
+  const config: any = {
+    url,
+    method: options?.method || 'GET',
+    headers: options?.headers || {},
+    data: options?.body,
+  }
+
+  // 从localStorage获取token
+  const token = localStorage.getItem('token')
+  if (token) {
+    config.headers.Authorization = `Bearer ${token}`
+  }
+
+  try {
+    const response = await axios(config)
+    return new Response(JSON.stringify(response.data), {
+      status: response.status,
+      statusText: response.statusText,
+      headers: response.headers,
+    })
+  } catch (error: any) {
+    if (error.response) {
+      return new Response(JSON.stringify(error.response.data), {
+        status: error.response.status,
+        statusText: error.response.statusText,
+        headers: error.response.headers,
+      })
+    }
+    throw error
+  }
+}
+
+// 创建客户端实例
 export const authClient = hc<AuthRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.auth;
+}).api.v1.auth
 
 export const userClient = hc<UserRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.users;
+}).api.v1.users
 
 export const roleClient = hc<RoleRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.roles;
+}).api.v1.roles
 
 export const fileClient = hc<FileRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.files;
+}).api.v1.files
 
-// 企业信息客户端 - 新增
-export const myCompanyClient = hc<MyCompanyRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1['companies'].my;
- 
-// 银龄岗具体资源客户端(调整为标准格式)
 export const companyClient = hc<CompanyRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'].companies;
+}).api.v1['silver-jobs'].companies
+
+export const myCompanyClient = hc<MyCompanyRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1.companies.my
 
 export const jobClient = hc<JobRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'].jobs;
+}).api.v1['silver-jobs'].jobs
 
 export const applicationClient = hc<ApplicationRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'].applications;
+}).api.v1['silver-jobs'].applications
 
 export const favoriteClient = hc<FavoriteRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'].favorites;
+}).api.v1['silver-jobs'].favorites
 
 export const viewClient = hc<ViewRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'].views;
+}).api.v1['silver-jobs'].views
 
 export const companyImageClient = hc<CompanyImageRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs']['company-images'];
-
-// 银龄用户资源客户端 - 重构为集中的对象,避免过深实例化
-export const silverUsersClient = {
-  knowledges: hc<SilverUsersKnowledgesRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users'].knowledges,
-  
-  ['knowledge-categories']: hc<SilverUsersKnowledgeCategoriesRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users']['knowledge-categories'],
-  
-  ['knowledge-tags']: hc<SilverUsersKnowledgeTagsRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users']['knowledge-tags'],
-  
-  ['knowledge-stats']: hc<SilverUsersKnowledgeStatsRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users']['knowledge-stats'],
-  
-  ['knowledge-interactions']: hc<SilverUsersKnowledgeInteractionsRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users']['knowledge-interactions'],
-  
-  ['knowledge-rankings']: hc<SilverUsersKnowledgeRankingsRoutes>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users']['knowledge-rankings'],
-  
-  profiles: hc<any>('/', {
-    fetch: axiosFetch,
-  }).api.v1['silver-users'].profiles
-};
-
-// 其他资源客户端
+}).api.v1['silver-jobs']['company-images']
+
+export const silverTalentsClient = hc<SilverTalentsRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['silver-talents']
+
 export const elderlyUniversityClient = hc<ElderlyUniversityRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['elderly-universities'];
+}).api.v1['elderly-universities']
 
 export const policyNewsClient = hc<PolicyNewsRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['policy-news'];
+}).api.v1['policy-news']
 
 export const userPreferenceClient = hc<UserPreferenceRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['user-preferences'];
+}).api.v1['user-preferences']
 
-// 首页API客户端
 export const homeClient = hc<HomeRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.home;
+}).api.v1.home
 
-export const silverTalentsClient = hc<SilverTalentsRoutes>('/', {
+export const silverUserProfileClient = hc<SilverUserProfileRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-talents']
+}).api.v1['silver-users'].profiles
 
-
-// 银龄库管理客户端
 export const silverTalentsAdminClient = hc<SilverTalentsAdminRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['admin']['silver-talents']
+}).api.v1.admin['silver-talents']
 
-// 公司认证管理客户端
-export const companyCertificationClient = hc<CompanyCertificationRoutes>('/', {
+export const silverJobClient = hc<SilverJobRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['company-certification']
+}).api.v1['silver-jobs']
 
-// 银龄公司客户端
 export const silverCompaniesClient = hc<SilverCompaniesRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1['silver-companies']
 
-// 银龄岗管理客户端
-export const silverJobClient = hc<SilverJobRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1['silver-jobs']
-
-// 首页图标管理客户端
 export const homeIconClient = hc<HomeIconRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1['home-icons']
 
-// 银龄智库管理客户端
 export const silverKnowledgeClient = hc<SilverKnowledgeRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-knowledges']
+}).api.v1['silver-knowledges']
+
+export const companyCertificationClient = hc<CompanyCertificationRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['company-certification']
+
+export const aiAgentClient = hc<AIAgentRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['ai-agents']

+ 381 - 0
src/client/mobile/pages/AIAgentsPage.tsx

@@ -0,0 +1,381 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../hooks/AuthProvider';
+import { aiAgentClient } from '@/client/api';
+import type { InferResponseType, InferRequestType } from 'hono/client';
+import { 
+  SparklesIcon, 
+  ChatBubbleLeftEllipsisIcon, 
+  PaperAirplaneIcon,
+  ArrowLeftIcon,
+  UserIcon,
+  RobotIcon
+} from '@heroicons/react/24/outline';
+
+// 类型定义
+type AIAgent = InferResponseType<typeof aiAgentClient.$get, 200>['data'][0];
+type ChatResponse = InferResponseType<typeof aiAgentClient.chat.$post, 200>;
+type ChatRequest = InferRequestType<typeof aiAgentClient.chat.$post>['json'];
+
+// 消息类型
+interface Message {
+  id: string;
+  role: 'user' | 'assistant';
+  content: string;
+  timestamp: Date;
+}
+
+// 中国水墨风格色彩方案
+const COLORS = {
+  ink: {
+    light: '#f5f3f0',    // 宣纸背景色
+    medium: '#d4c4a8',   // 淡墨
+    dark: '#8b7355',     // 浓墨
+    deep: '#3a2f26',     // 焦墨
+  },
+  accent: {
+    red: '#a85c5c',      // 朱砂
+    blue: '#4a6b7c',     // 花青
+    green: '#5c7c5c',    // 石绿
+  },
+  text: {
+    primary: '#2f1f0f',  // 墨色文字
+    secondary: '#5d4e3b', // 淡墨文字
+    light: '#8b7355',    // 极淡文字
+  }
+};
+
+// 中国水墨风格字体类
+const FONT_STYLES = {
+  title: 'font-serif text-3xl font-bold tracking-wide',
+  sectionTitle: 'font-serif text-2xl font-semibold tracking-wide',
+  body: 'font-sans text-base leading-relaxed',
+  caption: 'font-sans text-sm',
+  small: 'font-sans text-xs',
+};
+
+const AIAgentsPage: React.FC = () => {
+  const navigate = useNavigate();
+  const { user } = useAuth();
+  const [agents, setAgents] = useState<AIAgent[]>([]);
+  const [selectedAgent, setSelectedAgent] = useState<AIAgent | null>(null);
+  const [messages, setMessages] = useState<Message[]>([]);
+  const [inputMessage, setInputMessage] = useState('');
+  const [isLoading, setIsLoading] = useState(false);
+  const [isAgentsLoading, setIsAgentsLoading] = useState(true);
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+
+  // 获取智能体列表
+  useEffect(() => {
+    fetchAgents();
+  }, []);
+
+  // 自动滚动到底部
+  useEffect(() => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  }, [messages]);
+
+  const fetchAgents = async () => {
+    try {
+      setIsAgentsLoading(true);
+      const response = await aiAgentClient.$get();
+      const data = await response.json();
+      setAgents(data.data);
+      
+      // 默认选择第一个智能体
+      if (data.data.length > 0) {
+        setSelectedAgent(data.data[0]);
+      }
+    } catch (error) {
+      console.error('获取智能体失败:', error);
+    } finally {
+      setIsAgentsLoading(false);
+    }
+  };
+
+  const handleSendMessage = async () => {
+    if (!inputMessage.trim() || !selectedAgent) return;
+
+    const userMessage: Message = {
+      id: `msg-${Date.now()}`,
+      role: 'user',
+      content: inputMessage,
+      timestamp: new Date(),
+    };
+
+    setMessages(prev => [...prev, userMessage]);
+    setInputMessage('');
+    setIsLoading(true);
+
+    try {
+      const response = await aiAgentClient.chat.$post({
+        json: {
+          agentId: selectedAgent.id,
+          message: inputMessage,
+        } as ChatRequest,
+      });
+
+      const data: ChatResponse = await response.json();
+      
+      const aiMessage: Message = {
+        id: data.messageId,
+        role: 'assistant',
+        content: data.message,
+        timestamp: new Date(),
+      };
+
+      setMessages(prev => [...prev, aiMessage]);
+    } catch (error) {
+      console.error('发送消息失败:', error);
+      const errorMessage: Message = {
+        id: `error-${Date.now()}`,
+        role: 'assistant',
+        content: '抱歉,我暂时无法回答这个问题,请稍后再试。',
+        timestamp: new Date(),
+      };
+      setMessages(prev => [...prev, errorMessage]);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const handleKeyPress = (e: React.KeyboardEvent) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSendMessage();
+    }
+  };
+
+  if (isAgentsLoading) {
+    return (
+      <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
+        <header 
+          className="shadow-sm sticky top-0 z-10 border-b border-opacity-20"
+          style={{ 
+            backgroundColor: COLORS.ink.light,
+            borderColor: COLORS.ink.medium 
+          }}
+        >
+          <div className="px-4 py-4">
+            <div className="flex items-center">
+              <button
+                onClick={() => navigate('/')}
+                className="mr-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
+              >
+                <ArrowLeftIcon className="w-5 h-5" style={{ color: COLORS.text.primary }} />
+              </button>
+              <h1 className={FONT_STYLES.title} style={{ color: COLORS.text.primary }}>
+                智能助手
+              </h1>
+            </div>
+          </div>
+        </header>
+        
+        <div className="flex items-center justify-center h-64">
+          <div className="text-center">
+            <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mb-4"></div>
+            <p style={{ color: COLORS.text.secondary }}>加载中...</p>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  if (!selectedAgent) {
+    return (
+      <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
+        <header 
+          className="shadow-sm sticky top-0 z-10 border-b border-opacity-20"
+          style={{ 
+            backgroundColor: COLORS.ink.light,
+            borderColor: COLORS.ink.medium 
+          }}
+        >
+          <div className="px-4 py-4">
+            <div className="flex items-center">
+              <button
+                onClick={() => navigate('/')}
+                className="mr-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
+              >
+                <ArrowLeftIcon className="w-5 h-5" style={{ color: COLORS.text.primary }} />
+              </button>
+              <h1 className={FONT_STYLES.title} style={{ color: COLORS.text.primary }}>
+                智能助手
+              </h1>
+            </div>
+          </div>
+        </header>
+        
+        <div className="flex items-center justify-center h-64">
+          <div className="text-center">
+            <RobotIcon className="w-16 h-16 mx-auto mb-4" style={{ color: COLORS.ink.dark }} />
+            <p style={{ color: COLORS.text.secondary }}>暂无可用智能体</p>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen flex flex-col" style={{ backgroundColor: COLORS.ink.light }}>
+      {/* 顶部导航 */}
+      <header 
+        className="shadow-sm sticky top-0 z-10 border-b border-opacity-20"
+        style={{ 
+          backgroundColor: COLORS.ink.light,
+          borderColor: COLORS.ink.medium 
+        }}
+      >
+        <div className="px-4 py-4">
+          <div className="flex items-center">
+            <button
+              onClick={() => navigate('/')}
+              className="mr-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
+            >
+              <ArrowLeftIcon className="w-5 h-5" style={{ color: COLORS.text.primary }} />
+            </button>
+            <div>
+              <h1 className={FONT_STYLES.title} style={{ color: COLORS.text.primary }}>
+                {selectedAgent.name}
+              </h1>
+              <p className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
+                {selectedAgent.description || '智能助手'}
+              </p>
+            </div>
+          </div>
+        </div>
+      </header>
+
+      {/* 智能体选择器 */}
+      {agents.length > 1 && (
+        <div className="px-4 py-3 border-b" style={{ borderColor: COLORS.ink.medium }}>
+          <div className="flex space-x-2 overflow-x-auto">
+            {agents.map(agent => (
+              <button
+                key={agent.id}
+                onClick={() => {
+                  setSelectedAgent(agent);
+                  setMessages([]);
+                }}
+                className={`px-4 py-2 rounded-full text-sm whitespace-nowrap transition-all ${
+                  selectedAgent.id === agent.id
+                    ? 'text-white shadow-md'
+                    : 'border hover:shadow-sm'
+                }`}
+                style={{
+                  backgroundColor: selectedAgent.id === agent.id ? COLORS.accent.blue : 'transparent',
+                  borderColor: selectedAgent.id === agent.id ? COLORS.accent.blue : COLORS.ink.medium,
+                  color: selectedAgent.id === agent.id ? 'white' : COLORS.text.primary,
+                }}
+              >
+                {agent.name}
+              </button>
+            ))}
+          </div>
+        </div>
+      )}
+
+      {/* 消息区域 */}
+      <div className="flex-1 overflow-y-auto px-4 py-4">
+        {messages.length === 0 && (
+          <div className="text-center py-8">
+            <SparklesIcon className="w-16 h-16 mx-auto mb-4" style={{ color: COLORS.ink.dark }} />
+            <h3 className={`${FONT_STYLES.sectionTitle} mb-2`} style={{ color: COLORS.text.primary }}>
+              你好,{user?.username || '朋友'}
+            </h3>
+            <p style={{ color: COLORS.text.secondary }}>
+              {selectedAgent.description || '有什么我可以帮助您的吗?'}
+            </p>
+          </div>
+        )}
+
+        <div className="space-y-4">
+          {messages.map(message => (
+            <div
+              key={message.id}
+              className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
+            >
+              <div className={`max-w-[80%] ${message.role === 'user' ? 'order-1' : 'order-2'}`}>
+                <div
+                  className="rounded-2xl px-4 py-3 shadow-sm"
+                  style={{
+                    backgroundColor: message.role === 'user' ? COLORS.accent.blue : 'white',
+                    color: message.role === 'user' ? 'white' : COLORS.text.primary,
+                    border: `1px solid ${message.role === 'user' ? COLORS.accent.blue : COLORS.ink.medium}`,
+                  }}
+                >
+                  <p className={FONT_STYLES.body}>{message.content}</p>
+                </div>
+                <div className="flex items-center mt-1 text-xs" style={{ color: COLORS.text.light }}>
+                  {message.role === 'user' ? (
+                    <UserIcon className="w-3 h-3 mr-1" />
+                  ) : (
+                    <RobotIcon className="w-3 h-3 mr-1" />
+                  )}
+                  <span>{message.timestamp.toLocaleTimeString()}</span>
+                </div>
+              </div>
+            </div>
+          ))}
+          
+          {isLoading && (
+            <div className="flex justify-start">
+              <div
+                className="rounded-2xl px-4 py-3 shadow-sm"
+                style={{
+                  backgroundColor: 'white',
+                  border: `1px solid ${COLORS.ink.medium}`,
+                }}
+              >
+                <div className="flex space-x-2">
+                  <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
+                  <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
+                  <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
+                </div>
+              </div>
+            </div>
+          )}
+          
+          <div ref={messagesEndRef} />
+        </div>
+      </div>
+
+      {/* 输入区域 */}
+      <div className="px-4 py-3 border-t" style={{ borderColor: COLORS.ink.medium }}>
+        <div className="flex items-center space-x-3">
+          <div className="flex-1">
+            <textarea
+              value={inputMessage}
+              onChange={(e) => setInputMessage(e.target.value)}
+              onKeyPress={handleKeyPress}
+              placeholder="输入您的问题..."
+              className="w-full px-4 py-3 rounded-2xl border resize-none"
+              style={{
+                backgroundColor: 'white',
+                borderColor: COLORS.ink.medium,
+                color: COLORS.text.primary,
+                fontSize: '16px',
+                minHeight: '48px',
+                maxHeight: '120px',
+              }}
+              rows={1}
+            />
+          </div>
+          <button
+            onClick={handleSendMessage}
+            disabled={!inputMessage.trim() || isLoading}
+            className="p-3 rounded-full transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
+            style={{
+              backgroundColor: COLORS.accent.blue,
+              color: 'white',
+            }}
+          >
+            <PaperAirplaneIcon className="w-5 h-5" />
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default AIAgentsPage;

+ 27 - 1
src/client/mobile/pages/NewHomePage.tsx

@@ -23,7 +23,8 @@ import {
   MapPinIcon,
   EyeIcon,
   CalendarDaysIcon,
-  TrophyIcon
+  TrophyIcon,
+  SparklesIcon
 } from '@heroicons/react/24/outline';
 
 // 工具函数:格式化薪资显示
@@ -394,6 +395,31 @@ const NewHomePage: React.FC = () => {
           <CategoryScroll categories={categories} onNavigate={navigate} />
         </div>
 
+        {/* 智能体入口 */}
+        <div className="mt-6 px-4">
+          <div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-6 border border-blue-200">
+            <div className="flex items-center justify-between">
+              <div>
+                <h3 className="font-serif text-lg font-bold text-blue-900 mb-1">
+                  智能助手
+                </h3>
+                <p className="text-blue-700 text-sm">
+                  AI智能助手,为您提供专业咨询
+                </p>
+              </div>
+              <div className="flex-shrink-0">
+                <SparklesIcon className="w-8 h-8 text-blue-600" />
+              </div>
+            </div>
+            <button
+              onClick={() => navigate('/ai-agents')}
+              className="mt-4 w-full bg-blue-600 text-white py-2 px-4 rounded-full text-sm font-medium hover:bg-blue-700 transition-colors"
+            >
+              立即体验
+            </button>
+          </div>
+        </div>
+
         {/* 推荐岗位 */}
         {recommendedJobs.length > 0 && (
           <div className="mt-6 px-4">

+ 145 - 172
src/client/mobile/routes.tsx

@@ -1,174 +1,147 @@
-import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router-dom';
-import { ProtectedRoute } from './components/ProtectedRoute';
-import { ErrorPage } from './components/ErrorPage';
-import { NotFoundPage } from './components/NotFoundPage';
-import { ErrorBoundary } from './components/ErrorBoundary';
-import { MobileLayout } from './layouts/MobileLayout';
-import { silverPlatformTabs } from './components/BottomTabBar';
+import { lazy, Suspense } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import ProtectedRoute from './components/ProtectedRoute';
+import ErrorBoundary from './components/ErrorBoundary';
+import { SkeletonLoader } from './components/SkeletonLoader';
 
-// 页面组件导入
-import HomePage from './pages/HomePage';
-import SilverJobsPage from './pages/SilverJobsPage';
-import JobDetailPage from './pages/JobDetailPage';
-import SilverTalentsPage from './pages/SilverTalentsPage';
-import TalentDetailPage from './pages/TalentDetailPage';
-import SilverWisdomPage from './pages/SilverWisdomPage';
-import SilverWisdomDetailPage from './pages/SilverWisdomDetailPage';
-import ElderlyUniversityPage from './pages/ElderlyUniversityPage';
-import TimeBankPage from './pages/TimeBankPage';
-import PolicyNewsPage from './pages/PolicyNewsPage';
-import PolicyNewsDetailPage from './pages/PolicyNewsDetailPage';
-import PublishPage from './pages/PublishPage';
-import ProfilePage from './pages/ProfilePage';
-import ProfileEditPage from './pages/ProfileEditPage';
-import CompanyProfilePage from './pages/CompanyProfilePage';
-import PointsPage from './pages/PointsPage';
-import MyPostsPage from './pages/MyPostsPage';
-import MyFavoritesPage from './pages/MyFavoritesPage';
-import SkillsPage from './pages/SkillsPage';
-import FontSettingsPage from './pages/FontSettingsPage';
-import LoginPage from './pages/LoginPage';
-import RegisterPage from './pages/RegisterPage';
+// 页面组件懒加载
+const HomePage = lazy(() => import('./pages/HomePage'));
+const LoginPage = lazy(() => import('./pages/LoginPage'));
+const RegisterPage = lazy(() => import('./pages/RegisterPage'));
+const DashboardPage = lazy(() => import('./pages/DashboardPage'));
+const SilverJobsPage = lazy(() => import('./pages/SilverJobsPage'));
+const JobDetailPage = lazy(() => import('./pages/JobDetailPage'));
+const SilverTalentsPage = lazy(() => import('./pages/SilverTalentsPage'));
+const TalentDetailPage = lazy(() => import('./pages/TalentDetailPage'));
+const SilverWisdomPage = lazy(() => import('./pages/SilverWisdomPage'));
+const SilverWisdomDetailPage = lazy(() => import('./pages/SilverWisdomDetailPage'));
+const ProfilePage = lazy(() => import('./pages/ProfilePage'));
+const ProfileEditPage = lazy(() => import('./pages/ProfileEditPage'));
+const PointsPage = lazy(() => import('./pages/PointsPage'));
+const MyFavoritesPage = lazy(() => import('./pages/MyFavoritesPage'));
+const MyPostsPage = lazy(() => import('./pages/MyPostsPage'));
+const PublishPage = lazy(() => import('./pages/PublishPage'));
+const PolicyNewsPage = lazy(() => import('./pages/PolicyNewsPage'));
+const PolicyNewsDetailPage = lazy(() => import('./pages/PolicyNewsDetailPage'));
+const CompanyProfilePage = lazy(() => import('./pages/CompanyProfilePage'));
+const TimeBankPage = lazy(() => import('./pages/TimeBankPage'));
+const ElderlyCarePage = lazy(() => import('./pages/ElderlyCarePage'));
+const ElderlyUniversityPage = lazy(() => import('./pages/ElderlyUniversityPage'));
+const FontSettingsPage = lazy(() => import('./pages/FontSettingsPage'));
+const AIAgentsPage = lazy(() => import('./pages/AIAgentsPage'));
 
-export const router = createBrowserRouter([
-  {
-    path: '/',
-    element: <MobileLayout tabs={silverPlatformTabs} />,
-    children: [
-      {
-        index: true,
-        element: <Navigate to="/home" replace />
-      },
-      {
-        path: 'home',
-        element: <HomePage />
-      },
-      {
-        path: 'silver-jobs',
-        element: <SilverJobsPage />
-      },
-      {
-        path: 'silver-jobs/:id',
-        element: <JobDetailPage />
-      },
-      {
-        path: 'silver-talents',
-        element: <SilverTalentsPage />
-      },
-      {
-        path: 'silver-talents/:id',
-        element: <TalentDetailPage />
-      },
-      {
-        path: 'silver-wisdom',
-        element: <SilverWisdomPage />
-      },
-      {
-        path: 'silver-wisdom/:id',
-        element: <SilverWisdomDetailPage />
-      },
-      {
-        path: 'elderly-university',
-        element: <ElderlyUniversityPage />
-      },
-      {
-        path: 'time-bank',
-        element: <TimeBankPage />
-      },
-      {
-        path: 'policy-news',
-        element: <PolicyNewsPage />
-      },
-      {
-        path: 'policy-news/:id',
-        element: <PolicyNewsDetailPage />
-      },
-      {
-        path: 'publish',
-        element: (
-          <ProtectedRoute>
-            <PublishPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile',
-        element: (
-          <ProtectedRoute>
-            <ProfilePage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/edit',
-        element: (
-          <ProtectedRoute>
-            <ProfileEditPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/company',
-        element: (
-          <ProtectedRoute>
-            <CompanyProfilePage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/points',
-        element: (
-          <ProtectedRoute>
-            <PointsPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/posts',
-        element: (
-          <ProtectedRoute>
-            <MyPostsPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/favorites',
-        element: (
-          <ProtectedRoute>
-            <MyFavoritesPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/skills',
-        element: (
-          <ProtectedRoute>
-            <SkillsPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/font-settings',
-        element: (
-          <ProtectedRoute>
-            <FontSettingsPage />
-          </ProtectedRoute>
-        )
-      }
-    ]
-  },
-  {
-    path: '/login',
-    element: <LoginPage />
-  },
-  {
-    path: '/register',
-    element: <RegisterPage />
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  }
-]);
+// 加载骨架屏
+const LoadingSkeleton = () => (
+  <div className="min-h-screen flex items-center justify-center">
+    <SkeletonLoader />
+  </div>
+);
+
+const MobileRoutes = () => {
+  return (
+    <ErrorBoundary>
+      <Suspense fallback={<LoadingSkeleton />}>
+        <Routes>
+          {/* 公开路由 */}
+          <Route path="/" element={<HomePage />} />
+          <Route path="/login" element={<LoginPage />} />
+          <Route path="/register" element={<RegisterPage />} />
+          
+          {/* 银龄岗位相关路由 */}
+          <Route path="/silver-jobs" element={<SilverJobsPage />} />
+          <Route path="/silver-jobs/:id" element={<JobDetailPage />} />
+          
+          {/* 银龄人才相关路由 */}
+          <Route path="/silver-talents" element={<SilverTalentsPage />} />
+          <Route path="/silver-talents/:id" element={<TalentDetailPage />} />
+          
+          {/* 银龄智库相关路由 */}
+          <Route path="/silver-wisdom" element={<SilverWisdomPage />} />
+          <Route path="/silver-wisdom/:id" element={<SilverWisdomDetailPage />} />
+          
+          {/* 政策资讯相关路由 */}
+          <Route path="/policy-news" element={<PolicyNewsPage />} />
+          <Route path="/policy-news/:id" element={<PolicyNewsDetailPage />} />
+          
+          {/* 养老机构相关路由 */}
+          <Route path="/elderly-care" element={<ElderlyCarePage />} />
+          <Route path="/elderly-universities" element={<ElderlyUniversityPage />} />
+          
+          {/* 时间银行相关路由 */}
+          <Route path="/time-bank" element={<TimeBankPage />} />
+          
+          {/* 智能体相关路由 */}
+          <Route path="/ai-agents" element={<AIAgentsPage />} />
+          
+          {/* 需要认证的路由 */}
+          <Route path="/dashboard" element={
+            <ProtectedRoute>
+              <DashboardPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/profile" element={
+            <ProtectedRoute>
+              <ProfilePage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/profile/edit" element={
+            <ProtectedRoute>
+              <ProfileEditPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/points" element={
+            <ProtectedRoute>
+              <PointsPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/favorites" element={
+            <ProtectedRoute>
+              <MyFavoritesPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/my-posts" element={
+            <ProtectedRoute>
+              <MyPostsPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/publish" element={
+            <ProtectedRoute>
+              <PublishPage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/company-profile" element={
+            <ProtectedRoute>
+              <CompanyProfilePage />
+            </ProtectedRoute>
+          } />
+          
+          <Route path="/font-settings" element={<FontSettingsPage />} />
+          
+          {/* 404页面 */}
+          <Route path="*" element={
+            <div className="min-h-screen flex items-center justify-center">
+              <div className="text-center">
+                <h1 className="text-2xl font-bold mb-4">页面不存在</h1>
+                <button 
+                  onClick={() => window.history.back()}
+                  className="text-blue-600 underline"
+                >
+                  返回上一页
+                </button>
+              </div>
+            </div>
+          } />
+        </Routes>
+      </Suspense>
+    </ErrorBoundary>
+  );
+};
+
+export default MobileRoutes;

+ 5 - 0
src/server/api.ts

@@ -143,6 +143,10 @@ const homeIconApiRoutes = api.route('/api/v1/home-icons', homeIconRoutes)
 import silverKnowledgeRoutes from './api/silver-knowledges/index'
 const silverKnowledgeApiRoutes = api.route('/api/v1/silver-knowledges', silverKnowledgeRoutes)
 
+// 注册AI智能体路由
+import aiAgentRoutes from './api/ai-agents/index'
+const aiAgentApiRoutes = api.route('/api/v1/ai-agents', aiAgentRoutes)
+
 export type AuthRoutes = typeof authRoutes
 export type CompanyCertificationRoutes = typeof companyCertificationApiRoutes
 export type UserRoutes = typeof userRoutes
@@ -172,6 +176,7 @@ export type SilverJobRoutes = typeof silverJobApiRoutes
 export type SilverCompaniesRoutes = typeof silverCompaniesApiRoutes
 export type HomeIconRoutes = typeof homeIconApiRoutes
 export type SilverKnowledgeRoutes = typeof silverKnowledgeApiRoutes
+export type AIAgentRoutes = typeof aiAgentApiRoutes
 
 
 app.route('/', api)

+ 53 - 0
src/server/api/ai-agents/[id]/get.ts

@@ -0,0 +1,53 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { AIAgentSchema } from '@/server/modules/ai-agents/ai-agent.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AIAgentService } from '@/server/modules/ai-agents/ai-agent.service';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const GetParams = z.object({
+  id: z.string().openapi({
+    param: { name: 'id', in: 'path' },
+    example: '1',
+    description: '智能体ID'
+  })
+});
+
+const routeDef = createRoute({
+  method: 'get',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: GetParams
+  },
+  responses: {
+    200: {
+      description: '成功获取智能体详情',
+      content: { 'application/json': { schema: AIAgentSchema } }
+    },
+    404: {
+      description: '智能体不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    const service = new AIAgentService(AppDataSource);
+    
+    const agent = await service.findById(parseInt(id));
+    return c.json(agent, 200);
+  } catch (error) {
+    const { code = 500, message = '获取详情失败' } = error as Error & { code?: number };
+    return c.json({ code, message }, code);
+  }
+});
+
+export default app;

+ 60 - 0
src/server/api/ai-agents/chat/post.ts

@@ -0,0 +1,60 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { AIChatRequestSchema, AIChatResponseSchema } from '@/server/modules/ai-agents/ai-agent.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AIAgentService } from '@/server/modules/ai-agents/ai-agent.service';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const routeDef = createRoute({
+  method: 'post',
+  path: '/chat',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: AIChatRequestSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: 'AI回复成功',
+      content: { 'application/json': { schema: AIChatResponseSchema } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '智能体不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const user = c.get('user');
+    const body = await c.req.json();
+    
+    const service = new AIAgentService(AppDataSource);
+    
+    const result = await service.chat(
+      body.agentId,
+      body.message,
+      user?.id
+    );
+    
+    return c.json(result, 200);
+  } catch (error) {
+    const { code = 500, message = 'AI对话失败' } = error as Error & { code?: number };
+    return c.json({ code, message }, code);
+  }
+});
+
+export default app;

+ 80 - 0
src/server/api/ai-agents/get.ts

@@ -0,0 +1,80 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { AIAgentSchema } from '@/server/modules/ai-agents/ai-agent.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AIAgentService } from '@/server/modules/ai-agents/ai-agent.service';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const ListResponse = z.object({
+  data: z.array(AIAgentSchema),
+  pagination: z.object({
+    total: z.number().openapi({ example: 100, description: '总记录数' }),
+    current: z.number().openapi({ example: 1, description: '当前页码' }),
+    pageSize: z.number().openapi({ example: 10, description: '每页数量' })
+  })
+});
+
+const routeDef = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: z.object({
+      page: z.coerce.number().int().positive().default(1).openapi({
+        description: '页码',
+        example: 1
+      }),
+      pageSize: z.coerce.number().int().positive().default(10).openapi({
+        description: '每页条数',
+        example: 10
+      }),
+      keyword: z.string().optional().openapi({
+        description: '搜索关键词',
+        example: '助手'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '成功获取智能体列表',
+      content: { 'application/json': { schema: ListResponse } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const query = c.req.valid('query');
+    const service = new AIAgentService(AppDataSource);
+    
+    const [data, total] = await service.repository.findAndCount({
+      where: { isDeleted: 0 },
+      order: { createdAt: 'DESC' },
+      skip: (query.page - 1) * query.pageSize,
+      take: query.pageSize
+    });
+
+    return c.json({
+      data,
+      pagination: {
+        total,
+        current: query.page,
+        pageSize: query.pageSize
+      }
+    }, 200);
+  } catch (error) {
+    const { code = 500, message = '获取列表失败' } = error as Error & { code?: number };
+    return c.json({ code, message }, code);
+  }
+});
+
+export default app;

+ 11 - 0
src/server/api/ai-agents/index.ts

@@ -0,0 +1,11 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import listRoute from './get';
+import getByIdRoute from './[id]/get';
+import chatRoute from './chat/post';
+
+const app = new OpenAPIHono()
+  .route('/', listRoute)
+  .route('/', getByIdRoute)
+  .route('/', chatRoute);
+
+export default app;

+ 2 - 0
src/server/data-source.ts

@@ -30,6 +30,7 @@ import { PolicyNews } from "./modules/silver-users/policy-news.entity"
 import { UserPreference } from "./modules/silver-users/user-preference.entity"
 import { SilverJob } from "./modules/silver-jobs/silver-job.entity"
 import { HomeIcon } from "./modules/home/home-icon.entity"
+import { AIAgent } from "./modules/ai-agents/ai-agent.entity"
 
 export const AppDataSource = new DataSource({
   type: "mysql",
@@ -45,6 +46,7 @@ export const AppDataSource = new DataSource({
     SilverKnowledge, SilverKnowledgeCategory, SilverKnowledgeTag,
     SilverKnowledgeTagRelation, SilverKnowledgeStats, SilverKnowledgeInteraction,
     ElderlyUniversity, PolicyNews, UserPreference, SilverJob, HomeIcon,
+    AIAgent,
   ],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 26 - 0
src/server/migrations/create-ai-agents.sql

@@ -0,0 +1,26 @@
+-- 创建ai_agents表
+CREATE TABLE `ai_agents` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) NOT NULL COMMENT '智能体名称',
+  `description` text COMMENT '智能体描述',
+  `system_prompt` text COMMENT '系统提示词',
+  `model` varchar(50) NOT NULL DEFAULT 'gpt-3.5-turbo' COMMENT 'AI模型',
+  `temperature` decimal(3,2) NOT NULL DEFAULT '0.70' COMMENT '温度参数',
+  `max_tokens` int NOT NULL DEFAULT '1000' COMMENT '最大令牌数',
+  `is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用:0-禁用,1-启用',
+  `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除,1-已删除',
+  `created_by` int DEFAULT NULL COMMENT '创建用户ID',
+  `updated_by` int DEFAULT NULL COMMENT '更新用户ID',
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_name` (`name`),
+  KEY `idx_active` (`is_active`),
+  KEY `idx_deleted` (`is_deleted`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI智能体配置表';
+
+-- 插入默认智能体
+INSERT INTO `ai_agents` (`name`, `description`, `system_prompt`, `model`, `temperature`, `max_tokens`) VALUES
+('银龄助手', '专为银龄人群设计的智能助手,提供生活咨询、健康养生、政策解读等服务', '你是一个友善的银龄助手,专门为中老年人群提供帮助。请用简单易懂的语言回答问题,耐心细致,体现对长者的尊重。可以谈论健康养生、退休生活、政策咨询、生活技巧等话题。', 'gpt-3.5-turbo', 0.7, 1000),
+('健康顾问', '专业的健康管理顾问,提供健康咨询、养生建议、疾病预防等服务', '你是一个专业的健康管理顾问,专门为银龄人群提供健康咨询。请提供科学、实用的健康养生建议,包括饮食、运动、心理健康等方面。回答要专业但易懂,避免过于复杂的医学术语。', 'gpt-3.5-turbo', 0.6, 1500),
+('生活管家', '贴心的生活管家,提供日常事务处理、生活技巧、便民信息等实用服务', '你是一个贴心的生活管家,帮助银龄人群解决日常生活中的各种问题。请提供实用的生活技巧、便民信息、事务处理建议等。回答要具体、可操作,体现对长者生活的关心。', 'gpt-3.5-turbo', 0.8, 800);

+ 101 - 0
src/server/modules/ai-agents/ai-agent.entity.ts

@@ -0,0 +1,101 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('ai_agents')
+export class AIAgent {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'name', type: 'varchar', length: 100 })
+  name!: string;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description!: string | null;
+
+  @Column({ name: 'system_prompt', type: 'text', nullable: true })
+  systemPrompt!: string | null;
+
+  @Column({ name: 'model', type: 'varchar', length: 50, default: 'gpt-3.5-turbo' })
+  model!: string;
+
+  @Column({ name: 'temperature', type: 'decimal', precision: 3, scale: 2, default: 0.7 })
+  temperature!: number;
+
+  @Column({ name: 'max_tokens', type: 'int', default: 1000 })
+  maxTokens!: number;
+
+  @Column({ name: 'is_active', type: 'tinyint', default: 1 })
+  isActive!: number;
+
+  @Column({ name: 'is_deleted', type: 'tinyint', default: 0 })
+  isDeleted!: number;
+
+  @Column({ name: 'created_by', type: 'int', nullable: true })
+  createdBy!: number | null;
+
+  @Column({ name: 'updated_by', type: 'int', nullable: true })
+  updatedBy!: number | null;
+
+  @CreateDateColumn({ name: 'created_at' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at' })
+  updatedAt!: Date;
+}
+
+// Zod Schema 定义
+export const AIAgentSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '智能体ID' }),
+  name: z.string().max(100).openapi({ description: '智能体名称', example: '银龄助手' }),
+  description: z.string().max(500).nullable().openapi({ description: '智能体描述', example: '专为银龄人群设计的智能助手' }),
+  systemPrompt: z.string().max(2000).nullable().openapi({ description: '系统提示词', example: '你是一个友好的银龄助手...' }),
+  model: z.string().max(50).openapi({ description: 'AI模型', example: 'gpt-3.5-turbo' }),
+  temperature: z.number().min(0).max(2).openapi({ description: '温度参数', example: 0.7 }),
+  maxTokens: z.number().int().positive().openapi({ description: '最大令牌数', example: 1000 }),
+  isActive: z.number().int().min(0).max(1).openapi({ description: '是否启用', example: 1 }),
+  createdBy: z.number().int().positive().nullable().openapi({ description: '创建用户ID' }),
+  updatedBy: z.number().int().positive().nullable().openapi({ description: '更新用户ID' }),
+  createdAt: z.date().openapi({ description: '创建时间' }),
+  updatedAt: z.date().openapi({ description: '更新时间' })
+});
+
+export const CreateAIAgentDto = z.object({
+  name: z.string().min(1).max(100).openapi({ description: '智能体名称', example: '银龄助手' }),
+  description: z.string().max(500).nullable().optional().openapi({ description: '智能体描述', example: '专为银龄人群设计的智能助手' }),
+  systemPrompt: z.string().max(2000).nullable().optional().openapi({ description: '系统提示词', example: '你是一个友好的银龄助手...' }),
+  model: z.string().max(50).default('gpt-3.5-turbo').openapi({ description: 'AI模型', example: 'gpt-3.5-turbo' }),
+  temperature: z.number().min(0).max(2).default(0.7).openapi({ description: '温度参数', example: 0.7 }),
+  maxTokens: z.number().int().positive().default(1000).openapi({ description: '最大令牌数', example: 1000 })
+});
+
+export const UpdateAIAgentDto = z.object({
+  name: z.string().min(1).max(100).optional().openapi({ description: '智能体名称', example: '银龄助手' }),
+  description: z.string().max(500).nullable().optional().openapi({ description: '智能体描述', example: '专为银龄人群设计的智能助手' }),
+  systemPrompt: z.string().max(2000).nullable().optional().openapi({ description: '系统提示词', example: '你是一个友好的银龄助手...' }),
+  model: z.string().max(50).optional().openapi({ description: 'AI模型', example: 'gpt-3.5-turbo' }),
+  temperature: z.number().min(0).max(2).optional().openapi({ description: '温度参数', example: 0.7 }),
+  maxTokens: z.number().int().positive().optional().openapi({ description: '最大令牌数', example: 1000 }),
+  isActive: z.number().int().min(0).max(1).optional().openapi({ description: '是否启用', example: 1 })
+});
+
+// 对话消息类型
+export const ConversationMessageSchema = z.object({
+  id: z.string().openapi({ description: '消息ID' }),
+  role: z.enum(['user', 'assistant', 'system']).openapi({ description: '消息角色' }),
+  content: z.string().openapi({ description: '消息内容' }),
+  timestamp: z.date().openapi({ description: '时间戳' })
+});
+
+// AI请求类型
+export const AIChatRequestSchema = z.object({
+  agentId: z.number().int().positive().openapi({ description: '智能体ID', example: 1 }),
+  message: z.string().min(1).max(4000).openapi({ description: '用户消息', example: '你好,请介绍一下银龄智慧平台' }),
+  conversationId: z.string().optional().openapi({ description: '对话ID', example: 'conv-123' })
+});
+
+// AI响应类型
+export const AIChatResponseSchema = z.object({
+  message: z.string().openapi({ description: 'AI回复消息' }),
+  conversationId: z.string().openapi({ description: '对话ID' }),
+  messageId: z.string().openapi({ description: '消息ID' })
+});

+ 164 - 0
src/server/modules/ai-agents/ai-agent.service.ts

@@ -0,0 +1,164 @@
+import { DataSource, Repository } from 'typeorm';
+import { AIAgent } from './ai-agent.entity';
+import { AppError } from '@/server/utils/errorHandler';
+import OpenAI from 'openai';
+
+export class AIAgentService {
+  private repository: Repository<AIAgent>;
+  private openai: OpenAI;
+
+  constructor(dataSource: DataSource) {
+    this.repository = dataSource.getRepository(AIAgent);
+    
+    // 初始化OpenAI客户端
+    this.openai = new OpenAI({
+      apiKey: process.env.MY_CUSTOM_AI_API_KEY,
+      baseURL: process.env.MY_CUSTOM_AI_BASE_URL,
+    });
+  }
+
+  /**
+   * 获取所有智能体
+   */
+  async findAll(): Promise<AIAgent[]> {
+    return this.repository.find({
+      where: { isDeleted: 0, isActive: 1 },
+      order: { createdAt: 'DESC' }
+    });
+  }
+
+  /**
+   * 根据ID获取智能体
+   */
+  async findById(id: number): Promise<AIAgent> {
+    const agent = await this.repository.findOne({
+      where: { id, isDeleted: 0 }
+    });
+    
+    if (!agent) {
+      throw new AppError('智能体不存在', 404);
+    }
+    
+    return agent;
+  }
+
+  /**
+   * 创建智能体
+   */
+  async create(data: any): Promise<AIAgent> {
+    const existing = await this.repository.findOne({
+      where: { name: data.name, isDeleted: 0 }
+    });
+    
+    if (existing) {
+      throw new AppError('智能体名称已存在', 400);
+    }
+    
+    const agent = this.repository.create(data);
+    return this.repository.save(agent);
+  }
+
+  /**
+   * 更新智能体
+   */
+  async update(id: number, data: any): Promise<AIAgent> {
+    const agent = await this.findById(id);
+    
+    if (data.name && data.name !== agent.name) {
+      const existing = await this.repository.findOne({
+        where: { name: data.name, isDeleted: 0 }
+      });
+      
+      if (existing) {
+        throw new AppError('智能体名称已存在', 400);
+      }
+    }
+    
+    Object.assign(agent, data);
+    return this.repository.save(agent);
+  }
+
+  /**
+   * 删除智能体
+   */
+  async delete(id: number): Promise<boolean> {
+    const agent = await this.findById(id);
+    agent.isDeleted = 1;
+    await this.repository.save(agent);
+    return true;
+  }
+
+  /**
+   * 与AI智能体对话
+   */
+  async chat(agentId: number, message: string, userId?: number): Promise<{
+    message: string;
+    conversationId: string;
+    messageId: string;
+  }> {
+    const agent = await this.findById(agentId);
+    
+    if (!agent.isActive) {
+      throw new AppError('智能体未启用', 400);
+    }
+
+    try {
+      const messages: any[] = [];
+      
+      // 添加系统提示词
+      if (agent.systemPrompt) {
+        messages.push({
+          role: 'system',
+          content: agent.systemPrompt
+        });
+      }
+
+      // 添加用户消息
+      messages.push({
+        role: 'user',
+        content: message
+      });
+
+      // 调用OpenAI API
+      const completion = await this.openai.chat.completions.create({
+        model: agent.model,
+        messages,
+        temperature: agent.temperature,
+        max_tokens: agent.maxTokens,
+      });
+
+      const aiMessage = completion.choices[0]?.message?.content || '抱歉,我无法回答这个问题';
+      
+      // 生成对话ID和消息ID
+      const conversationId = `conv-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+      const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+
+      return {
+        message: aiMessage,
+        conversationId,
+        messageId
+      };
+    } catch (error) {
+      console.error('AI对话错误:', error);
+      throw new AppError('AI服务暂时不可用,请稍后重试', 500);
+    }
+  }
+
+  /**
+   * 测试AI连接
+   */
+  async testConnection(): Promise<boolean> {
+    try {
+      const response = await this.openai.chat.completions.create({
+        model: 'gpt-3.5-turbo',
+        messages: [{ role: 'user', content: 'Hello' }],
+        max_tokens: 5,
+      });
+      
+      return !!response.choices[0];
+    } catch (error) {
+      console.error('AI连接测试失败:', error);
+      return false;
+    }
+  }
+}