@lobehub/lobehub 2.0.0-next.37 → 2.0.0-next.39
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/CHANGELOG.md +50 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
- package/changelog/v1.json +14 -0
- package/locales/ar/auth.json +45 -1
- package/locales/ar/modelProvider.json +13 -1
- package/locales/bg-BG/auth.json +45 -1
- package/locales/bg-BG/modelProvider.json +13 -1
- package/locales/de-DE/auth.json +45 -1
- package/locales/de-DE/modelProvider.json +13 -1
- package/locales/en-US/auth.json +45 -1
- package/locales/en-US/modelProvider.json +13 -1
- package/locales/es-ES/auth.json +45 -1
- package/locales/es-ES/modelProvider.json +13 -1
- package/locales/fa-IR/auth.json +45 -1
- package/locales/fa-IR/modelProvider.json +13 -1
- package/locales/fr-FR/auth.json +45 -1
- package/locales/fr-FR/modelProvider.json +13 -1
- package/locales/it-IT/auth.json +45 -1
- package/locales/it-IT/modelProvider.json +13 -1
- package/locales/ja-JP/auth.json +45 -1
- package/locales/ja-JP/modelProvider.json +13 -1
- package/locales/ko-KR/auth.json +45 -1
- package/locales/ko-KR/modelProvider.json +13 -1
- package/locales/nl-NL/auth.json +45 -1
- package/locales/nl-NL/modelProvider.json +13 -1
- package/locales/pl-PL/auth.json +45 -1
- package/locales/pl-PL/modelProvider.json +13 -1
- package/locales/pt-BR/auth.json +45 -1
- package/locales/pt-BR/modelProvider.json +13 -1
- package/locales/ru-RU/auth.json +45 -1
- package/locales/ru-RU/modelProvider.json +13 -1
- package/locales/tr-TR/auth.json +45 -1
- package/locales/tr-TR/modelProvider.json +13 -1
- package/locales/vi-VN/auth.json +45 -1
- package/locales/vi-VN/modelProvider.json +13 -1
- package/locales/zh-CN/auth.json +45 -1
- package/locales/zh-CN/modelProvider.json +13 -1
- package/locales/zh-TW/auth.json +45 -1
- package/locales/zh-TW/modelProvider.json +13 -1
- package/package.json +1 -1
- package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
- package/packages/obervability-otel/package.json +3 -1
- package/packages/obervability-otel/src/api.ts +2 -0
- package/packages/obervability-otel/src/trpc/convention.ts +16 -0
- package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
- package/packages/obervability-otel/src/trpc/index.ts +62 -0
- package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
- package/packages/types/src/usage/usageRecord.ts +54 -0
- package/packages/web-crawler/src/crawImpl/browserless.ts +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +9 -9
- package/packages/web-crawler/src/crawler.ts +5 -5
- package/packages/web-crawler/src/urlRules.ts +13 -13
- package/packages/web-crawler/src/utils/appUrlRules.ts +5 -5
- package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
- package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
- package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
- package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
- package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
- package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
- package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
- package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
- package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
- package/src/features/Conversation/Messages/Group/index.tsx +7 -2
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +3 -0
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
- package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
- package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
- package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
- package/src/features/PluginsUI/Render/index.tsx +17 -0
- package/src/libs/mcp/client.ts +3 -2
- package/src/libs/mcp/types.ts +71 -0
- package/src/libs/trpc/lambda/index.ts +5 -2
- package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
- package/src/locales/default/auth.ts +44 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/server/routers/desktop/mcp.ts +1 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/usage.ts +36 -0
- package/src/server/routers/tools/mcp.ts +1 -3
- package/src/server/services/mcp/index.test.ts +28 -15
- package/src/server/services/mcp/index.ts +29 -18
- package/src/server/services/usage/index.test.ts +310 -0
- package/src/server/services/usage/index.ts +164 -0
- package/src/services/chat/contextEngineering.test.ts +4 -0
- package/src/services/mcp.test.ts +7 -1
- package/src/services/mcp.ts +13 -12
- package/src/services/usage.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +2 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
- package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
- package/src/store/chat/slices/message/initialState.ts +5 -0
- package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
- package/src/store/chat/slices/message/selectors/chat.ts +0 -2
- package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
- package/src/store/chat/slices/plugin/action.test.ts +4 -4
- package/src/store/chat/slices/plugin/actions/index.ts +39 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
- package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
- package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
- package/src/store/chat/store.ts +1 -1
- package/src/store/global/initialState.ts +1 -0
- package/src/store/chat/slices/plugin/action.ts +0 -539
|
@@ -131,6 +131,34 @@ describe('MessageCleanupProcessor', () => {
|
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
it('should preserve reasoning in assistant messages', async () => {
|
|
135
|
+
const processor = new MessageCleanupProcessor();
|
|
136
|
+
const reasoning = {
|
|
137
|
+
content: 'Let me think about this...',
|
|
138
|
+
signature: 'sha256:abc123',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const context = createContext([
|
|
142
|
+
{
|
|
143
|
+
content: 'Here is the answer',
|
|
144
|
+
extraField: 'remove',
|
|
145
|
+
id: 'msg5',
|
|
146
|
+
reasoning: reasoning,
|
|
147
|
+
role: 'assistant',
|
|
148
|
+
timestamp: Date.now(),
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
const result = await processor.process(context);
|
|
153
|
+
|
|
154
|
+
expect(result.messages).toHaveLength(1);
|
|
155
|
+
expect(result.messages[0]).toEqual({
|
|
156
|
+
content: 'Here is the answer',
|
|
157
|
+
reasoning: reasoning,
|
|
158
|
+
role: 'assistant',
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
134
162
|
it('should clean tool messages with name', async () => {
|
|
135
163
|
const processor = new MessageCleanupProcessor();
|
|
136
164
|
const context = createContext([
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum TRPCAttribute {
|
|
2
|
+
RPC_METHOD = 'rpc.method',
|
|
3
|
+
RPC_SERVICE = 'rpc.service',
|
|
4
|
+
RPC_SYSTEM = 'rpc.system',
|
|
5
|
+
RPC_TRPC_PATH = 'rpc.trpc.path',
|
|
6
|
+
RPC_TRPC_STATUS_CODE = 'rpc.trpc.status_code',
|
|
7
|
+
RPC_TRPC_SUCCESS = 'rpc.trpc.success',
|
|
8
|
+
RPC_TRPC_TYPE = 'rpc.trpc.type',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
ATTR_ERROR_TYPE,
|
|
13
|
+
ATTR_EXCEPTION_MESSAGE,
|
|
14
|
+
ATTR_EXCEPTION_STACKTRACE,
|
|
15
|
+
ATTR_EXCEPTION_TYPE,
|
|
16
|
+
} from '@opentelemetry/semantic-conventions';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseTRPCPath, tRPCConventionFromPathAndType } from './';
|
|
2
|
+
|
|
3
|
+
describe('splitRpcPath', () => {
|
|
4
|
+
it('should split path into service and method with url', () => {
|
|
5
|
+
const { method, service } = parseTRPCPath('/trpc/lambda/someService.someMethod');
|
|
6
|
+
expect(method).toBe('someMethod');
|
|
7
|
+
expect(service).toBe('someService');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should split path into service and method without url', () => {
|
|
11
|
+
const { method, service } = parseTRPCPath('someService.someMethod');
|
|
12
|
+
expect(method).toBe('someMethod');
|
|
13
|
+
expect(service).toBe('someService');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('createBaseAttributes', () => {
|
|
18
|
+
it('should create base attributes with service and method', () => {
|
|
19
|
+
const attributes = tRPCConventionFromPathAndType('someService.someMethod', 'query');
|
|
20
|
+
expect(attributes).toEqual({
|
|
21
|
+
'rpc.system': 'trpc',
|
|
22
|
+
'rpc.trpc.path': 'someService.someMethod',
|
|
23
|
+
'rpc.trpc.type': 'query',
|
|
24
|
+
'rpc.service': 'someService',
|
|
25
|
+
'rpc.method': 'someMethod',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should create base attributes without service', () => {
|
|
30
|
+
const attributes = tRPCConventionFromPathAndType('someMethod', 'mutation');
|
|
31
|
+
expect(attributes).toEqual({
|
|
32
|
+
'rpc.system': 'trpc',
|
|
33
|
+
'rpc.trpc.path': 'someMethod',
|
|
34
|
+
'rpc.trpc.type': 'mutation',
|
|
35
|
+
'rpc.method': 'someMethod',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Attributes } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
import { TRPCAttribute } from './convention';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ERROR_CODE = 'UNKNOWN_ERROR';
|
|
6
|
+
export const DEFAULT_SUCCESS_STATUS = 'OK';
|
|
7
|
+
|
|
8
|
+
const textEncoder = new TextEncoder();
|
|
9
|
+
|
|
10
|
+
export function parseTRPCPath(path: string) {
|
|
11
|
+
const parts = path?.split('.') ?? [];
|
|
12
|
+
if (parts.length <= 1) {
|
|
13
|
+
return { method: path, service: undefined };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const method = parts.at(-1);
|
|
17
|
+
const service = parts.slice(0, -1).join('.').split('/').pop();
|
|
18
|
+
return { method, service };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function tRPCConventionFromPathAndType(path: string, type: string): Attributes {
|
|
22
|
+
const { method, service } = parseTRPCPath(path);
|
|
23
|
+
|
|
24
|
+
const attributes: Attributes = {
|
|
25
|
+
[TRPCAttribute.RPC_SYSTEM]: 'trpc',
|
|
26
|
+
[TRPCAttribute.RPC_TRPC_PATH]: path,
|
|
27
|
+
[TRPCAttribute.RPC_TRPC_TYPE]: type,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (service) {
|
|
31
|
+
attributes[TRPCAttribute.RPC_SERVICE] = service;
|
|
32
|
+
}
|
|
33
|
+
if (method) {
|
|
34
|
+
attributes[TRPCAttribute.RPC_METHOD] = method;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return attributes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const getPayloadSize = (payload: unknown): number | undefined => {
|
|
41
|
+
if (payload === undefined || payload === null) return undefined;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const serialized = JSON.stringify(payload);
|
|
45
|
+
return textEncoder.encode(serialized).length;
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const createAttributesForMetrics = (
|
|
52
|
+
baseAttributes: Attributes,
|
|
53
|
+
statusCode: string,
|
|
54
|
+
extraAttributes?: Attributes,
|
|
55
|
+
): Attributes => ({
|
|
56
|
+
...baseAttributes,
|
|
57
|
+
[TRPCAttribute.RPC_TRPC_STATUS_CODE]: statusCode,
|
|
58
|
+
...extraAttributes,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export * from './convention';
|
|
62
|
+
export * from './metrics';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { metrics } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
const meter = metrics.getMeter('trpc-server');
|
|
4
|
+
|
|
5
|
+
export const serverDurationHistogram = meter.createHistogram('rpc.server.duration', {
|
|
6
|
+
description: 'Measures the duration of inbound RPC.',
|
|
7
|
+
unit: 'ms',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const serverRequestSizeHistogram = meter.createHistogram('rpc.server.request.size', {
|
|
11
|
+
description: 'Measures the size of RPC request messages (uncompressed).',
|
|
12
|
+
unit: 'By',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const serverResponseSizeHistogram = meter.createHistogram('rpc.server.response.size', {
|
|
16
|
+
description: 'Measures the size of RPC response messages (uncompressed).',
|
|
17
|
+
unit: 'By',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const serverRequestsPerRpcHistogram = meter.createHistogram('rpc.server.requests_per_rpc', {
|
|
21
|
+
description: 'Measures the number of messages received per RPC.',
|
|
22
|
+
unit: '{count}',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const serverResponsesPerRpcHistogram = meter.createHistogram(
|
|
26
|
+
'rpc.server.responses_per_rpc',
|
|
27
|
+
{
|
|
28
|
+
description: 'Measures the number of messages sent per RPC.',
|
|
29
|
+
unit: '{count}',
|
|
30
|
+
},
|
|
31
|
+
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MessageMetadata } from '../message';
|
|
2
|
+
|
|
3
|
+
export interface UsageRecordItem {
|
|
4
|
+
createdAt: Date;
|
|
5
|
+
/**
|
|
6
|
+
* ID
|
|
7
|
+
**/
|
|
8
|
+
id: string;
|
|
9
|
+
inputStartAt?: Date | null;
|
|
10
|
+
/**
|
|
11
|
+
* Meta information
|
|
12
|
+
**/
|
|
13
|
+
metadata?: MessageMetadata | null;
|
|
14
|
+
/**
|
|
15
|
+
* Model id
|
|
16
|
+
*/
|
|
17
|
+
model: string;
|
|
18
|
+
outputFinishAt?: Date | null;
|
|
19
|
+
outputStartAt?: Date | null;
|
|
20
|
+
/**
|
|
21
|
+
* Provider id
|
|
22
|
+
*/
|
|
23
|
+
provider: string;
|
|
24
|
+
/**
|
|
25
|
+
* Spend
|
|
26
|
+
**/
|
|
27
|
+
spend: number;
|
|
28
|
+
/**
|
|
29
|
+
* Usage details
|
|
30
|
+
**/
|
|
31
|
+
totalInputTokens?: number | null;
|
|
32
|
+
totalOutputTokens?: number | null;
|
|
33
|
+
totalTokens?: number | null;
|
|
34
|
+
/**
|
|
35
|
+
* Performance details
|
|
36
|
+
**/
|
|
37
|
+
tps?: number | null;
|
|
38
|
+
ttft?: number | null;
|
|
39
|
+
/**
|
|
40
|
+
* Call types
|
|
41
|
+
**/
|
|
42
|
+
type: string;
|
|
43
|
+
updatedAt: Date;
|
|
44
|
+
userId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type UsageLog = {
|
|
48
|
+
date: number;
|
|
49
|
+
day: string;
|
|
50
|
+
records: UsageRecordItem[];
|
|
51
|
+
totalRequests: number;
|
|
52
|
+
totalSpend: number;
|
|
53
|
+
totalTokens: number;
|
|
54
|
+
};
|
|
@@ -56,7 +56,7 @@ export const browserless: CrawlImpl = async (url, { filterOptions }) => {
|
|
|
56
56
|
if (
|
|
57
57
|
!!result.content &&
|
|
58
58
|
result.title &&
|
|
59
|
-
// Just a moment...
|
|
59
|
+
// "Just a moment..." indicates being blocked by CloudFlare
|
|
60
60
|
result.title.trim() !== 'Just a moment...'
|
|
61
61
|
) {
|
|
62
62
|
return {
|
|
@@ -6,25 +6,25 @@ import { htmlToMarkdown } from '../utils/htmlToMarkdown';
|
|
|
6
6
|
import { DEFAULT_TIMEOUT, withTimeout } from '../utils/withTimeout';
|
|
7
7
|
|
|
8
8
|
const mixinHeaders = {
|
|
9
|
-
//
|
|
9
|
+
// Accepted content types
|
|
10
10
|
'Accept':
|
|
11
11
|
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
|
12
|
-
//
|
|
12
|
+
// Accepted encoding methods
|
|
13
13
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
14
|
-
//
|
|
14
|
+
// Accepted languages
|
|
15
15
|
'Accept-Language': 'en-US,en;q=0.9,zh;q=0.8',
|
|
16
|
-
//
|
|
16
|
+
// Cache control
|
|
17
17
|
'Cache-Control': 'max-age=0',
|
|
18
|
-
//
|
|
18
|
+
// Connection type
|
|
19
19
|
'Connection': 'keep-alive',
|
|
20
|
-
//
|
|
20
|
+
// Indicates which site the request is from
|
|
21
21
|
'Referer': 'https://www.google.com/',
|
|
22
|
-
//
|
|
22
|
+
// Upgrade insecure requests
|
|
23
23
|
'Upgrade-Insecure-Requests': '1',
|
|
24
|
-
//
|
|
24
|
+
// Simulate real browser User-Agent
|
|
25
25
|
'User-Agent':
|
|
26
26
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
|
27
|
-
//
|
|
27
|
+
// Prevent cross-site request forgery
|
|
28
28
|
'sec-ch-ua': '"Google Chrome";v="121", "Not A(Brand";v="99", "Chromium";v="121"',
|
|
29
29
|
'sec-ch-ua-mobile': '?0',
|
|
30
30
|
'sec-ch-ua-platform': '"Windows"',
|
|
@@ -19,8 +19,8 @@ export class Crawler {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param options
|
|
22
|
+
* Crawl webpage content
|
|
23
|
+
* @param options Crawl options
|
|
24
24
|
*/
|
|
25
25
|
async crawl({
|
|
26
26
|
url,
|
|
@@ -31,14 +31,14 @@ export class Crawler {
|
|
|
31
31
|
impls?: CrawlImplType[];
|
|
32
32
|
url: string;
|
|
33
33
|
}): Promise<CrawlUniformResult> {
|
|
34
|
-
//
|
|
34
|
+
// Apply URL rules
|
|
35
35
|
const {
|
|
36
36
|
transformedUrl,
|
|
37
37
|
filterOptions: ruleFilterOptions,
|
|
38
38
|
impls: ruleImpls,
|
|
39
39
|
} = applyUrlRules(url, crawUrlRules);
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Merge user-provided filter options and rule filter options, user options take priority
|
|
42
42
|
const mergedFilterOptions = {
|
|
43
43
|
...ruleFilterOptions,
|
|
44
44
|
...userFilterOptions,
|
|
@@ -53,7 +53,7 @@ export class Crawler {
|
|
|
53
53
|
? (userImpls.filter((impl) => Object.keys(crawlImpls).includes(impl)) as CrawlImplType[])
|
|
54
54
|
: systemImpls;
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Try each implementation in the built-in order
|
|
57
57
|
for (const impl of finalImpls) {
|
|
58
58
|
try {
|
|
59
59
|
const res = await crawlImpls[impl](transformedUrl, { filterOptions: mergedFilterOptions });
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
import { CrawlUrlRule } from './type';
|
|
2
2
|
|
|
3
3
|
export const crawUrlRules: CrawlUrlRule[] = [
|
|
4
|
-
//
|
|
4
|
+
// Sogou WeChat links, use search1api
|
|
5
5
|
{
|
|
6
6
|
impls: ['search1api'],
|
|
7
7
|
urlPattern: 'https://weixin.sogou.com/link(.*)',
|
|
8
8
|
},
|
|
9
|
-
//
|
|
9
|
+
// Sogou links, use search1api
|
|
10
10
|
{
|
|
11
11
|
impls: ['search1api'],
|
|
12
12
|
urlPattern: 'https://sogou.com/link(.*)',
|
|
13
13
|
},
|
|
14
|
-
// YouTube
|
|
14
|
+
// YouTube links, use search1api, formatted as markdown, can return subtitle content
|
|
15
15
|
{
|
|
16
16
|
impls: ['search1api'],
|
|
17
17
|
urlPattern: 'https://www.youtube.com/watch(.*)',
|
|
18
18
|
},
|
|
19
|
-
// Reddit
|
|
19
|
+
// Reddit links, use search1api, formatted as markdown, includes title, author, interaction count, specific comment content, etc.
|
|
20
20
|
{
|
|
21
21
|
impls: ['search1api'],
|
|
22
22
|
urlPattern: 'https://www.reddit.com/r/(.*)/comments/(.*)',
|
|
23
23
|
},
|
|
24
|
-
//
|
|
24
|
+
// WeChat official accounts have crawler protection, use search1api first, jina as fallback (currently jina crawling may be blocked)
|
|
25
25
|
{
|
|
26
26
|
impls: ['search1api', 'jina'],
|
|
27
27
|
urlPattern: 'https://mp.weixin.qq.com(.*)',
|
|
28
28
|
},
|
|
29
|
-
//
|
|
29
|
+
// GitHub source code parsing
|
|
30
30
|
{
|
|
31
31
|
filterOptions: {
|
|
32
32
|
enableReadability: false,
|
|
@@ -43,7 +43,7 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
|
43
43
|
// GitHub discussion
|
|
44
44
|
urlPattern: 'https://github.com/(.*)/discussions/(.*)',
|
|
45
45
|
},
|
|
46
|
-
//
|
|
46
|
+
// All PDFs use jina
|
|
47
47
|
{
|
|
48
48
|
impls: ['jina'],
|
|
49
49
|
urlPattern: 'https://(.*).pdf',
|
|
@@ -53,7 +53,7 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
|
53
53
|
impls: ['jina'],
|
|
54
54
|
urlPattern: 'https://arxiv.org/pdf/(.*)',
|
|
55
55
|
},
|
|
56
|
-
//
|
|
56
|
+
// Zhihu has crawler protection, use jina
|
|
57
57
|
{
|
|
58
58
|
impls: ['jina'],
|
|
59
59
|
urlPattern: 'https://zhuanlan.zhihu.com(.*)',
|
|
@@ -63,7 +63,7 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
|
63
63
|
urlPattern: 'https://zhihu.com(.*)',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
|
-
// Medium
|
|
66
|
+
// Convert Medium articles to Scribe.rip
|
|
67
67
|
urlPattern: 'https://medium.com/(.*)',
|
|
68
68
|
urlTransform: 'https://scribe.rip/$1',
|
|
69
69
|
},
|
|
@@ -74,10 +74,10 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
|
74
74
|
impls: ['jina', 'browserless'],
|
|
75
75
|
urlPattern: 'https://(twitter.com|x.com)/(.*)',
|
|
76
76
|
},
|
|
77
|
-
//
|
|
77
|
+
// Sports data website rules
|
|
78
78
|
{
|
|
79
79
|
filterOptions: {
|
|
80
|
-
//
|
|
80
|
+
// Disable Readability for sports data tables and convert to plain text
|
|
81
81
|
enableReadability: false,
|
|
82
82
|
pureText: true,
|
|
83
83
|
},
|
|
@@ -94,13 +94,13 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
|
94
94
|
impls: ['jina'],
|
|
95
95
|
urlPattern: 'https://cvpr.thecvf.com(.*)',
|
|
96
96
|
},
|
|
97
|
-
//
|
|
97
|
+
// Feishu use jina
|
|
98
98
|
// https://github.com/lobehub/lobe-chat/issues/6879
|
|
99
99
|
{
|
|
100
100
|
impls: ['jina'],
|
|
101
101
|
urlPattern: 'https://(.*).feishu.cn/(.*)',
|
|
102
102
|
},
|
|
103
|
-
//
|
|
103
|
+
// Xiaohongshu has crawler protection, use Search1API or Jina (fallback)
|
|
104
104
|
{
|
|
105
105
|
impls: ['search1api', 'jina'],
|
|
106
106
|
urlPattern: 'https://(.*).xiaohongshu.com/(.*)',
|
|
@@ -9,14 +9,14 @@ export const applyUrlRules = (
|
|
|
9
9
|
transformedUrl: string;
|
|
10
10
|
} => {
|
|
11
11
|
for (const rule of urlRules) {
|
|
12
|
-
//
|
|
12
|
+
// Convert to regular expression
|
|
13
13
|
const regex = new RegExp(rule.urlPattern);
|
|
14
14
|
const match = url.match(regex);
|
|
15
15
|
|
|
16
16
|
if (match) {
|
|
17
17
|
if (rule.urlTransform) {
|
|
18
|
-
//
|
|
19
|
-
//
|
|
18
|
+
// If there is a transformation rule, perform URL transformation
|
|
19
|
+
// Replace placeholders like $1, $2 with capture group content
|
|
20
20
|
const transformedUrl = rule.urlTransform.replaceAll(
|
|
21
21
|
/\$(\d+)/g,
|
|
22
22
|
(_, index) => match[parseInt(index)] || '',
|
|
@@ -28,7 +28,7 @@ export const applyUrlRules = (
|
|
|
28
28
|
transformedUrl,
|
|
29
29
|
};
|
|
30
30
|
} else {
|
|
31
|
-
//
|
|
31
|
+
// No transformation rule but pattern matched, only return filter options
|
|
32
32
|
return {
|
|
33
33
|
filterOptions: rule.filterOptions,
|
|
34
34
|
impls: rule.impls,
|
|
@@ -38,6 +38,6 @@ export const applyUrlRules = (
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// No rule matched, return original URL
|
|
42
42
|
return { transformedUrl: url };
|
|
43
43
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
|
2
|
-
import { ChartColumnBigIcon, KeyIcon, ShieldCheck, UserCircle } from 'lucide-react';
|
|
2
|
+
import { BadgeCentIcon, ChartColumnBigIcon, KeyIcon, ShieldCheck, UserCircle } from 'lucide-react';
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
@@ -54,6 +54,15 @@ export const useCategory = () => {
|
|
|
54
54
|
</Link>
|
|
55
55
|
),
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
icon: <Icon icon={BadgeCentIcon} />,
|
|
59
|
+
key: ProfileTabs.Usage,
|
|
60
|
+
label: (
|
|
61
|
+
<Link href={'/profile/usage'} onClick={(e) => e.preventDefault()}>
|
|
62
|
+
{t('tab.usage')}
|
|
63
|
+
</Link>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
57
66
|
].filter(Boolean) as MenuProps['items'];
|
|
58
67
|
|
|
59
68
|
return cateItems;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Icon, Segmented } from '@lobehub/ui';
|
|
4
|
+
import { Col, DatePicker, DatePickerProps, Row } from 'antd';
|
|
5
|
+
import dayjs from 'dayjs';
|
|
6
|
+
import { Brain, Codesandbox } from 'lucide-react';
|
|
7
|
+
import { memo, useEffect, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
|
10
|
+
|
|
11
|
+
import { useClientDataSWR } from '@/libs/swr';
|
|
12
|
+
import { usageService } from '@/services/usage';
|
|
13
|
+
import { UsageLog } from '@/types/usage/usageRecord';
|
|
14
|
+
|
|
15
|
+
import Welcome from '../stats/features/Welcome';
|
|
16
|
+
import UsageCards from './features/UsageCards';
|
|
17
|
+
import UsageTable from './features/UsageTable';
|
|
18
|
+
import UsageTrends from './features/UsageTrends';
|
|
19
|
+
|
|
20
|
+
export interface UsageChartProps {
|
|
21
|
+
data?: UsageLog[];
|
|
22
|
+
dateStrings?: string;
|
|
23
|
+
groupBy?: GroupBy;
|
|
24
|
+
inShare?: boolean;
|
|
25
|
+
isLoading?: boolean;
|
|
26
|
+
mobile?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export enum GroupBy {
|
|
30
|
+
Model = 'model',
|
|
31
|
+
Provider = 'provider',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
35
|
+
const { t, i18n } = useTranslation('auth');
|
|
36
|
+
dayjs.locale(i18n.language);
|
|
37
|
+
|
|
38
|
+
const [groupBy, setGroupBy] = useState<GroupBy>(GroupBy.Model);
|
|
39
|
+
const [dateRange, setDateRange] = useState<dayjs.Dayjs>(dayjs(new Date()));
|
|
40
|
+
const [dateStrings, setDateStrings] = useState<string>();
|
|
41
|
+
|
|
42
|
+
const { data, isLoading, mutate } = useClientDataSWR('usage-stat', async () =>
|
|
43
|
+
usageService.findAndGroupByDay(dateStrings),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (dateStrings) {
|
|
48
|
+
mutate();
|
|
49
|
+
}
|
|
50
|
+
}, [dateStrings]);
|
|
51
|
+
|
|
52
|
+
const handleDateChange: DatePickerProps['onChange'] = (dates, dateStrings) => {
|
|
53
|
+
setDateRange(dates);
|
|
54
|
+
if (typeof dateStrings === 'string') {
|
|
55
|
+
setDateStrings(dateStrings);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Flexbox gap={mobile ? 0 : 24}>
|
|
61
|
+
<Flexbox>
|
|
62
|
+
<Row>
|
|
63
|
+
<Col span={16}>
|
|
64
|
+
{mobile ? (
|
|
65
|
+
<Welcome mobile />
|
|
66
|
+
) : (
|
|
67
|
+
<Flexbox align={'flex-start'} gap={16} horizontal justify={'space-between'}>
|
|
68
|
+
<Welcome />
|
|
69
|
+
</Flexbox>
|
|
70
|
+
)}
|
|
71
|
+
</Col>
|
|
72
|
+
<Col span={8}>
|
|
73
|
+
<Flexbox gap={16} horizontal>
|
|
74
|
+
<Segmented
|
|
75
|
+
onChange={(v) => setGroupBy(v as GroupBy)}
|
|
76
|
+
options={[
|
|
77
|
+
{
|
|
78
|
+
icon: <Icon icon={Codesandbox} />,
|
|
79
|
+
label: t('usage.welcome.model'),
|
|
80
|
+
value: GroupBy.Model,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
icon: <Icon icon={Brain} />,
|
|
84
|
+
label: t('usage.welcome.provider'),
|
|
85
|
+
value: GroupBy.Provider,
|
|
86
|
+
},
|
|
87
|
+
]}
|
|
88
|
+
value={groupBy}
|
|
89
|
+
/>
|
|
90
|
+
<DatePicker onChange={handleDateChange} picker="month" value={dateRange} />
|
|
91
|
+
</Flexbox>
|
|
92
|
+
</Col>
|
|
93
|
+
</Row>
|
|
94
|
+
</Flexbox>
|
|
95
|
+
<Flexbox>
|
|
96
|
+
<UsageCards data={data} groupBy={groupBy} isLoading={isLoading} />
|
|
97
|
+
</Flexbox>
|
|
98
|
+
<Flexbox>
|
|
99
|
+
<Row gutter={[16, 16]}>
|
|
100
|
+
<Col span={24}>
|
|
101
|
+
<UsageTrends data={data} groupBy={groupBy} isLoading={isLoading} />
|
|
102
|
+
</Col>
|
|
103
|
+
</Row>
|
|
104
|
+
</Flexbox>
|
|
105
|
+
<Row>
|
|
106
|
+
<Col span={24}>
|
|
107
|
+
<UsageTable dateStrings={dateStrings} />
|
|
108
|
+
</Col>
|
|
109
|
+
</Row>
|
|
110
|
+
</Flexbox>
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export default Client;
|