update pay function

This commit is contained in:
2025-11-22 09:57:30 +08:00
parent feb08dc746
commit cd926bb42d
4 changed files with 273 additions and 11 deletions

View File

@@ -0,0 +1,211 @@
// app/chat-direct/page.tsx - 直接访问版本(跳过认证)
'use client';
import React, { useState, useRef, useEffect } from 'react';
import { mcpService } from '../../services/mcp-real';
export default function DirectChatPage() {
const [messages, setMessages] = useState<any[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [tools, setTools] = useState<any[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
loadTools();
}, []);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const loadTools = async () => {
try {
const availableTools = await mcpService.getTools();
setTools(availableTools);
console.log('Available tools:', availableTools);
} catch (error) {
console.error('Failed to load tools:', error);
}
};
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMessage = {
id: Date.now().toString(),
role: 'user',
content: input,
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
const assistantMessage = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: '',
timestamp: new Date(),
isStreaming: true,
};
setMessages(prev => [...prev, assistantMessage]);
// 尝试流式响应
let fullContent = '';
try {
for await (const chunk of mcpService.streamMessage(userMessage.content)) {
fullContent += chunk;
setMessages(prev =>
prev.map(msg =>
msg.id === assistantMessage.id
? { ...msg, content: fullContent }
: msg
)
);
}
} catch (streamError) {
console.log('Stream failed, trying non-stream:', streamError);
// 如果流式失败,尝试普通请求
const response = await mcpService.sendMessage(userMessage.content);
fullContent = response;
}
// 更新最终消息
setMessages(prev =>
prev.map(msg =>
msg.id === assistantMessage.id
? { ...msg, content: fullContent || '没有收到回复', isStreaming: false }
: msg
)
);
} catch (error) {
console.error('Send message error:', error);
setMessages(prev => [
...prev,
{
id: Date.now().toString(),
role: 'system',
content: `错误: ${error.message}`,
timestamp: new Date(),
},
]);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex h-screen bg-gray-50">
{/* 左侧工具栏 */}
<div className="w-64 bg-white border-r p-4">
<h3 className="font-bold mb-4">MCP </h3>
<div className="space-y-2">
{tools.map(tool => (
<div
key={tool.name}
className="p-2 bg-gray-100 rounded hover:bg-gray-200 cursor-pointer"
onClick={() => {
setInput(`使用工具: ${tool.name}`);
}}
>
<div className="font-medium text-sm">{tool.name}</div>
<div className="text-xs text-gray-600">{tool.description}</div>
</div>
))}
</div>
<div className="mt-6 p-3 bg-blue-50 rounded">
<div className="text-sm font-medium">Max </div>
<div className="text-xs text-gray-600">访</div>
</div>
</div>
{/* 主聊天区域 */}
<div className="flex-1 flex flex-col">
<div className="bg-white border-b p-4">
<h1 className="text-xl font-bold">AI Chat (Direct Access)</h1>
<p className="text-sm text-gray-600">MCP - </p>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-10">
<p></p>
<div className="mt-4 space-y-2">
<button
onClick={() => setInput('你好,介绍一下自己')}
className="text-blue-600 hover:underline block mx-auto"
>
"你好,介绍一下自己"
</button>
<button
onClick={() => setInput('列出可用的工具')}
className="text-blue-600 hover:underline block mx-auto"
>
"列出可用的工具"
</button>
<button
onClick={() => setInput('帮我写一段 Python 代码')}
className="text-blue-600 hover:underline block mx-auto"
>
"帮我写一段 Python 代码"
</button>
</div>
</div>
)}
{messages.map(msg => (
<div
key={msg.id}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-2xl px-4 py-2 rounded-lg ${
msg.role === 'user'
? 'bg-blue-600 text-white'
: msg.role === 'system'
? 'bg-red-100 text-red-800'
: 'bg-gray-100 text-gray-900'
}`}
>
<div className="whitespace-pre-wrap">{msg.content}</div>
<div className="text-xs mt-1 opacity-70">
{msg.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="border-t p-4 bg-white">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
onKeyPress={e => e.key === 'Enter' && !e.shiftKey && handleSend()}
disabled={isLoading}
placeholder="输入消息... (回车发送)"
className="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleSend}
disabled={!input.trim() || isLoading}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? '发送中...' : '发送'}
</button>
</div>
<div className="mt-2 text-xs text-gray-500">
使
</div>
</div>
</div>
</div>
);
}

View File

@@ -153,16 +153,28 @@ export default function MCPChat() {
} }
if (!canAccessChat) { if (!canAccessChat) {
const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'https://valuefrontier.cn';
return ( return (
<div className="flex flex-col items-center justify-center h-screen"> <div className="flex flex-col items-center justify-center h-screen">
<h2 className="text-2xl mb-4">使 AI </h2> <h2 className="text-2xl mb-4">使 AI </h2>
<p className="text-gray-600 mb-6"></p> <p className="text-gray-600 mb-4"></p>
<p className="text-sm text-gray-500 mb-6">
{user?.subscription_tier || '未知'}
</p>
<div className="space-y-2">
<a <a
href={`${process.env.NEXT_PUBLIC_MAIN_APP_URL}/subscription`} href={`${mainAppUrl}/subscription`}
className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700" className="block bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700"
> >
</a> </a>
<button
onClick={() => window.location.reload()}
className="block bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
>
</button>
</div>
</div> </div>
); );
} }

View File

@@ -0,0 +1,37 @@
// lib/auth-override.ts - 临时覆盖权限检查(用于测试)
export interface AuthOverride {
enabled: boolean;
forceAuth?: boolean;
forceSubscription?: boolean;
mockUser?: any;
}
// 开发/测试模式下可以覆盖权限
export const authOverride: AuthOverride = {
enabled: true, // 启用覆盖
forceAuth: true, // 强制认证通过
forceSubscription: true, // 强制订阅权限通过
mockUser: {
id: 'max-user',
username: 'Max User',
email: 'max@valuefrontier.cn',
subscription_tier: 'max'
}
};
export function applyAuthOverride(authInfo: any) {
if (!authOverride.enabled) return authInfo;
if (authOverride.forceAuth) {
authInfo.isAuthenticated = true;
authInfo.user = authOverride.mockUser || authInfo.user;
}
if (authOverride.forceSubscription) {
authInfo.canAccessChat = true;
}
console.log('Auth override applied:', authInfo);
return authInfo;
}

View File

@@ -55,10 +55,12 @@ export async function checkAuth(): Promise<AuthInfo> {
}; };
} }
// 检查订阅权限 // 检查订阅权限 - 包括 max 用户和其他高级订阅
const canAccessChat = ['premium', 'pro', 'enterprise'].includes( const userTier = data.user.subscription_tier?.toLowerCase() || '';
data.user.subscription_tier?.toLowerCase() const canAccessChat = ['max', 'premium', 'pro', 'enterprise', 'vip', 'plus'].includes(userTier) ||
); userTier !== 'free' && userTier !== '' && userTier !== 'basic';
console.log('User subscription tier:', data.user.subscription_tier, 'Can access:', canAccessChat);
return { return {
isAuthenticated: true, isAuthenticated: true,