@nextclaw/ui 0.9.12 → 0.9.14
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 +13 -0
- package/package.json +17 -16
- package/public/logos/weixin.svg +5 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.test.ts +9 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.ts +10 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +0 -1
- package/src/components/config/ChannelForm.tsx +1 -122
- package/src/components/config/ChannelsList.test.tsx +127 -0
- package/src/components/config/ChannelsList.tsx +2 -1
- package/src/components/config/channel-form-fields.ts +131 -0
- package/src/lib/i18n.channels.ts +52 -0
- package/src/lib/i18n.ts +2 -43
- package/src/lib/logos.ts +1 -0
- package/src/transport/app-client.test.ts +49 -0
- package/src/transport/app-client.ts +22 -6
- package/src/transport/local.transport.ts +4 -1
- package/src/transport/remote.transport.ts +5 -1
- package/src/transport/sse-stream.test.ts +68 -0
- package/src/transport/sse-stream.ts +48 -7
- package/src/transport/transport.types.ts +2 -0
- package/LICENSE +0 -21
- package/dist/assets/ChannelsList-CJy2GG1a.js +0 -1
- package/dist/assets/ChatPage-C7WxI8VY.js +0 -41
- package/dist/assets/DocBrowser-Nu-ae-eS.js +0 -1
- package/dist/assets/LogoBadge-DbbMxPlr.js +0 -1
- package/dist/assets/MarketplacePage-BQYQPeg2.js +0 -49
- package/dist/assets/McpMarketplacePage-kiMJbS8r.js +0 -40
- package/dist/assets/ModelConfig-DRQ07Snj.js +0 -1
- package/dist/assets/ProvidersList-C0NjzKX1.js +0 -1
- package/dist/assets/RemoteAccessPage-DVJ5hBNJ.js +0 -1
- package/dist/assets/RuntimeConfig-BkYWyRW7.js +0 -1
- package/dist/assets/SearchConfig-DZTW8Wnq.js +0 -1
- package/dist/assets/SecretsConfig-WMcwg5KV.js +0 -3
- package/dist/assets/SessionsConfig-CWtCXQRn.js +0 -2
- package/dist/assets/chat-message-BcjCODYN.js +0 -3
- package/dist/assets/config-hints-CApS3K_7.js +0 -1
- package/dist/assets/config-layout-BHnOoweL.js +0 -1
- package/dist/assets/index-BOhlxC12.js +0 -8
- package/dist/assets/index-SGSkQCPi.css +0 -1
- package/dist/assets/label-DOWMfYPL.js +0 -1
- package/dist/assets/marketplace-localization-Dk31LJJJ.js +0 -1
- package/dist/assets/page-layout-DQtmTgqR.js +0 -1
- package/dist/assets/popover-k11l1-ko.js +0 -1
- package/dist/assets/provider-models-BOeNnjk9.js +0 -1
- package/dist/assets/security-config-FFy-bOJb.js +0 -1
- package/dist/assets/skeleton-DQ4QRdSe.js +0 -1
- package/dist/assets/status-dot-CsZRxe8p.js +0 -1
- package/dist/assets/switch-DfMy8G96.js +0 -1
- package/dist/assets/tabs-custom-CITPDGXY.js +0 -1
- package/dist/assets/useConfirmDialog-Dr39o-0I.js +0 -1
- package/dist/assets/vendor-TJ2hy_Lv.js +0 -441
- package/dist/index.html +0 -18
- package/dist/logo.svg +0 -5
- package/dist/logos/aihubmix.png +0 -0
- package/dist/logos/anthropic.svg +0 -1
- package/dist/logos/dashscope.png +0 -0
- package/dist/logos/deepseek.png +0 -0
- package/dist/logos/dingtalk.svg +0 -1
- package/dist/logos/discord.svg +0 -1
- package/dist/logos/email.svg +0 -1
- package/dist/logos/feishu.svg +0 -12
- package/dist/logos/gemini.svg +0 -1
- package/dist/logos/groq.svg +0 -1
- package/dist/logos/minimax.svg +0 -1
- package/dist/logos/mochat.svg +0 -6
- package/dist/logos/moonshot.png +0 -0
- package/dist/logos/openai.svg +0 -1
- package/dist/logos/openrouter.svg +0 -1
- package/dist/logos/qq.svg +0 -1
- package/dist/logos/slack.svg +0 -1
- package/dist/logos/telegram.svg +0 -1
- package/dist/logos/vllm.svg +0 -1
- package/dist/logos/wecom.svg +0 -11
- package/dist/logos/whatsapp.svg +0 -1
- package/dist/logos/zhipu.svg +0 -15
package/src/lib/logos.ts
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('appClient runtime detection', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.restoreAllMocks();
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('falls back to LocalAppTransport when runtime probe returns html', async () => {
|
|
14
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
15
|
+
new Response('<html>ui shell</html>', {
|
|
16
|
+
status: 200,
|
|
17
|
+
headers: {
|
|
18
|
+
'content-type': 'text/html; charset=utf-8'
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
23
|
+
|
|
24
|
+
const { LocalAppTransport } = await import('@/transport/local.transport');
|
|
25
|
+
const localRequest = vi
|
|
26
|
+
.spyOn(LocalAppTransport.prototype, 'request')
|
|
27
|
+
.mockResolvedValue({ ok: true } as never);
|
|
28
|
+
|
|
29
|
+
const { appClient } = await import('@/transport/app-client');
|
|
30
|
+
const result = await appClient.request<{ ok: boolean }>({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: '/api/config'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
36
|
+
expect.stringContaining('/_remote/runtime'),
|
|
37
|
+
expect.objectContaining({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
credentials: 'include',
|
|
40
|
+
cache: 'no-store'
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
expect(localRequest).toHaveBeenCalledWith({
|
|
44
|
+
method: 'GET',
|
|
45
|
+
path: '/api/config'
|
|
46
|
+
});
|
|
47
|
+
expect(result).toEqual({ ok: true });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -4,6 +4,11 @@ import { RemoteSessionMultiplexTransport } from './remote.transport';
|
|
|
4
4
|
import type { AppTransport, RemoteRuntimeInfo, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
5
5
|
|
|
6
6
|
const REMOTE_RUNTIME_PATH = '/_remote/runtime';
|
|
7
|
+
const DEFAULT_REMOTE_RUNTIME: RemoteRuntimeInfo = {
|
|
8
|
+
mode: 'remote',
|
|
9
|
+
protocolVersion: 1,
|
|
10
|
+
wsPath: '/_remote/ws'
|
|
11
|
+
};
|
|
7
12
|
|
|
8
13
|
async function resolveRuntime(apiBase: string): Promise<AppTransport> {
|
|
9
14
|
const runtimeUrl = `${apiBase.replace(/\/$/, '')}${REMOTE_RUNTIME_PATH}`;
|
|
@@ -22,17 +27,28 @@ async function resolveRuntime(apiBase: string): Promise<AppTransport> {
|
|
|
22
27
|
return new LocalAppTransport({ apiBase });
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
const
|
|
30
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
31
|
+
if (!contentType.includes('application/json')) {
|
|
32
|
+
return response.status >= 400
|
|
33
|
+
? new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase)
|
|
34
|
+
: new LocalAppTransport({ apiBase });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let payload: { ok?: boolean; data?: RemoteRuntimeInfo } | null = null;
|
|
38
|
+
try {
|
|
39
|
+
payload = await response.json() as { ok?: boolean; data?: RemoteRuntimeInfo };
|
|
40
|
+
} catch {
|
|
41
|
+
return response.status >= 400
|
|
42
|
+
? new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase)
|
|
43
|
+
: new LocalAppTransport({ apiBase });
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
if (response.ok && payload.ok && payload.data?.mode === 'remote') {
|
|
27
47
|
return new RemoteSessionMultiplexTransport(payload.data, apiBase);
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
if (response.status >= 400) {
|
|
31
|
-
return new RemoteSessionMultiplexTransport(
|
|
32
|
-
mode: 'remote',
|
|
33
|
-
protocolVersion: 1,
|
|
34
|
-
wsPath: '/_remote/ws'
|
|
35
|
-
}, apiBase);
|
|
51
|
+
return new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase);
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
return new LocalAppTransport({ apiBase });
|
|
@@ -151,7 +151,10 @@ export class LocalAppTransport implements AppTransport {
|
|
|
151
151
|
throw new Error(text.trim() || `HTTP ${response.status}`);
|
|
152
152
|
}
|
|
153
153
|
try {
|
|
154
|
-
return await readSseStreamResult<TFinal>(response, input.onEvent
|
|
154
|
+
return await readSseStreamResult<TFinal>(response, input.onEvent, {
|
|
155
|
+
terminalEventNames: input.terminalEventNames,
|
|
156
|
+
terminalEventPayloadTypes: input.terminalEventPayloadTypes
|
|
157
|
+
});
|
|
155
158
|
} finally {
|
|
156
159
|
input.signal?.removeEventListener('abort', abort);
|
|
157
160
|
}
|
|
@@ -7,6 +7,8 @@ type RemoteTarget = {
|
|
|
7
7
|
method: string;
|
|
8
8
|
path: string;
|
|
9
9
|
body?: unknown;
|
|
10
|
+
terminalEventNames?: readonly string[];
|
|
11
|
+
terminalEventPayloadTypes?: Partial<Record<string, readonly string[]>>;
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
type RemoteBrowserFrame =
|
|
@@ -167,7 +169,9 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
167
169
|
target: {
|
|
168
170
|
method: input.method,
|
|
169
171
|
path: input.path,
|
|
170
|
-
...(input.body !== undefined ? { body: input.body } : {})
|
|
172
|
+
...(input.body !== undefined ? { body: input.body } : {}),
|
|
173
|
+
...(input.terminalEventNames ? { terminalEventNames: input.terminalEventNames } : {}),
|
|
174
|
+
...(input.terminalEventPayloadTypes ? { terminalEventPayloadTypes: input.terminalEventPayloadTypes } : {})
|
|
171
175
|
}
|
|
172
176
|
});
|
|
173
177
|
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { readSseStreamResult } from './sse-stream';
|
|
3
|
+
|
|
4
|
+
function createSseResponse(frames: string[]): Response {
|
|
5
|
+
const payload = new TextEncoder().encode(frames.join(''));
|
|
6
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
7
|
+
start(controller) {
|
|
8
|
+
controller.enqueue(payload);
|
|
9
|
+
controller.close();
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return new Response(stream, {
|
|
13
|
+
status: 200,
|
|
14
|
+
headers: {
|
|
15
|
+
'content-type': 'text/event-stream; charset=utf-8'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function encodeFrame(event: string, payload: unknown): string {
|
|
21
|
+
return `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('readSseStreamResult', () => {
|
|
25
|
+
it('accepts terminal-event streams without final frame when configured', async () => {
|
|
26
|
+
const events: Array<{ name: string; payload?: unknown }> = [];
|
|
27
|
+
const response = createSseResponse([
|
|
28
|
+
encodeFrame('ncp-event', { type: 'message.text-delta', payload: { delta: 'hello' } }),
|
|
29
|
+
encodeFrame('run.finished', { sessionId: 's1' })
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const result = await readSseStreamResult(response, (event) => {
|
|
33
|
+
events.push(event);
|
|
34
|
+
}, {
|
|
35
|
+
terminalEventNames: ['run.finished']
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result).toBeUndefined();
|
|
39
|
+
expect(events.map((event) => event.name)).toEqual(['ncp-event', 'run.finished']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('accepts terminal payload-type streams without final frame when configured', async () => {
|
|
43
|
+
const events: Array<{ name: string; payload?: unknown }> = [];
|
|
44
|
+
const response = createSseResponse([
|
|
45
|
+
encodeFrame('ncp-event', { type: 'message.text-delta', payload: { delta: 'hello' } }),
|
|
46
|
+
encodeFrame('ncp-event', { type: 'run.finished', payload: { sessionId: 's1' } })
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const result = await readSseStreamResult(response, (event) => {
|
|
50
|
+
events.push(event);
|
|
51
|
+
}, {
|
|
52
|
+
terminalEventPayloadTypes: {
|
|
53
|
+
'ncp-event': ['run.finished']
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result).toBeUndefined();
|
|
58
|
+
expect(events.map((event) => event.name)).toEqual(['ncp-event', 'ncp-event']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('still rejects streams that end without final or configured terminal event', async () => {
|
|
62
|
+
const response = createSseResponse([
|
|
63
|
+
encodeFrame('ncp-event', { type: 'message.text-delta', payload: { delta: 'hello' } })
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
await expect(readSseStreamResult(response, () => undefined)).rejects.toThrow('stream ended without final event');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { StreamEvent } from './transport.types';
|
|
2
2
|
|
|
3
3
|
type SseErrorPayload = { message?: string } | string | undefined;
|
|
4
|
+
type FinalResultSink = (value: unknown) => void;
|
|
5
|
+
type TerminalEventSink = (frame: StreamEvent) => void;
|
|
6
|
+
type TerminalDetectionOptions = {
|
|
7
|
+
terminalEventNames?: readonly string[];
|
|
8
|
+
terminalEventPayloadTypes?: Partial<Record<string, readonly string[]>>;
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
function parseSseFrame(frame: string): StreamEvent | null {
|
|
6
12
|
const lines = frame.split('\n');
|
|
@@ -42,10 +48,29 @@ function readSseErrorMessage(payload: SseErrorPayload, fallback: string): string
|
|
|
42
48
|
: payload?.message ?? fallback;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
function matchesTerminalFrame(frame: StreamEvent, options: TerminalDetectionOptions): boolean {
|
|
52
|
+
if ((options.terminalEventNames ?? []).includes(frame.name)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const payloadTypes = options.terminalEventPayloadTypes?.[frame.name];
|
|
56
|
+
if (!payloadTypes || payloadTypes.length === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const payloadType =
|
|
60
|
+
typeof frame.payload === 'object' &&
|
|
61
|
+
frame.payload &&
|
|
62
|
+
'type' in frame.payload &&
|
|
63
|
+
typeof (frame.payload as { type?: unknown }).type === 'string'
|
|
64
|
+
? (frame.payload as { type: string }).type
|
|
65
|
+
: null;
|
|
66
|
+
return payloadType !== null && payloadTypes.includes(payloadType);
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
function processSseFrame(
|
|
46
70
|
rawFrame: string,
|
|
47
71
|
onEvent: (event: StreamEvent) => void,
|
|
48
|
-
setFinalResult:
|
|
72
|
+
setFinalResult: FinalResultSink,
|
|
73
|
+
setTerminalEvent: TerminalEventSink
|
|
49
74
|
): void {
|
|
50
75
|
const frame = parseSseFrame(rawFrame);
|
|
51
76
|
if (!frame) {
|
|
@@ -58,17 +83,19 @@ function processSseFrame(
|
|
|
58
83
|
if (frame.name === 'error') {
|
|
59
84
|
throw new Error(readSseErrorMessage(frame.payload as SseErrorPayload, 'chat stream failed'));
|
|
60
85
|
}
|
|
86
|
+
setTerminalEvent(frame);
|
|
61
87
|
onEvent(frame);
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
function flushBufferedFrames(
|
|
65
91
|
bufferState: { value: string },
|
|
66
92
|
onEvent: (event: StreamEvent) => void,
|
|
67
|
-
setFinalResult:
|
|
93
|
+
setFinalResult: FinalResultSink,
|
|
94
|
+
setTerminalEvent: TerminalEventSink
|
|
68
95
|
): void {
|
|
69
96
|
let boundary = bufferState.value.indexOf('\n\n');
|
|
70
97
|
while (boundary !== -1) {
|
|
71
|
-
processSseFrame(bufferState.value.slice(0, boundary), onEvent, setFinalResult);
|
|
98
|
+
processSseFrame(bufferState.value.slice(0, boundary), onEvent, setFinalResult, setTerminalEvent);
|
|
72
99
|
bufferState.value = bufferState.value.slice(boundary + 2);
|
|
73
100
|
boundary = bufferState.value.indexOf('\n\n');
|
|
74
101
|
}
|
|
@@ -76,7 +103,8 @@ function flushBufferedFrames(
|
|
|
76
103
|
|
|
77
104
|
export async function readSseStreamResult<TFinal>(
|
|
78
105
|
response: Response,
|
|
79
|
-
onEvent: (event: StreamEvent) => void
|
|
106
|
+
onEvent: (event: StreamEvent) => void,
|
|
107
|
+
options: TerminalDetectionOptions = {}
|
|
80
108
|
): Promise<TFinal> {
|
|
81
109
|
const reader = response.body?.getReader();
|
|
82
110
|
if (!reader) {
|
|
@@ -86,7 +114,7 @@ export async function readSseStreamResult<TFinal>(
|
|
|
86
114
|
const decoder = new TextDecoder();
|
|
87
115
|
const bufferState = { value: '' };
|
|
88
116
|
let finalResult: unknown = undefined;
|
|
89
|
-
|
|
117
|
+
let sawTerminalEvent = false;
|
|
90
118
|
try {
|
|
91
119
|
while (true) {
|
|
92
120
|
const { value, done } = await reader.read();
|
|
@@ -96,18 +124,31 @@ export async function readSseStreamResult<TFinal>(
|
|
|
96
124
|
bufferState.value += decoder.decode(value, { stream: true });
|
|
97
125
|
flushBufferedFrames(bufferState, onEvent, (nextValue) => {
|
|
98
126
|
finalResult = nextValue;
|
|
127
|
+
}, (event) => {
|
|
128
|
+
if (matchesTerminalFrame(event, options)) {
|
|
129
|
+
sawTerminalEvent = true;
|
|
130
|
+
}
|
|
99
131
|
});
|
|
100
132
|
}
|
|
101
133
|
if (bufferState.value.trim()) {
|
|
102
|
-
processSseFrame(bufferState.value,
|
|
134
|
+
processSseFrame(bufferState.value, (event) => {
|
|
135
|
+
if (matchesTerminalFrame(event, options)) {
|
|
136
|
+
sawTerminalEvent = true;
|
|
137
|
+
}
|
|
138
|
+
onEvent(event);
|
|
139
|
+
}, (nextValue) => {
|
|
103
140
|
finalResult = nextValue;
|
|
141
|
+
}, (event) => {
|
|
142
|
+
if (matchesTerminalFrame(event, options)) {
|
|
143
|
+
sawTerminalEvent = true;
|
|
144
|
+
}
|
|
104
145
|
});
|
|
105
146
|
}
|
|
106
147
|
} finally {
|
|
107
148
|
reader.releaseLock();
|
|
108
149
|
}
|
|
109
150
|
|
|
110
|
-
if (finalResult === undefined) {
|
|
151
|
+
if (finalResult === undefined && !sawTerminalEvent) {
|
|
111
152
|
throw new Error('stream ended without final event');
|
|
112
153
|
}
|
|
113
154
|
return finalResult as TFinal;
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 NextClaw contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as v,j as a,X as Z,a3 as ee,e as T,K as ae,aq as te,b1 as se,b2 as ne,b3 as le,a0 as re,G as oe,ai as ce,H as ie}from"./vendor-TJ2hy_Lv.js";import{t as e,c as I,Z as me,u as q,a as $,b as H,$ as pe,a0 as de,I as D,S as be,e as ue,f as xe,g as ye,h as ge,B as E}from"./index-BOhlxC12.js";import{L as he}from"./label-DOWMfYPL.js";import{S as fe}from"./switch-DfMy8G96.js";import{S as K}from"./status-dot-CsZRxe8p.js";import{L as J}from"./LogoBadge-DbbMxPlr.js";import{h as _}from"./config-hints-CApS3K_7.js";import{c as we,b as ve,a as je,C as ke}from"./config-layout-BHnOoweL.js";import{T as Se}from"./tabs-custom-CITPDGXY.js";import{P as Ce,a as Ne}from"./page-layout-DQtmTgqR.js";function Pe({value:t,onChange:m,className:i,placeholder:r=""}){const[o,u]=v.useState(""),d=x=>{x.key==="Enter"&&o.trim()?(x.preventDefault(),m([...t,o.trim()]),u("")):x.key==="Backspace"&&!o&&t.length>0&&m(t.slice(0,-1))},g=x=>{m(t.filter((j,h)=>h!==x))};return a.jsxs("div",{className:I("flex flex-wrap gap-2 p-2 border rounded-md min-h-[42px]",i),children:[t.map((x,j)=>a.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm",children:[x,a.jsx("button",{type:"button",onClick:()=>g(j),className:"hover:text-red-300 transition-colors",children:a.jsx(Z,{className:"h-3 w-3"})})]},j)),a.jsx("input",{type:"text",value:o,onChange:x=>u(x.target.value),onKeyDown:d,className:"flex-1 outline-none min-w-[100px] bg-transparent text-sm",placeholder:r||e("enterTag")})]})}function z(t){var r,o;const m=me();return((r=t.tutorialUrls)==null?void 0:r[m])||((o=t.tutorialUrls)==null?void 0:o.default)||t.tutorialUrl}const Ie={telegram:"telegram.svg",slack:"slack.svg",discord:"discord.svg",whatsapp:"whatsapp.svg",qq:"qq.svg",feishu:"feishu.svg",dingtalk:"dingtalk.svg",wecom:"wecom.svg",mochat:"mochat.svg",email:"email.svg"};function Fe(t,m){const i=m.toLowerCase(),r=t[i];return r?`/logos/${r}`:null}function Y(t){return Fe(Ie,t)}const B=[{value:"pairing",label:"pairing"},{value:"allowlist",label:"allowlist"},{value:"open",label:"open"},{value:"disabled",label:"disabled"}],G=[{value:"open",label:"open"},{value:"allowlist",label:"allowlist"},{value:"disabled",label:"disabled"}],Te=[{value:"off",label:"off"},{value:"partial",label:"partial"},{value:"block",label:"block"},{value:"progress",label:"progress"}],De=t=>t.includes("token")||t.includes("secret")||t.includes("password")?a.jsx(ae,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("url")||t.includes("host")?a.jsx(te,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("email")||t.includes("mail")?a.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("id")||t.includes("from")?a.jsx(ne,{className:"h-3.5 w-3.5 text-gray-500"}):t==="enabled"||t==="consentGranted"?a.jsx(le,{className:"h-3.5 w-3.5 text-gray-500"}):a.jsx(re,{className:"h-3.5 w-3.5 text-gray-500"});function R(){return{telegram:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"proxy",type:"text",label:e("proxy")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:G},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],discord:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"gatewayUrl",type:"text",label:e("gatewayUrl")},{name:"intents",type:"number",label:e("intents")},{name:"proxy",type:"text",label:e("proxy")},{name:"mediaMaxMb",type:"number",label:e("attachmentMaxSizeMb")},{name:"streaming",type:"select",label:e("streamingMode"),options:Te},{name:"draftChunk",type:"json",label:e("draftChunkingJson")},{name:"textChunkLimit",type:"number",label:e("textChunkLimit")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:G},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],whatsapp:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"bridgeUrl",type:"text",label:e("bridgeUrl")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],feishu:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"appSecret",type:"password",label:e("appSecret")},{name:"encryptKey",type:"password",label:e("encryptKey")},{name:"verificationToken",type:"password",label:e("verificationToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],dingtalk:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"clientId",type:"text",label:e("clientId")},{name:"clientSecret",type:"password",label:e("clientSecret")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],wecom:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"corpId",type:"text",label:e("corpId")},{name:"agentId",type:"text",label:e("agentId")},{name:"secret",type:"password",label:e("secret")},{name:"token",type:"password",label:e("token")},{name:"callbackPort",type:"number",label:e("callbackPort")},{name:"callbackPath",type:"text",label:e("callbackPath")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],slack:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"mode",type:"text",label:e("mode")},{name:"webhookPath",type:"text",label:e("webhookPath")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"botToken",type:"password",label:e("botToken")},{name:"appToken",type:"password",label:e("appToken")}],email:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"consentGranted",type:"boolean",label:e("consentGranted")},{name:"imapHost",type:"text",label:e("imapHost")},{name:"imapPort",type:"number",label:e("imapPort")},{name:"imapUsername",type:"text",label:e("imapUsername")},{name:"imapPassword",type:"password",label:e("imapPassword")},{name:"fromAddress",type:"email",label:e("fromAddress")}],mochat:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"clawToken",type:"password",label:e("clawToken")},{name:"agentUserId",type:"text",label:e("agentUserId")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],qq:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"secret",type:"password",label:e("appSecret")},{name:"markdownSupport",type:"boolean",label:e("markdownSupport")},{name:"allowFrom",type:"tags",label:e("allowFrom")}]}}function A(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function V(t,m){const i={...t};for(const[r,o]of Object.entries(m)){const u=i[r];if(A(u)&&A(o)){i[r]=V(u,o);continue}i[r]=o}return i}function Ae(t,m){const i=t.split("."),r={};let o=r;for(let u=0;u<i.length-1;u+=1){const d=i[u];o[d]={},o=o[d]}return o[i[i.length-1]]=m,r}function Le({channelName:t}){var O,U;const{data:m}=q(),{data:i}=$(),{data:r}=H(),o=pe(),u=de(),[d,g]=v.useState({}),[x,j]=v.useState({}),[h,f]=v.useState(null),k=t?m==null?void 0:m.channels[t]:null,w=t?R()[t]??[]:[],c=r==null?void 0:r.uiHints,p=t?`channels.${t}`:null,S=((O=r==null?void 0:r.actions)==null?void 0:O.filter(s=>s.scope===p))??[],C=t&&(((U=_(`channels.${t}`,c))==null?void 0:U.label)??t),P=i==null?void 0:i.channels.find(s=>s.name===t),F=P?z(P):void 0;v.useEffect(()=>{if(k){g({...k});const s={};(t?R()[t]??[]:[]).filter(l=>l.type==="json").forEach(l=>{const y=k[l.name];s[l.name]=JSON.stringify(y??{},null,2)}),j(s)}else g({}),j({})},[k,t]);const N=(s,n)=>{g(l=>({...l,[s]:n}))},L=s=>{if(s.preventDefault(),!t)return;const n={...d};for(const l of w){if(l.type!=="password")continue;const y=n[l.name];(typeof y!="string"||y.length===0)&&delete n[l.name]}for(const l of w){if(l.type!=="json")continue;const y=x[l.name]??"";try{n[l.name]=y.trim()?JSON.parse(y):{}}catch{T.error(`${e("invalidJson")}: ${l.name}`);return}}o.mutate({channel:t,data:n})},Q=s=>{if(!s||!t)return;const n=s.channels;if(!A(n))return;const l=n[t];A(l)&&g(y=>V(y,l))},W=async s=>{if(!(!t||!p)){f(s.id);try{let n={...d};s.saveBeforeRun&&(n={...n,...s.savePatch??{}},g(n),await o.mutateAsync({channel:t,data:n}));const l=await u.mutateAsync({actionId:s.id,data:{scope:p,draftConfig:Ae(p,n)}});Q(l.patch),l.ok?T.success(l.message||e("success")):T.error(l.message||e("error"))}catch(n){const l=n instanceof Error?n.message:String(n);T.error(`${e("error")}: ${l}`)}finally{f(null)}}};if(!t||!P||!k)return a.jsx("div",{className:we,children:a.jsxs("div",{children:[a.jsx("h3",{className:"text-base font-semibold text-gray-900",children:e("channelsSelectTitle")}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsSelectDescription")})]})});const M=!!k.enabled;return a.jsxs("div",{className:ve,children:[a.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[a.jsxs("div",{className:"min-w-0",children:[a.jsxs("div",{className:"flex items-center gap-3",children:[a.jsx(J,{name:t,src:Y(t),className:I("h-9 w-9 rounded-lg border",M?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:t[0]})}),a.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900 capitalize",children:C})]}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsFormDescription")}),F&&a.jsxs("a",{href:F,className:"mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[a.jsx(ee,{className:"h-3.5 w-3.5"}),e("channelsGuideTitle")]})]}),a.jsx(K,{status:M?"active":"inactive",label:M?e("statusActive"):e("statusInactive")})]})}),a.jsxs("form",{onSubmit:L,className:"flex min-h-0 flex-1 flex-col",children:[a.jsx("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-6 py-5",children:w.map(s=>{const n=t?_(`channels.${t}.${s.name}`,c):void 0,l=(n==null?void 0:n.label)??s.label,y=n==null?void 0:n.placeholder;return a.jsxs("div",{className:"space-y-2.5",children:[a.jsxs(he,{htmlFor:s.name,className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[De(s.name),l]}),s.type==="boolean"&&a.jsxs("div",{className:"flex items-center justify-between rounded-xl bg-gray-50 p-3",children:[a.jsx("span",{className:"text-sm text-gray-500",children:d[s.name]?e("enabled"):e("disabled")}),a.jsx(fe,{id:s.name,checked:d[s.name]||!1,onCheckedChange:b=>N(s.name,b),className:"data-[state=checked]:bg-emerald-500"})]}),(s.type==="text"||s.type==="email")&&a.jsx(D,{id:s.name,type:s.type,value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y,className:"rounded-xl"}),s.type==="password"&&a.jsx(D,{id:s.name,type:"password",value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y??e("leaveBlankToKeepUnchanged"),className:"rounded-xl"}),s.type==="number"&&a.jsx(D,{id:s.name,type:"number",value:d[s.name]||0,onChange:b=>N(s.name,parseInt(b.target.value,10)||0),placeholder:y,className:"rounded-xl"}),s.type==="tags"&&a.jsx(Pe,{value:d[s.name]||[],onChange:b=>N(s.name,b)}),s.type==="select"&&a.jsxs(be,{value:d[s.name]||"",onValueChange:b=>N(s.name,b),children:[a.jsx(ue,{className:"rounded-xl",children:a.jsx(xe,{})}),a.jsx(ye,{children:(s.options??[]).map(b=>a.jsx(ge,{value:b.value,children:b.label},b.value))})]}),s.type==="json"&&a.jsx("textarea",{id:s.name,value:x[s.name]??"{}",onChange:b=>j(X=>({...X,[s.name]:b.target.value})),className:"min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"})]},s.name)})}),a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[a.jsx("div",{className:"flex flex-wrap items-center gap-2",children:S.filter(s=>s.trigger==="manual").map(s=>a.jsx(E,{type:"button",onClick:()=>W(s),disabled:o.isPending||!!h,variant:"secondary",children:h===s.id?e("connecting"):s.title},s.id))}),a.jsx(E,{type:"submit",disabled:o.isPending||!!h,children:o.isPending?e("saving"):e("save")})]})]})]})}const Me={telegram:"channelDescTelegram",slack:"channelDescSlack",email:"channelDescEmail",webhook:"channelDescWebhook",discord:"channelDescDiscord",feishu:"channelDescFeishu"};function Ke(){const{data:t}=q(),{data:m}=$(),{data:i}=H(),[r,o]=v.useState("enabled"),[u,d]=v.useState(),[g,x]=v.useState(""),j=i==null?void 0:i.uiHints,h=m==null?void 0:m.channels,f=t==null?void 0:t.channels,k=[{id:"enabled",label:e("channelsTabEnabled"),count:(h??[]).filter(c=>{var p;return(p=f==null?void 0:f[c.name])==null?void 0:p.enabled}).length},{id:"all",label:e("channelsTabAll"),count:(h??[]).length}],w=v.useMemo(()=>{const c=g.trim().toLowerCase();return(h??[]).filter(p=>{var C;const S=((C=f==null?void 0:f[p.name])==null?void 0:C.enabled)||!1;return r==="enabled"?S:!0}).filter(p=>c?(p.displayName||p.name).toLowerCase().includes(c)||p.name.toLowerCase().includes(c):!0)},[r,f,h,g]);return v.useEffect(()=>{if(w.length===0){d(void 0);return}w.some(p=>p.name===u)||d(w[0].name)},[w,u]),!t||!m?a.jsx("div",{className:"p-8 text-gray-400",children:e("channelsLoading")}):a.jsxs(Ce,{className:"xl:flex xl:h-full xl:min-h-0 xl:flex-col xl:pb-0",children:[a.jsx(Ne,{title:e("channelsPageTitle"),description:e("channelsPageDescription")}),a.jsxs("div",{className:I(ke,"xl:min-h-0 xl:flex-1"),children:[a.jsxs("section",{className:je,children:[a.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:a.jsx(Se,{tabs:k,activeTab:r,onChange:o,className:"mb-0"})}),a.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:a.jsxs("div",{className:"relative",children:[a.jsx(oe,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),a.jsx(D,{value:g,onChange:c=>x(c.target.value),placeholder:e("channelsFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),a.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto overscroll-contain p-3",children:[w.map(c=>{const p=t.channels[c.name],S=(p==null?void 0:p.enabled)||!1,C=_(`channels.${c.name}`,j),P=z(c),F=(C==null?void 0:C.help)||e(Me[c.name]||"channelDescriptionDefault"),N=u===c.name;return a.jsx("button",{type:"button",onClick:()=>d(c.name),className:I("w-full rounded-xl border p-2.5 text-left transition-all",N?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:a.jsxs("div",{className:"flex items-start justify-between gap-3",children:[a.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[a.jsx(J,{name:c.name,src:Y(c.name),className:I("h-10 w-10 rounded-lg border",S?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:c.name[0]})}),a.jsxs("div",{className:"min-w-0",children:[a.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:c.displayName||c.name}),a.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:F})]})]}),a.jsxs("div",{className:"flex items-center gap-2",children:[P&&a.jsx("a",{href:P,onClick:L=>L.stopPropagation(),className:"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500",title:e("channelsGuideTitle"),children:a.jsx(ce,{className:"h-3.5 w-3.5"})}),a.jsx(K,{status:S?"active":"inactive",label:S?e("statusActive"):e("statusInactive"),className:"min-w-[56px] justify-center"})]})]})},c.name)}),w.length===0&&a.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[a.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:a.jsx(ie,{className:"h-5 w-5 text-gray-300"})}),a.jsx("p",{className:"text-sm font-medium text-gray-700",children:e("channelsNoMatch")})]})]})]}),a.jsx(Le,{channelName:u})]})]})}export{Ke as ChannelsList};
|