@tradejs/app 3.1.29-beta.261 → 3.1.29-beta.263
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -7
- package/src/app/.well-known/oauth-authorization-server/route.ts +3 -0
- package/src/app/.well-known/oauth-protected-resource/mcp/route.ts +3 -0
- package/src/app/.well-known/oauth-protected-resource/route.ts +1 -0
- package/src/app/components/Shared/Sidebar/index.tsx +2 -0
- package/src/app/lib/mcp/Mcp.module.css +63 -0
- package/src/app/lib/mcp/artifacts.ts +225 -0
- package/src/app/lib/mcp/http.ts +24 -0
- package/src/app/lib/mcp/oauth.ts +460 -0
- package/src/app/lib/mcp/server.ts +465 -0
- package/src/app/mcp/route.ts +75 -0
- package/src/app/oauth/authorize/route.ts +84 -0
- package/src/app/oauth/register/route.ts +33 -0
- package/src/app/oauth/revoke/route.ts +42 -0
- package/src/app/oauth/token/route.ts +69 -0
- package/src/app/routes/dashboard/Dashboard.tsx +41 -11
- package/src/app/routes/mcp/consent/page.tsx +67 -0
- package/src/app/routes/mcp/page.tsx +58 -0
- package/src/app/store/index.ts +0 -1
- package/src/proxy.ts +18 -0
- package/src/app/actions/ai.ts +0 -33
- package/src/app/api/ai/route.ts +0 -201
- package/src/app/components/Dashboard/AiDrawer/Message.tsx +0 -34
- package/src/app/components/Dashboard/AiDrawer/index.tsx +0 -150
- package/src/app/store/ai.ts +0 -174
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
CloseButton,
|
|
7
|
-
Drawer,
|
|
8
|
-
Portal,
|
|
9
|
-
Icon,
|
|
10
|
-
Textarea,
|
|
11
|
-
Timeline,
|
|
12
|
-
HStack,
|
|
13
|
-
Stack,
|
|
14
|
-
Text,
|
|
15
|
-
Alert,
|
|
16
|
-
SkeletonCircle,
|
|
17
|
-
SkeletonText,
|
|
18
|
-
} from '@chakra-ui/react';
|
|
19
|
-
import { GiArtificialHive } from 'react-icons/gi';
|
|
20
|
-
import { useAiChatStore, useFilters } from '#store';
|
|
21
|
-
import { Message } from './Message';
|
|
22
|
-
|
|
23
|
-
export const AiDrawer = () => {
|
|
24
|
-
const [open, setOpen] = useState(false);
|
|
25
|
-
const [input, setInput] = useState('');
|
|
26
|
-
const { filters } = useFilters();
|
|
27
|
-
const getChat = useAiChatStore((s) => s.getChat);
|
|
28
|
-
const loadHistory = useAiChatStore((s) => s.loadHistory);
|
|
29
|
-
const sendPrompt = useAiChatStore((s) => s.sendPrompt);
|
|
30
|
-
const sendQuickCommand = useAiChatStore((s) => s.sendQuickCommand);
|
|
31
|
-
const chat = getChat(filters.symbol);
|
|
32
|
-
const { loading, sending, error, messages } = chat;
|
|
33
|
-
const isBusy = loading || sending;
|
|
34
|
-
const canSend = useMemo(
|
|
35
|
-
() => input.trim().length > 0 && !sending,
|
|
36
|
-
[input, sending],
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
void loadHistory(filters.symbol);
|
|
41
|
-
}, [filters.symbol, loadHistory]);
|
|
42
|
-
|
|
43
|
-
const handleSend = async () => {
|
|
44
|
-
if (!input.trim()) return;
|
|
45
|
-
await sendPrompt(filters, input);
|
|
46
|
-
setInput('');
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const handleQuick = async (command: string) => {
|
|
50
|
-
await sendQuickCommand(filters, command);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Drawer.Root open={open} onOpenChange={(e) => setOpen(e.open)} size={'lg'}>
|
|
55
|
-
<Drawer.Trigger asChild>
|
|
56
|
-
<Button
|
|
57
|
-
colorPalette="teal"
|
|
58
|
-
size="sm"
|
|
59
|
-
variant={open ? 'surface' : 'outline'}
|
|
60
|
-
>
|
|
61
|
-
<Icon as={GiArtificialHive} boxSize={6} />
|
|
62
|
-
<Text>AI</Text>
|
|
63
|
-
</Button>
|
|
64
|
-
</Drawer.Trigger>
|
|
65
|
-
<Portal>
|
|
66
|
-
<Drawer.Backdrop />
|
|
67
|
-
<Drawer.Positioner>
|
|
68
|
-
<Drawer.Content display="flex" flexDirection="column">
|
|
69
|
-
<Drawer.Header>
|
|
70
|
-
<Drawer.Title>AI assistant</Drawer.Title>
|
|
71
|
-
<Drawer.CloseTrigger asChild>
|
|
72
|
-
<CloseButton position="absolute" right="3" top="3" />
|
|
73
|
-
</Drawer.CloseTrigger>
|
|
74
|
-
</Drawer.Header>
|
|
75
|
-
|
|
76
|
-
<Drawer.Body overflowY="auto" flex="1">
|
|
77
|
-
{error ? (
|
|
78
|
-
<Alert.Root status="error" mb={4}>
|
|
79
|
-
<Alert.Indicator />
|
|
80
|
-
<Alert.Content>
|
|
81
|
-
<Alert.Title>AI chat error</Alert.Title>
|
|
82
|
-
<Alert.Description>{error}</Alert.Description>
|
|
83
|
-
</Alert.Content>
|
|
84
|
-
</Alert.Root>
|
|
85
|
-
) : null}
|
|
86
|
-
{loading ? (
|
|
87
|
-
<Stack gap="4" maxW="xs">
|
|
88
|
-
<HStack width="full">
|
|
89
|
-
<SkeletonCircle size="6" />
|
|
90
|
-
<SkeletonText noOfLines={2} />
|
|
91
|
-
</HStack>
|
|
92
|
-
<SkeletonText ml="8" noOfLines={3} />
|
|
93
|
-
</Stack>
|
|
94
|
-
) : (
|
|
95
|
-
<Timeline.Root>
|
|
96
|
-
{messages.map((message, index) => (
|
|
97
|
-
<Message key={index} message={message} index={index} />
|
|
98
|
-
))}
|
|
99
|
-
</Timeline.Root>
|
|
100
|
-
)}
|
|
101
|
-
</Drawer.Body>
|
|
102
|
-
|
|
103
|
-
<Drawer.Footer flexDirection="column" alignItems="stretch" gap={3}>
|
|
104
|
-
<HStack>
|
|
105
|
-
<Button
|
|
106
|
-
size="sm"
|
|
107
|
-
variant="outline"
|
|
108
|
-
disabled={isBusy}
|
|
109
|
-
onClick={() => handleQuick('/line')}
|
|
110
|
-
>
|
|
111
|
-
/line
|
|
112
|
-
</Button>
|
|
113
|
-
<Button
|
|
114
|
-
size="sm"
|
|
115
|
-
variant="outline"
|
|
116
|
-
disabled={isBusy}
|
|
117
|
-
onClick={() => handleQuick('/analyze')}
|
|
118
|
-
>
|
|
119
|
-
/analyze
|
|
120
|
-
</Button>
|
|
121
|
-
</HStack>
|
|
122
|
-
|
|
123
|
-
<Textarea
|
|
124
|
-
size="sm"
|
|
125
|
-
placeholder="Введите сообщение..."
|
|
126
|
-
autoresize
|
|
127
|
-
rows={3}
|
|
128
|
-
maxH="15lh"
|
|
129
|
-
value={input}
|
|
130
|
-
disabled={sending}
|
|
131
|
-
onChange={(e) => setInput(e.target.value)}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
<Button
|
|
135
|
-
mt={2}
|
|
136
|
-
size={'sm'}
|
|
137
|
-
variant="subtle"
|
|
138
|
-
disabled={!canSend}
|
|
139
|
-
loading={sending}
|
|
140
|
-
onClick={handleSend}
|
|
141
|
-
>
|
|
142
|
-
Send
|
|
143
|
-
</Button>
|
|
144
|
-
</Drawer.Footer>
|
|
145
|
-
</Drawer.Content>
|
|
146
|
-
</Drawer.Positioner>
|
|
147
|
-
</Portal>
|
|
148
|
-
</Drawer.Root>
|
|
149
|
-
);
|
|
150
|
-
};
|
package/src/app/store/ai.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand';
|
|
2
|
-
import { getHistory, sendMessage } from '#actions/ai';
|
|
3
|
-
import { AIChatHistory, AIChatMessage, Filters } from '@tradejs/types';
|
|
4
|
-
|
|
5
|
-
type AiChatEntry = {
|
|
6
|
-
loading: boolean;
|
|
7
|
-
sending: boolean;
|
|
8
|
-
loaded: boolean;
|
|
9
|
-
error: string | null;
|
|
10
|
-
messages: AIChatHistory;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
interface AiChatState {
|
|
14
|
-
chats: Record<string, AiChatEntry>;
|
|
15
|
-
getChat: (symbol: string) => AiChatEntry;
|
|
16
|
-
loadHistory: (symbol: string) => Promise<void>;
|
|
17
|
-
sendPrompt: (filters: Filters, input: string) => Promise<void>;
|
|
18
|
-
sendQuickCommand: (filters: Filters, command: string) => Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const EMPTY_CHAT: AiChatEntry = {
|
|
22
|
-
loading: false,
|
|
23
|
-
sending: false,
|
|
24
|
-
loaded: false,
|
|
25
|
-
error: null,
|
|
26
|
-
messages: [],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const normalizeSymbolKey = (symbol: string) => symbol.trim().toUpperCase();
|
|
30
|
-
|
|
31
|
-
const getQuickMessage = (command: string): AIChatMessage | null => {
|
|
32
|
-
if (command === '/line') {
|
|
33
|
-
return {
|
|
34
|
-
from: 'user',
|
|
35
|
-
text: 'Какие наклонные линии можно построить на данном графике',
|
|
36
|
-
command,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const updateChat = (
|
|
44
|
-
chats: Record<string, AiChatEntry>,
|
|
45
|
-
symbol: string,
|
|
46
|
-
patch: Partial<AiChatEntry>,
|
|
47
|
-
) => ({
|
|
48
|
-
...chats,
|
|
49
|
-
[symbol]: {
|
|
50
|
-
...(chats[symbol] ?? EMPTY_CHAT),
|
|
51
|
-
...patch,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export const useAiChatStore = create<AiChatState>((set, get) => ({
|
|
56
|
-
chats: {},
|
|
57
|
-
|
|
58
|
-
getChat: (symbol) => get().chats[normalizeSymbolKey(symbol)] ?? EMPTY_CHAT,
|
|
59
|
-
|
|
60
|
-
loadHistory: async (symbol) => {
|
|
61
|
-
const symbolKey = normalizeSymbolKey(symbol);
|
|
62
|
-
if (!symbolKey) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const existing = get().chats[symbolKey];
|
|
67
|
-
if (existing?.loading) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
set((state) => ({
|
|
72
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
73
|
-
loading: true,
|
|
74
|
-
error: null,
|
|
75
|
-
}),
|
|
76
|
-
}));
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const history = await getHistory(symbol);
|
|
80
|
-
set((state) => ({
|
|
81
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
82
|
-
loading: false,
|
|
83
|
-
loaded: true,
|
|
84
|
-
messages: history,
|
|
85
|
-
}),
|
|
86
|
-
}));
|
|
87
|
-
} catch (error) {
|
|
88
|
-
set((state) => ({
|
|
89
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
90
|
-
loading: false,
|
|
91
|
-
error: error instanceof Error ? error.message : 'Failed to load chat',
|
|
92
|
-
}),
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
sendPrompt: async (filters, input) => {
|
|
98
|
-
const trimmed = input.trim();
|
|
99
|
-
if (!trimmed) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const message: AIChatMessage = {
|
|
104
|
-
from: 'user',
|
|
105
|
-
text: trimmed,
|
|
106
|
-
command: 'prompt',
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const symbolKey = normalizeSymbolKey(filters.symbol);
|
|
110
|
-
|
|
111
|
-
set((state) => ({
|
|
112
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
113
|
-
sending: true,
|
|
114
|
-
error: null,
|
|
115
|
-
loaded: true,
|
|
116
|
-
messages: [...(state.chats[symbolKey]?.messages ?? []), message],
|
|
117
|
-
}),
|
|
118
|
-
}));
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const response = await sendMessage({ message, filters });
|
|
122
|
-
set((state) => ({
|
|
123
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
124
|
-
sending: false,
|
|
125
|
-
messages: [...(state.chats[symbolKey]?.messages ?? []), response],
|
|
126
|
-
}),
|
|
127
|
-
}));
|
|
128
|
-
} catch (error) {
|
|
129
|
-
set((state) => ({
|
|
130
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
131
|
-
sending: false,
|
|
132
|
-
error:
|
|
133
|
-
error instanceof Error ? error.message : 'Failed to send message',
|
|
134
|
-
}),
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
sendQuickCommand: async (filters, command) => {
|
|
140
|
-
const message = getQuickMessage(command);
|
|
141
|
-
if (!message) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const symbolKey = normalizeSymbolKey(filters.symbol);
|
|
146
|
-
|
|
147
|
-
set((state) => ({
|
|
148
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
149
|
-
sending: true,
|
|
150
|
-
error: null,
|
|
151
|
-
loaded: true,
|
|
152
|
-
messages: [...(state.chats[symbolKey]?.messages ?? []), message],
|
|
153
|
-
}),
|
|
154
|
-
}));
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const response = await sendMessage({ message, filters });
|
|
158
|
-
set((state) => ({
|
|
159
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
160
|
-
sending: false,
|
|
161
|
-
messages: [...(state.chats[symbolKey]?.messages ?? []), response],
|
|
162
|
-
}),
|
|
163
|
-
}));
|
|
164
|
-
} catch (error) {
|
|
165
|
-
set((state) => ({
|
|
166
|
-
chats: updateChat(state.chats, symbolKey, {
|
|
167
|
-
sending: false,
|
|
168
|
-
error:
|
|
169
|
-
error instanceof Error ? error.message : 'Failed to send message',
|
|
170
|
-
}),
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
}));
|