update pay function
This commit is contained in:
24
src/views/AgentChat/neuratalk/components/Image/MCPImage.tsx
Normal file
24
src/views/AgentChat/neuratalk/components/Image/MCPImage.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { default as NextImage, ImageProps } from "next/image";
|
||||||
|
|
||||||
|
const MCPImage = ({ className, src, ...props }: ImageProps) => {
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
|
// 处理图片路径 - 如果是相对路径,添加 basePath
|
||||||
|
const processedSrc = typeof src === 'string' && src.startsWith('/') && !src.startsWith('/ai-chat')
|
||||||
|
? `/ai-chat${src}`
|
||||||
|
: src;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NextImage
|
||||||
|
className={`inline-block align-top opacity-0 transition-opacity ${
|
||||||
|
loaded && "opacity-100"
|
||||||
|
} ${className || ""}`}
|
||||||
|
onLoad={() => setLoaded(true)}
|
||||||
|
src={processedSrc}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MCPImage;
|
||||||
174
src/views/AgentChat/neuratalk/components/Layout/MCPLayout.tsx
Normal file
174
src/views/AgentChat/neuratalk/components/Layout/MCPLayout.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
import Tools from "@/components/Tools";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import PythonRunner from "@/components/PythonRunner";
|
||||||
|
import Calculator from "@/components/Calculator";
|
||||||
|
import Browser from "@/components/Browser";
|
||||||
|
import WebDesign from "@/components/WebDesign";
|
||||||
|
import FileConverter from "@/components/FileConverter";
|
||||||
|
import LanguageTranslator from "@/components/LanguageTranslator";
|
||||||
|
import ApiIntegrator from "@/components/ApiIntegrator";
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import MCPImage from "@/components/Image/MCPImage";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MCPLayout = ({ children }: Props) => {
|
||||||
|
const [activeId, setActiveId] = useState<string | null>(null);
|
||||||
|
const [visibleTools, setVisibleTools] = useState(true);
|
||||||
|
const [visibleSidebar, setVisibleSidebar] = useState(false);
|
||||||
|
const { user, isAuthenticated, canAccessChat, loading: authLoading } = useAuth();
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
if (authLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-screen bg-gray-50">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
||||||
|
<p className="text-lg text-gray-600">加载中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'https://valuefrontier.cn';
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-screen bg-gray-50">
|
||||||
|
<div className="text-center p-8 bg-white rounded-lg shadow-lg">
|
||||||
|
<h2 className="text-2xl mb-4">请先登录</h2>
|
||||||
|
<p className="text-gray-600 mb-6">您需要登录才能使用 AI 助手</p>
|
||||||
|
<a
|
||||||
|
href={`${mainAppUrl}/auth/sign-in?redirect=/ai-chat`}
|
||||||
|
className="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
前往登录
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canAccessChat) {
|
||||||
|
const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'https://valuefrontier.cn';
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-screen bg-gray-50">
|
||||||
|
<div className="text-center p-8 bg-white rounded-lg shadow-lg">
|
||||||
|
<h2 className="text-2xl mb-4">需要订阅才能使用 AI 助手</h2>
|
||||||
|
<p className="text-gray-600 mb-2">升级到高级版解锁所有功能</p>
|
||||||
|
<p className="text-sm text-gray-500 mb-6">
|
||||||
|
当前订阅等级:{user?.subscription_type || '未订阅'}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<a
|
||||||
|
href={`${mainAppUrl}/subscription`}
|
||||||
|
className="block bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors"
|
||||||
|
>
|
||||||
|
查看订阅方案
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="block w-full bg-gray-200 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
刷新页面
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`pl-90 overflow-hidden transition-all max-3xl:pl-75 max-lg:pl-5 max-md:pl-4 ${
|
||||||
|
visibleTools
|
||||||
|
? "pr-90 max-3xl:pr-75 max-2xl:pr-5 max-md:pr-4"
|
||||||
|
: "pr-5 max-md:pr-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
visible={visibleSidebar}
|
||||||
|
onClose={() => setVisibleSidebar(false)}
|
||||||
|
onClickNewChat={() => setActiveId(null)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 用户信息显示 */}
|
||||||
|
{user && (
|
||||||
|
<div className="fixed bottom-4 left-4 z-50 bg-white rounded-lg shadow-lg p-3 flex items-center space-x-3 max-w-xs">
|
||||||
|
{user.avatar_url ? (
|
||||||
|
<img
|
||||||
|
src={user.avatar_url}
|
||||||
|
alt={user.username || user.nickname}
|
||||||
|
className="w-10 h-10 rounded-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-semibold">
|
||||||
|
{(user.username || user.nickname || 'U')[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="font-medium text-sm text-gray-900 truncate">
|
||||||
|
{user.username || user.nickname}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
{user.subscription_type === 'max' ? '🌟 MAX会员' :
|
||||||
|
user.subscription_type === 'premium' ? '💎 高级会员' :
|
||||||
|
user.subscription_type === 'pro' ? '🚀 专业版' : '免费版'}
|
||||||
|
</p>
|
||||||
|
{user.subscription_days_left && user.subscription_days_left > 0 && (
|
||||||
|
<p className="text-xs text-green-600">
|
||||||
|
剩余 {user.subscription_days_left} 天
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="pt-9.5 pb-5 max-2xl:pt-5 max-md:pt-3 max-md:pb-4">
|
||||||
|
<Header
|
||||||
|
onOpenSidebar={() => setVisibleSidebar(true)}
|
||||||
|
onToggleTools={() => setVisibleTools(!visibleTools)}
|
||||||
|
/>
|
||||||
|
{activeId === "python" ? (
|
||||||
|
<PythonRunner />
|
||||||
|
) : activeId === "calculator" ? (
|
||||||
|
<Calculator />
|
||||||
|
) : activeId === "browser" ? (
|
||||||
|
<Browser />
|
||||||
|
) : activeId === "web-design" ? (
|
||||||
|
<WebDesign />
|
||||||
|
) : activeId === "exchange" ? (
|
||||||
|
<FileConverter />
|
||||||
|
) : activeId === "language" ? (
|
||||||
|
<LanguageTranslator />
|
||||||
|
) : activeId === "api" ? (
|
||||||
|
<ApiIntegrator />
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Tools
|
||||||
|
activeId={activeId}
|
||||||
|
setActiveId={setActiveId}
|
||||||
|
visible={visibleTools}
|
||||||
|
onClose={() => setVisibleTools(!visibleTools)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`fixed inset-0 z-10 hidden bg-overlay backdrop-blur-sm transition-all max-lg:block max-md:hidden ${
|
||||||
|
visibleSidebar || !visibleTools
|
||||||
|
? "visible opacity-100"
|
||||||
|
: "invisible opacity-0"
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setVisibleSidebar(false);
|
||||||
|
setVisibleTools(true);
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MCPLayout;
|
||||||
@@ -2,7 +2,7 @@ import { useState, useEffect, KeyboardEvent } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import TextareaAutosize from "react-textarea-autosize";
|
import TextareaAutosize from "react-textarea-autosize";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
import Image from "@/components/Image";
|
import MCPImage from "@/components/Image/MCPImage";
|
||||||
import Note from "./Note";
|
import Note from "./Note";
|
||||||
import Speed from "./Speed";
|
import Speed from "./Speed";
|
||||||
import Search from "./Search";
|
import Search from "./Search";
|
||||||
@@ -109,7 +109,7 @@ const MCPPanelMessage = ({
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
<div className="w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<MCPImage
|
||||||
className="w-5 opacity-100"
|
className="w-5 opacity-100"
|
||||||
src="/images/sent.svg"
|
src="/images/sent.svg"
|
||||||
width={20}
|
width={20}
|
||||||
|
|||||||
@@ -35,7 +35,14 @@ const nextConfig: NextConfig = {
|
|||||||
|
|
||||||
// 配置允许的域名(用于 Next.js Image 组件)
|
// 配置允许的域名(用于 Next.js Image 组件)
|
||||||
images: {
|
images: {
|
||||||
domains: ['valuefrontier.cn', 'localhost'],
|
domains: ['valuefrontier.cn', 'localhost', 'thirdwx.qlogo.cn'],
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'thirdwx.qlogo.cn',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import Layout from "@/components/Layout";
|
import MCPLayout from "@/components/Layout/MCPLayout";
|
||||||
import MCPChat from "@/components/Chat/MCPChat";
|
import MCPChat from "@/components/Chat/MCPChat";
|
||||||
import Question from "@/components/Question";
|
import Question from "@/components/Question";
|
||||||
import Answer from "@/components/Answer";
|
import Answer from "@/components/Answer";
|
||||||
@@ -136,54 +136,14 @@ const MCPGenerateCodePage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 权限检查
|
// MCPLayout 已经处理了认证检查,这里只需要简单检查
|
||||||
if (authLoading) {
|
if (!isAuthenticated || !canAccessChat) {
|
||||||
return (
|
// MCPLayout 会自动显示登录或订阅提示
|
||||||
<Layout>
|
return <MCPLayout><div /></MCPLayout>;
|
||||||
<div className="flex items-center justify-center h-96">
|
|
||||||
<div className="text-lg">加载中...</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'https://valuefrontier.cn';
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<div className="flex flex-col items-center justify-center h-96">
|
|
||||||
<h2 className="text-2xl mb-4">请先登录</h2>
|
|
||||||
<a
|
|
||||||
href={`${mainAppUrl}/auth/sign-in?redirect=/ai-chat/generate-code`}
|
|
||||||
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
前往登录
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canAccessChat) {
|
|
||||||
const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'https://valuefrontier.cn';
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<div className="flex flex-col items-center justify-center h-96">
|
|
||||||
<h2 className="text-2xl mb-4">需要订阅才能使用 AI 助手</h2>
|
|
||||||
<p className="text-gray-600 mb-4">升级到高级版解锁所有功能</p>
|
|
||||||
<a
|
|
||||||
href={`${mainAppUrl}/subscription`}
|
|
||||||
className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700"
|
|
||||||
>
|
|
||||||
查看订阅方案
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<MCPLayout>
|
||||||
<MCPChat
|
<MCPChat
|
||||||
onInputChange={setInput}
|
onInputChange={setInput}
|
||||||
onSend={handleSend}
|
onSend={handleSend}
|
||||||
@@ -267,7 +227,7 @@ const MCPGenerateCodePage = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MCPChat>
|
</MCPChat>
|
||||||
</Layout>
|
</MCPLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,21 @@ server {
|
|||||||
proxy_read_timeout 86400s;
|
proxy_read_timeout 86400s;
|
||||||
}
|
}
|
||||||
# AI Chat 应用 (Next.js) - MCP 集成
|
# AI Chat 应用 (Next.js) - MCP 集成
|
||||||
|
# AI Chat 静态资源(图片、CSS、JS)
|
||||||
|
location ~ ^/ai-chat/(images|_next/static|_next/image|favicon.ico) {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# 缓存设置
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# AI Chat 主应用
|
||||||
location /ai-chat {
|
location /ai-chat {
|
||||||
proxy_pass http://127.0.0.1:3000/ai-chat;
|
proxy_pass http://127.0.0.1:3000/ai-chat;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|||||||
Reference in New Issue
Block a user