update pay function
This commit is contained in:
211
src/views/AgentChat/neuratalk/app/chat-direct/page.tsx
Normal file
211
src/views/AgentChat/neuratalk/app/chat-direct/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -153,16 +153,28 @@ export default function MCPChat() {
|
||||
}
|
||||
|
||||
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">
|
||||
<h2 className="text-2xl mb-4">需要订阅才能使用 AI 助手</h2>
|
||||
<p className="text-gray-600 mb-6">升级到高级版解锁所有功能</p>
|
||||
<a
|
||||
href={`${process.env.NEXT_PUBLIC_MAIN_APP_URL}/subscription`}
|
||||
className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700"
|
||||
>
|
||||
查看订阅方案
|
||||
</a>
|
||||
<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
|
||||
href={`${mainAppUrl}/subscription`}
|
||||
className="block bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700"
|
||||
>
|
||||
查看订阅方案
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
37
src/views/AgentChat/neuratalk/lib/auth-override.ts
Normal file
37
src/views/AgentChat/neuratalk/lib/auth-override.ts
Normal 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;
|
||||
}
|
||||
@@ -55,10 +55,12 @@ export async function checkAuth(): Promise<AuthInfo> {
|
||||
};
|
||||
}
|
||||
|
||||
// 检查订阅权限
|
||||
const canAccessChat = ['premium', 'pro', 'enterprise'].includes(
|
||||
data.user.subscription_tier?.toLowerCase()
|
||||
);
|
||||
// 检查订阅权限 - 包括 max 用户和其他高级订阅
|
||||
const userTier = 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 {
|
||||
isAuthenticated: true,
|
||||
|
||||
Reference in New Issue
Block a user