@tradejs/app 1.0.6 → 1.0.9

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.
@@ -98,6 +98,13 @@ async function main() {
98
98
  const nextBin = require.resolve('next/dist/bin/next');
99
99
  const args = [nextBin, command, ...rawArgs];
100
100
  const explicitPort = parsePort(readArgValue(rawArgs, ['-p', '--port']));
101
+ const hasBundlerFlag = rawArgs.some(
102
+ (arg) => arg === '--webpack' || arg === '--turbopack',
103
+ );
104
+
105
+ if ((command === 'dev' || command === 'build') && !hasBundlerFlag) {
106
+ args.push('--webpack');
107
+ }
101
108
 
102
109
  if (dev && explicitPort === null) {
103
110
  const requestedPort = parsePort(process.env.PORT) || 3000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/app",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "Installable Next.js UI for the TradeJS open-source framework: dashboards, backtests, charts, and runtime data.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -41,20 +41,20 @@
41
41
  "@chakra-ui/charts": "^3.24.0",
42
42
  "@chakra-ui/react": "^3.24.2",
43
43
  "@emotion/react": "^11.14.0",
44
- "@langchain/core": "^0.3.68",
45
- "@langchain/openai": "^0.6.11",
46
- "@tradejs/connectors": "^1.0.6",
47
- "@tradejs/core": "^1.0.6",
48
- "@tradejs/indicators": "^1.0.6",
49
- "@tradejs/infra": "^1.0.6",
50
- "@tradejs/node": "^1.0.6",
51
- "@tradejs/types": "^1.0.6",
44
+ "@langchain/core": "^1.1.42",
45
+ "@langchain/openai": "^1.4.5",
46
+ "@tradejs/connectors": "^1.0.9",
47
+ "@tradejs/core": "^1.0.9",
48
+ "@tradejs/indicators": "^1.0.9",
49
+ "@tradejs/infra": "^1.0.9",
50
+ "@tradejs/node": "^1.0.9",
51
+ "@tradejs/types": "^1.0.9",
52
52
  "bcryptjs": "^2.4.3",
53
53
  "date-fns": "^3.3.1",
54
54
  "idb-keyval": "^6.2.2",
55
55
  "klinecharts": "10.0.0-alpha9",
56
- "lodash": "^4.17.21",
57
- "next": "^16.1.7",
56
+ "lodash": "^4.18.1",
57
+ "next": "^16.2.3",
58
58
  "next-auth": "^5.0.0-beta.26",
59
59
  "next-themes": "^0.4.6",
60
60
  "react": "^19.2.3",
@@ -72,7 +72,7 @@
72
72
  "dev": "node ./bin/tradejs-app.mjs dev",
73
73
  "build": "NODE_ENV=production node ./bin/tradejs-app.mjs build",
74
74
  "start": "node ./bin/tradejs-app.mjs start",
75
- "lint": "yarn run -T eslint src --ext .js,.jsx,.ts,.tsx"
75
+ "lint": "ESLINT_USE_FLAT_CONFIG=true yarn run -T eslint src"
76
76
  },
77
77
  "license": "MIT",
78
78
  "author": "aleksnick (https://github.com/aleksnick)"
@@ -6,7 +6,8 @@ import {
6
6
  SystemMessage,
7
7
  } from '@langchain/core/messages';
8
8
  import { toJson } from '@tradejs/core/data';
9
- import { getOpenRouterModelKwargs } from '@tradejs/node/ai';
9
+ import { getAiResponseLanguagePromptName } from '@tradejs/infra/aiLanguages';
10
+ import { DEFAULT_AI_MODEL, getOpenRouterModelKwargs } from '@tradejs/node/ai';
10
11
  import { getConnectorCreatorByProvider } from '@tradejs/node/connectors';
11
12
  import {
12
13
  AIChatHistory,
@@ -14,29 +15,50 @@ import {
14
15
  ConnectorCreator,
15
16
  Filters,
16
17
  } from '@tradejs/types';
17
- import { getFile, setFile } from '@tradejs/infra/files';
18
+ import { getData, redisKeys, setData } from '@tradejs/infra/redis';
18
19
  import { logger } from '@tradejs/infra/logger';
19
20
  import { getUserSettings } from '@tradejs/infra/userSettings';
20
21
  import { getCurrentUserName } from '@app/lib/currentUser';
21
22
 
22
23
  export const dynamic = 'force-dynamic';
23
24
 
24
- const HISTORY_DIR = 'data/chats';
25
25
  const projectRoot =
26
26
  String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
27
27
 
28
- const getHistory = async (symbol: string): Promise<AIChatHistory> => {
29
- const history = await getFile(HISTORY_DIR, symbol, [], projectRoot);
30
- return history;
28
+ const normalizeChatSymbolKey = (symbol: string): string => {
29
+ const normalized = symbol
30
+ .trim()
31
+ .toUpperCase()
32
+ .replace(/[^A-Z0-9._-]+/g, '_')
33
+ .replace(/_+/g, '_')
34
+ .replace(/^_+|_+$/g, '')
35
+ .slice(0, 120);
36
+
37
+ if (!normalized) {
38
+ throw new Error('Invalid AI chat symbol');
39
+ }
40
+
41
+ return normalized;
42
+ };
43
+
44
+ const getHistoryKey = (userName: string, symbol: string) =>
45
+ redisKeys.aiChatHistory(userName, normalizeChatSymbolKey(symbol));
46
+
47
+ const getHistory = async (
48
+ userName: string,
49
+ symbol: string,
50
+ ): Promise<AIChatHistory> => {
51
+ return (await getData(getHistoryKey(userName, symbol), [])) as AIChatHistory;
31
52
  };
32
53
 
33
54
  const appendMessagesToHistory = async (
55
+ userName: string,
34
56
  symbol: string,
35
57
  messages: AIChatHistory,
36
58
  ): Promise<void> => {
37
- const history = await getHistory(symbol);
38
- await setFile(HISTORY_DIR, symbol, [...history, ...messages], {
39
- projectRoot,
59
+ const history = await getHistory(userName, symbol);
60
+ await setData(getHistoryKey(userName, symbol), [...history, ...messages], {
61
+ expire: 0,
40
62
  });
41
63
  };
42
64
 
@@ -44,18 +66,21 @@ const buildMessages = (
44
66
  filters: Filters,
45
67
  historyEntry: AIChatMessage,
46
68
  historyData: unknown,
69
+ responseLanguage: string,
47
70
  ) => {
48
71
  const messages = new Array<BaseMessage>();
49
72
 
50
73
  messages.push(
51
74
  new SystemMessage(
52
- 'Ты помощник крипто-трейдера. Отвечай на русском языке',
75
+ `You are a crypto trader assistant. Reply in ${getAiResponseLanguagePromptName(
76
+ responseLanguage,
77
+ )}.`,
53
78
  ),
54
79
  );
55
80
 
56
81
  messages.push(
57
82
  new SystemMessage(
58
- `Вот данные по монете ${filters.symbol}: ${toJson(historyData)}`,
83
+ `Here is the market data for ${filters.symbol}: ${toJson(historyData)}`,
59
84
  ),
60
85
  );
61
86
 
@@ -72,19 +97,19 @@ const buildMessages = (
72
97
 
73
98
  const invokeChatModel = async (messages: BaseMessage[], userName: string) => {
74
99
  const settings = await getUserSettings(userName);
75
- if (!settings.OPENAI_API_KEY || !settings.OPENAI_API_ENDPOINT) {
100
+ if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
76
101
  throw new Error(`AI settings are incomplete for user ${userName}`);
77
102
  }
78
103
 
79
- const modelKwargs = getOpenRouterModelKwargs(settings.OPENAI_API_ENDPOINT);
104
+ const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
80
105
 
81
106
  const model = new ChatOpenAI({
82
107
  temperature: 0.7,
83
- modelName: 'gpt-4o',
84
- apiKey: settings.OPENAI_API_KEY,
108
+ modelName: settings.AI_MODEL || DEFAULT_AI_MODEL,
109
+ apiKey: settings.AI_API_KEY,
85
110
  ...(Object.keys(modelKwargs).length ? { modelKwargs } : {}),
86
111
  configuration: {
87
- baseURL: settings.OPENAI_API_ENDPOINT,
112
+ baseURL: settings.AI_API_ENDPOINT,
88
113
  },
89
114
  });
90
115
 
@@ -107,7 +132,13 @@ export const GET = async (request: NextRequest) => {
107
132
  );
108
133
  }
109
134
 
110
- const history = await getHistory(symbol);
135
+ try {
136
+ normalizeChatSymbolKey(symbol);
137
+ } catch {
138
+ return NextResponse.json({ error: 'Invalid symbol' }, { status: 400 });
139
+ }
140
+
141
+ const history = await getHistory(userName, symbol);
111
142
  return NextResponse.json({ history });
112
143
  } catch (error) {
113
144
  logger.log('error', `AI history error: %o`, error);
@@ -138,7 +169,13 @@ export const POST = async (request: NextRequest) => {
138
169
  );
139
170
  }
140
171
 
141
- await appendMessagesToHistory(filters.symbol, [message]);
172
+ try {
173
+ normalizeChatSymbolKey(filters.symbol);
174
+ } catch {
175
+ return NextResponse.json({ error: 'Invalid symbol' }, { status: 400 });
176
+ }
177
+
178
+ await appendMessagesToHistory(userName, filters.symbol, [message]);
142
179
 
143
180
  const connectorCreator = await getConnectorCreatorByProvider(
144
181
  'bybit',
@@ -157,7 +194,13 @@ export const POST = async (request: NextRequest) => {
157
194
  interval: '60',
158
195
  });
159
196
 
160
- const chatMessages = buildMessages(filters, message, data.slice(-100));
197
+ const settings = await getUserSettings(userName);
198
+ const chatMessages = buildMessages(
199
+ filters,
200
+ message,
201
+ data.slice(-100),
202
+ settings.AI_RESPONSE_LANGUAGE,
203
+ );
161
204
 
162
205
  const response = await invokeChatModel(chatMessages, userName);
163
206
 
@@ -166,7 +209,7 @@ export const POST = async (request: NextRequest) => {
166
209
  text: response.content as string,
167
210
  };
168
211
 
169
- await appendMessagesToHistory(filters.symbol, [responseMessage]);
212
+ await appendMessagesToHistory(userName, filters.symbol, [responseMessage]);
170
213
 
171
214
  return NextResponse.json({ message: responseMessage });
172
215
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { Item, TestStat } from '@tradejs/types';
3
3
  import { parseTestName } from '@tradejs/core/backtest';
4
- import { getData, getKeys, redisKeys } from '@tradejs/infra/redis';
4
+ import { getData, getKeys, redisKeys, setData } from '@tradejs/infra/redis';
5
5
  import { logger } from '@tradejs/infra/logger';
6
6
  import { auth } from '@app/auth';
7
7
 
@@ -17,6 +17,15 @@ export const GET = async () => {
17
17
  }
18
18
 
19
19
  const result = new Array<Item>();
20
+ const indexedItems = (await getData(
21
+ redisKeys.testSummaries(userName),
22
+ null,
23
+ )) as Item[] | null;
24
+
25
+ if (Array.isArray(indexedItems) && indexedItems.length) {
26
+ return NextResponse.json({ items: indexedItems });
27
+ }
28
+
20
29
  const testsPrefix = redisKeys.tests(userName);
21
30
  const keys = await getKeys(testsPrefix);
22
31
  const configKeys = keys.filter((key) => key.endsWith(':config'));
@@ -49,6 +58,8 @@ export const GET = async () => {
49
58
  });
50
59
  }
51
60
 
61
+ await setData(redisKeys.testSummaries(userName), result, { expire: 0 });
62
+
52
63
  return NextResponse.json({ items: result });
53
64
  } catch (error) {
54
65
  logger.log('error', `Backtest list error: %o`, error);
@@ -1,7 +1,8 @@
1
1
  'use server';
2
2
 
3
3
  import { NextResponse } from 'next/server';
4
- import { delKey, redisKeys } from '@tradejs/infra/redis';
4
+ import { delKey, getData, redisKeys, setData } from '@tradejs/infra/redis';
5
+ import { Item } from '@tradejs/types';
5
6
  import { logger } from '@tradejs/infra/logger';
6
7
  import { auth } from '@app/auth';
7
8
 
@@ -46,6 +47,22 @@ export const DELETE = async (
46
47
  );
47
48
  }
48
49
 
50
+ const indexedItems = (await getData(
51
+ redisKeys.testSummaries(userName),
52
+ [],
53
+ )) as Item[];
54
+ const nextIndexedItems = indexedItems.filter(
55
+ (item) =>
56
+ !(
57
+ item?.value === name &&
58
+ typeof item?.data?.strategyName === 'string' &&
59
+ item.data.strategyName === strategy
60
+ ),
61
+ );
62
+ await setData(redisKeys.testSummaries(userName), nextIndexedItems, {
63
+ expire: 0,
64
+ });
65
+
49
66
  return NextResponse.json({ deleted: true, removedKeys });
50
67
  } catch (error) {
51
68
  logger.log('error', 'Backtest delete error: %o', error);