@blocklet/aigne-hub 0.2.7
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/README.md +29 -0
- package/lib/cjs/api/ai-kit.js +60 -0
- package/lib/cjs/api/api.js +17 -0
- package/lib/cjs/api/app.js +57 -0
- package/lib/cjs/api/call/api.js +51 -0
- package/lib/cjs/api/call/app.js +74 -0
- package/lib/cjs/api/call/index.js +19 -0
- package/lib/cjs/api/call/proxy.js +64 -0
- package/lib/cjs/api/call/v1.js +166 -0
- package/lib/cjs/api/call/v2.js +101 -0
- package/lib/cjs/api/config.js +46 -0
- package/lib/cjs/api/constants.js +4 -0
- package/lib/cjs/api/error.js +42 -0
- package/lib/cjs/api/index.js +19 -0
- package/lib/cjs/api/types/audio.js +2 -0
- package/lib/cjs/api/types/chat.js +14 -0
- package/lib/cjs/api/types/embedding.js +2 -0
- package/lib/cjs/api/types/image.js +2 -0
- package/lib/cjs/api/types/index.js +19 -0
- package/lib/cjs/api/types/status.js +2 -0
- package/lib/cjs/api/utils/auth.js +90 -0
- package/lib/cjs/api/utils/event-stream.js +59 -0
- package/lib/cjs/components/conversation/conversation.js +71 -0
- package/lib/cjs/components/conversation/index.js +25 -0
- package/lib/cjs/components/conversation/message.js +120 -0
- package/lib/cjs/components/conversation/prompt.js +43 -0
- package/lib/cjs/components/conversation/use-conversation.js +100 -0
- package/lib/cjs/components/credit/alert.js +40 -0
- package/lib/cjs/components/credit/balance.js +95 -0
- package/lib/cjs/components/credit/button.js +69 -0
- package/lib/cjs/components/credit/index.js +12 -0
- package/lib/cjs/components/form-label.js +27 -0
- package/lib/cjs/components/image-preview.js +116 -0
- package/lib/cjs/components/index.js +45 -0
- package/lib/cjs/components/loading-image.js +37 -0
- package/lib/cjs/components/subscribe/alert.js +53 -0
- package/lib/cjs/components/subscribe/button.js +92 -0
- package/lib/cjs/components/subscribe/state.js +42 -0
- package/lib/cjs/components/switch-button.js +48 -0
- package/lib/cjs/components/table.js +203 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/libs/logger.js +8 -0
- package/lib/cjs/utils/withLocaleProvider.js +11 -0
- package/lib/esm/api/ai-kit.js +54 -0
- package/lib/esm/api/api.js +11 -0
- package/lib/esm/api/app.js +42 -0
- package/lib/esm/api/call/api.js +24 -0
- package/lib/esm/api/call/app.js +68 -0
- package/lib/esm/api/call/index.js +3 -0
- package/lib/esm/api/call/proxy.js +58 -0
- package/lib/esm/api/call/v1.js +155 -0
- package/lib/esm/api/call/v2.js +93 -0
- package/lib/esm/api/config.js +41 -0
- package/lib/esm/api/constants.js +1 -0
- package/lib/esm/api/error.js +37 -0
- package/lib/esm/api/index.js +3 -0
- package/lib/esm/api/types/audio.js +1 -0
- package/lib/esm/api/types/chat.js +9 -0
- package/lib/esm/api/types/embedding.js +1 -0
- package/lib/esm/api/types/image.js +1 -0
- package/lib/esm/api/types/index.js +3 -0
- package/lib/esm/api/types/status.js +1 -0
- package/lib/esm/api/utils/auth.js +79 -0
- package/lib/esm/api/utils/event-stream.js +50 -0
- package/lib/esm/components/conversation/conversation.js +65 -0
- package/lib/esm/components/conversation/index.js +4 -0
- package/lib/esm/components/conversation/message.js +114 -0
- package/lib/esm/components/conversation/prompt.js +40 -0
- package/lib/esm/components/conversation/use-conversation.js +97 -0
- package/lib/esm/components/credit/alert.js +35 -0
- package/lib/esm/components/credit/balance.js +90 -0
- package/lib/esm/components/credit/button.js +64 -0
- package/lib/esm/components/credit/index.js +3 -0
- package/lib/esm/components/form-label.js +24 -0
- package/lib/esm/components/image-preview.js +110 -0
- package/lib/esm/components/index.js +14 -0
- package/lib/esm/components/loading-image.js +35 -0
- package/lib/esm/components/subscribe/alert.js +48 -0
- package/lib/esm/components/subscribe/button.js +87 -0
- package/lib/esm/components/subscribe/state.js +39 -0
- package/lib/esm/components/switch-button.js +46 -0
- package/lib/esm/components/table.js +198 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/libs/logger.js +3 -0
- package/lib/esm/utils/withLocaleProvider.js +8 -0
- package/lib/types/api/ai-kit.d.ts +70 -0
- package/lib/types/api/api.d.ts +4 -0
- package/lib/types/api/app.d.ts +113 -0
- package/lib/types/api/call/api.d.ts +2 -0
- package/lib/types/api/call/app.d.ts +50 -0
- package/lib/types/api/call/index.d.ts +3 -0
- package/lib/types/api/call/proxy.d.ts +6 -0
- package/lib/types/api/call/v1.d.ts +51 -0
- package/lib/types/api/call/v2.d.ts +30 -0
- package/lib/types/api/config.d.ts +14 -0
- package/lib/types/api/constants.d.ts +1 -0
- package/lib/types/api/error.d.ts +18 -0
- package/lib/types/api/index.d.ts +3 -0
- package/lib/types/api/types/audio.d.ts +18 -0
- package/lib/types/api/types/chat.d.ts +97 -0
- package/lib/types/api/types/embedding.d.ts +9 -0
- package/lib/types/api/types/image.d.ts +23 -0
- package/lib/types/api/types/index.d.ts +3 -0
- package/lib/types/api/types/status.d.ts +3 -0
- package/lib/types/api/utils/auth.d.ts +33 -0
- package/lib/types/api/utils/event-stream.d.ts +7 -0
- package/lib/types/components/conversation/conversation.d.ts +30 -0
- package/lib/types/components/conversation/index.d.ts +4 -0
- package/lib/types/components/conversation/message.d.ts +10 -0
- package/lib/types/components/conversation/prompt.d.ts +10 -0
- package/lib/types/components/conversation/use-conversation.d.ts +44 -0
- package/lib/types/components/credit/alert.d.ts +10 -0
- package/lib/types/components/credit/balance.d.ts +7 -0
- package/lib/types/components/credit/button.d.ts +11 -0
- package/lib/types/components/credit/index.d.ts +3 -0
- package/lib/types/components/form-label.d.ts +7 -0
- package/lib/types/components/image-preview.d.ts +17 -0
- package/lib/types/components/index.d.ts +12 -0
- package/lib/types/components/loading-image.d.ts +6 -0
- package/lib/types/components/subscribe/alert.d.ts +5 -0
- package/lib/types/components/subscribe/button.d.ts +5 -0
- package/lib/types/components/subscribe/state.d.ts +14 -0
- package/lib/types/components/switch-button.d.ts +7 -0
- package/lib/types/components/table.d.ts +3 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/libs/logger.d.ts +2 -0
- package/lib/types/utils/withLocaleProvider.d.ts +9 -0
- package/package.json +158 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import https from 'https';
|
|
3
|
+
import { getComponentWebEndpoint } from '@blocklet/sdk/lib/component';
|
|
4
|
+
import { getSignData } from '@blocklet/sdk/lib/util/verify-sign';
|
|
5
|
+
import { isNil, pick } from 'lodash';
|
|
6
|
+
import { joinURL, parseURL, stringifyParsedURL, withQuery } from 'ufo';
|
|
7
|
+
import { AI_KIT_BASE_URL } from '../constants';
|
|
8
|
+
import { getRemoteComponentCallHeaders } from '../utils/auth';
|
|
9
|
+
export function proxyToAIKit(path, { useAIKitService, proxyReqHeaders = ['accept', 'content-type'], proxyResHeaders = ['content-type', 'cache-control'], } = {}) {
|
|
10
|
+
const parseReqBody = path !== '/api/v1/audio/transcriptions';
|
|
11
|
+
return (req, res, next) => {
|
|
12
|
+
var _a;
|
|
13
|
+
const url = parseURL(withQuery(joinURL(useAIKitService ? AI_KIT_BASE_URL : getComponentWebEndpoint('ai-kit'), path), req.query));
|
|
14
|
+
const userDid = ((_a = req.user) === null || _a === void 0 ? void 0 : _a.did) || (req === null || req === void 0 ? void 0 : req.get('x-app-user-did'));
|
|
15
|
+
const proxyReq = (url.protocol === 'https:' ? https : http).request(stringifyParsedURL(url), {
|
|
16
|
+
headers: {
|
|
17
|
+
...pick(req.headers, ...proxyReqHeaders),
|
|
18
|
+
...(useAIKitService
|
|
19
|
+
? getRemoteComponentCallHeaders(req.body || {}, userDid)
|
|
20
|
+
: (() => {
|
|
21
|
+
const { iat, exp, sig, version } = getSignData({
|
|
22
|
+
data: req.body,
|
|
23
|
+
params: req.query,
|
|
24
|
+
method: req.method,
|
|
25
|
+
url: stringifyParsedURL(url),
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
'x-component-sig': sig,
|
|
29
|
+
'x-component-sig-iat': iat,
|
|
30
|
+
'x-component-sig-exp': exp,
|
|
31
|
+
'x-component-sig-version': version,
|
|
32
|
+
};
|
|
33
|
+
})()),
|
|
34
|
+
},
|
|
35
|
+
method: req.method,
|
|
36
|
+
}, (proxyRes) => {
|
|
37
|
+
if (proxyRes.statusCode)
|
|
38
|
+
res.status(proxyRes.statusCode);
|
|
39
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
40
|
+
for (const [k, v] of Object.entries(pick(proxyRes.headers, ...proxyResHeaders))) {
|
|
41
|
+
if (!isNil(v))
|
|
42
|
+
res.setHeader(k, v);
|
|
43
|
+
}
|
|
44
|
+
proxyRes.pipe(res);
|
|
45
|
+
});
|
|
46
|
+
proxyReq.on('error', (e) => next(e));
|
|
47
|
+
req.on('aborted', () => {
|
|
48
|
+
proxyReq.destroy();
|
|
49
|
+
});
|
|
50
|
+
if (parseReqBody) {
|
|
51
|
+
proxyReq.write(JSON.stringify(req.body));
|
|
52
|
+
proxyReq.end();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
req.pipe(proxyReq);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ReadableStream, TextDecoderStream } from 'stream/web';
|
|
2
|
+
import { call, getComponentWebEndpoint } from '@blocklet/sdk/lib/component';
|
|
3
|
+
import { getSignData } from '@blocklet/sdk/lib/util/verify-sign';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import FormData from 'form-data';
|
|
6
|
+
import stringify from 'json-stable-stringify';
|
|
7
|
+
import { joinURL } from 'ufo';
|
|
8
|
+
import { isChatCompletionError, } from '../types';
|
|
9
|
+
import { getRemoteComponentCallHeaders } from '../utils/auth';
|
|
10
|
+
import { EventSourceParserStream, readableToWeb } from '../utils/event-stream';
|
|
11
|
+
import { aiKitApi, catchAndRethrowUpstreamError } from './api';
|
|
12
|
+
export async function status({ useAIKitService, ...options } = {}) {
|
|
13
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
14
|
+
? aiKitApi
|
|
15
|
+
.get('/api/v1/status', {
|
|
16
|
+
responseType: options.responseType,
|
|
17
|
+
headers: { ...getRemoteComponentCallHeaders({}) },
|
|
18
|
+
})
|
|
19
|
+
.then((res) => res.data)
|
|
20
|
+
: call({ name: 'ai-kit', method: 'GET', path: '/api/v1/status', responseType: options === null || options === void 0 ? void 0 : options.responseType }).then((res) => res.data));
|
|
21
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
22
|
+
return response;
|
|
23
|
+
return response.data;
|
|
24
|
+
}
|
|
25
|
+
export async function chatCompletions(input, { useAIKitService, ...options } = {}) {
|
|
26
|
+
const response = catchAndRethrowUpstreamError(useAIKitService
|
|
27
|
+
? aiKitApi('/api/v1/chat/completions', {
|
|
28
|
+
responseType: 'stream',
|
|
29
|
+
method: 'POST',
|
|
30
|
+
data: stringify(input),
|
|
31
|
+
headers: {
|
|
32
|
+
...getRemoteComponentCallHeaders(input),
|
|
33
|
+
Accept: 'text/event-stream',
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
: call({
|
|
38
|
+
name: 'ai-kit',
|
|
39
|
+
path: 'api/v1/chat/completions',
|
|
40
|
+
data: input,
|
|
41
|
+
responseType: 'stream',
|
|
42
|
+
headers: { Accept: 'text/event-stream' },
|
|
43
|
+
}));
|
|
44
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
45
|
+
return response;
|
|
46
|
+
return new ReadableStream({
|
|
47
|
+
async start(controller) {
|
|
48
|
+
try {
|
|
49
|
+
const stream = readableToWeb((await response).data)
|
|
50
|
+
.pipeThrough(new TextDecoderStream())
|
|
51
|
+
.pipeThrough(new EventSourceParserStream());
|
|
52
|
+
for await (const chunk of stream) {
|
|
53
|
+
if (isChatCompletionError(chunk)) {
|
|
54
|
+
if (chunk.error.type) {
|
|
55
|
+
const error = new Error(chunk.error.message);
|
|
56
|
+
error.type = chunk.error.type;
|
|
57
|
+
error.timestamp = chunk.error.timestamp;
|
|
58
|
+
controller.error(error);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
controller.error(new Error(chunk.error.message));
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
controller.enqueue(chunk);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
controller.error(error);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
controller.close();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function imageGenerations(input, { useAIKitService, ...options } = {}) {
|
|
78
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
79
|
+
? aiKitApi.post('/api/v1/image/generations', input, {
|
|
80
|
+
responseType: options.responseType,
|
|
81
|
+
headers: { ...getRemoteComponentCallHeaders(input) },
|
|
82
|
+
})
|
|
83
|
+
: // @ts-ignore
|
|
84
|
+
call({
|
|
85
|
+
name: 'ai-kit',
|
|
86
|
+
path: '/api/v1/image/generations',
|
|
87
|
+
data: input,
|
|
88
|
+
responseType: options === null || options === void 0 ? void 0 : options.responseType,
|
|
89
|
+
timeout: options === null || options === void 0 ? void 0 : options.timeout,
|
|
90
|
+
}));
|
|
91
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
92
|
+
return response;
|
|
93
|
+
return response.data;
|
|
94
|
+
}
|
|
95
|
+
export async function embeddings(input, { useAIKitService, ...options } = {}) {
|
|
96
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
97
|
+
? aiKitApi.post('/api/v1/embeddings', input, {
|
|
98
|
+
responseType: options.responseType,
|
|
99
|
+
headers: { ...getRemoteComponentCallHeaders(input) },
|
|
100
|
+
})
|
|
101
|
+
: call({
|
|
102
|
+
name: 'ai-kit',
|
|
103
|
+
path: '/api/v1/embeddings',
|
|
104
|
+
data: input,
|
|
105
|
+
responseType: options === null || options === void 0 ? void 0 : options.responseType,
|
|
106
|
+
}));
|
|
107
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
108
|
+
return response;
|
|
109
|
+
return response.data;
|
|
110
|
+
}
|
|
111
|
+
export async function audioTranscriptions(input, { useAIKitService, ...options } = {}) {
|
|
112
|
+
const form = new FormData();
|
|
113
|
+
for (const [key, val] of Object.entries(input)) {
|
|
114
|
+
form.append(key, val);
|
|
115
|
+
}
|
|
116
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
117
|
+
? aiKitApi.post('/api/v1/audio/transcriptions', form, {
|
|
118
|
+
responseType: options.responseType,
|
|
119
|
+
headers: { ...getRemoteComponentCallHeaders({}) },
|
|
120
|
+
})
|
|
121
|
+
: (() => {
|
|
122
|
+
const { iat, exp, sig, version } = getSignData({
|
|
123
|
+
data: {},
|
|
124
|
+
params: {},
|
|
125
|
+
method: 'post',
|
|
126
|
+
url: '/api/v1/audio/transcriptions',
|
|
127
|
+
});
|
|
128
|
+
return axios.post(joinURL(getComponentWebEndpoint('ai-kit'), '/api/v1/audio/transcriptions'), form, {
|
|
129
|
+
headers: {
|
|
130
|
+
'x-component-sig': sig,
|
|
131
|
+
'x-component-sig-iat': iat,
|
|
132
|
+
'x-component-sig-exp': exp,
|
|
133
|
+
'x-component-sig-version': version,
|
|
134
|
+
},
|
|
135
|
+
responseType: options === null || options === void 0 ? void 0 : options.responseType,
|
|
136
|
+
});
|
|
137
|
+
})());
|
|
138
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
139
|
+
return response;
|
|
140
|
+
return response.data;
|
|
141
|
+
}
|
|
142
|
+
export async function audioSpeech(input, { useAIKitService } = {}) {
|
|
143
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
144
|
+
? aiKitApi.post('/api/v1/audio/speech', input, {
|
|
145
|
+
responseType: 'stream',
|
|
146
|
+
headers: { ...getRemoteComponentCallHeaders(input) },
|
|
147
|
+
})
|
|
148
|
+
: call({
|
|
149
|
+
name: 'ai-kit',
|
|
150
|
+
path: '/api/v1/audio/speech',
|
|
151
|
+
data: input,
|
|
152
|
+
responseType: 'stream',
|
|
153
|
+
}));
|
|
154
|
+
return response;
|
|
155
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ReadableStream, TextDecoderStream } from 'stream/web';
|
|
2
|
+
import { call } from '@blocklet/sdk/lib/component';
|
|
3
|
+
import stringify from 'json-stable-stringify';
|
|
4
|
+
import { isChatCompletionError, } from '../types';
|
|
5
|
+
import { getRemoteComponentCallHeaders } from '../utils/auth';
|
|
6
|
+
import { EventSourceParserStream, readableToWeb } from '../utils/event-stream';
|
|
7
|
+
import { aiKitApi, catchAndRethrowUpstreamError } from './api';
|
|
8
|
+
export async function chatCompletionsV2(input, { useAIKitService, ...options } = {}) {
|
|
9
|
+
const response = catchAndRethrowUpstreamError(useAIKitService
|
|
10
|
+
? aiKitApi('/api/v2/chat/completions', {
|
|
11
|
+
responseType: 'stream',
|
|
12
|
+
method: 'POST',
|
|
13
|
+
data: stringify(input),
|
|
14
|
+
headers: {
|
|
15
|
+
...getRemoteComponentCallHeaders(input, options.userDid),
|
|
16
|
+
Accept: 'text/event-stream',
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
: call({
|
|
21
|
+
name: 'ai-kit',
|
|
22
|
+
path: 'api/v2/chat/completions',
|
|
23
|
+
data: input,
|
|
24
|
+
responseType: 'stream',
|
|
25
|
+
headers: { Accept: 'text/event-stream' },
|
|
26
|
+
}));
|
|
27
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
28
|
+
return response;
|
|
29
|
+
return new ReadableStream({
|
|
30
|
+
async start(controller) {
|
|
31
|
+
try {
|
|
32
|
+
const stream = readableToWeb((await response).data)
|
|
33
|
+
.pipeThrough(new TextDecoderStream())
|
|
34
|
+
.pipeThrough(new EventSourceParserStream());
|
|
35
|
+
for await (const chunk of stream) {
|
|
36
|
+
if (isChatCompletionError(chunk)) {
|
|
37
|
+
if (chunk.error.type) {
|
|
38
|
+
const error = new Error(chunk.error.message);
|
|
39
|
+
error.type = chunk.error.type;
|
|
40
|
+
error.timestamp = chunk.error.timestamp;
|
|
41
|
+
controller.error(error);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
controller.error(new Error(chunk.error.message));
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
controller.enqueue(chunk);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
controller.error(error);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
controller.close();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function imageGenerationsV2(input, { useAIKitService, ...options } = {}) {
|
|
61
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
62
|
+
? aiKitApi.post('/api/v2/image/generations', input, {
|
|
63
|
+
responseType: options.responseType,
|
|
64
|
+
headers: { ...getRemoteComponentCallHeaders(input, options.userDid) },
|
|
65
|
+
})
|
|
66
|
+
: // @ts-ignore
|
|
67
|
+
call({
|
|
68
|
+
name: 'ai-kit',
|
|
69
|
+
path: '/api/v2/image/generations',
|
|
70
|
+
data: input,
|
|
71
|
+
responseType: options === null || options === void 0 ? void 0 : options.responseType,
|
|
72
|
+
timeout: options === null || options === void 0 ? void 0 : options.timeout,
|
|
73
|
+
}));
|
|
74
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
75
|
+
return response;
|
|
76
|
+
return response.data;
|
|
77
|
+
}
|
|
78
|
+
export async function embeddingsV2(input, { useAIKitService, ...options } = {}) {
|
|
79
|
+
const response = await catchAndRethrowUpstreamError(useAIKitService
|
|
80
|
+
? aiKitApi.post('/api/v2/embeddings', input, {
|
|
81
|
+
responseType: options.responseType,
|
|
82
|
+
headers: { ...getRemoteComponentCallHeaders(input, options.userDid) },
|
|
83
|
+
})
|
|
84
|
+
: call({
|
|
85
|
+
name: 'ai-kit',
|
|
86
|
+
path: '/api/v2/embeddings',
|
|
87
|
+
data: input,
|
|
88
|
+
responseType: options === null || options === void 0 ? void 0 : options.responseType,
|
|
89
|
+
}));
|
|
90
|
+
if ((options === null || options === void 0 ? void 0 : options.responseType) === 'stream')
|
|
91
|
+
return response;
|
|
92
|
+
return response.data;
|
|
93
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
3
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import config from '@blocklet/sdk/lib/config';
|
|
6
|
+
import { parse, stringify } from 'yaml';
|
|
7
|
+
import logger from '../libs/logger';
|
|
8
|
+
class Config extends EventEmitter {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.reloadConfigFile = async () => {
|
|
12
|
+
try {
|
|
13
|
+
this.config = parse((await readFile(Config.CONFIG_FILE_PATH)).toString());
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
logger.error(`Parse ${Config.CONFIG_FILE_PATH} error`, { error });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (!existsSync(Config.CONFIG_FILE_PATH)) {
|
|
20
|
+
writeFileSync(Config.CONFIG_FILE_PATH, '');
|
|
21
|
+
}
|
|
22
|
+
this.reloadConfigFile();
|
|
23
|
+
}
|
|
24
|
+
get useAIKitService() {
|
|
25
|
+
var _a;
|
|
26
|
+
return (_a = this.config) === null || _a === void 0 ? void 0 : _a.useAIKitService;
|
|
27
|
+
}
|
|
28
|
+
set useAIKitService(value) {
|
|
29
|
+
var _a;
|
|
30
|
+
(_a = this.config) !== null && _a !== void 0 ? _a : (this.config = {});
|
|
31
|
+
this.config.useAIKitService = value;
|
|
32
|
+
this.save();
|
|
33
|
+
this.emit('change', this.config);
|
|
34
|
+
}
|
|
35
|
+
async save() {
|
|
36
|
+
await writeFile(Config.CONFIG_FILE_PATH, stringify(this.config));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
Config.CONFIG_FILE_PATH = join(config.env.dataDir, 'ai-kit-service.config.yaml');
|
|
40
|
+
const AIKitConfig = new Config();
|
|
41
|
+
export default AIKitConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const AI_KIT_BASE_URL = process.env.AI_KIT_BASE_URL || 'https://www.aikit.rocks/ai-kit';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export var SubscriptionErrorType;
|
|
2
|
+
(function (SubscriptionErrorType) {
|
|
3
|
+
SubscriptionErrorType["UNSUBSCRIBED"] = "UNSUBSCRIBED";
|
|
4
|
+
SubscriptionErrorType["UNKNOWN"] = "UNKNOWN";
|
|
5
|
+
})(SubscriptionErrorType || (SubscriptionErrorType = {}));
|
|
6
|
+
export var CreditErrorType;
|
|
7
|
+
(function (CreditErrorType) {
|
|
8
|
+
CreditErrorType["NOT_ENOUGH"] = "NOT_ENOUGH";
|
|
9
|
+
CreditErrorType["UNKNOWN"] = "UNKNOWN";
|
|
10
|
+
})(CreditErrorType || (CreditErrorType = {}));
|
|
11
|
+
const SubscriptionErrors = {
|
|
12
|
+
[SubscriptionErrorType.UNSUBSCRIBED]: 'Hello, in order to continue chatting, please first subscribe to AI-KIT service',
|
|
13
|
+
[SubscriptionErrorType.UNKNOWN]: 'An unknown error occurred',
|
|
14
|
+
};
|
|
15
|
+
export class SubscriptionError extends Error {
|
|
16
|
+
constructor(type) {
|
|
17
|
+
const message = SubscriptionErrors[type] || SubscriptionErrors[SubscriptionErrorType.UNKNOWN];
|
|
18
|
+
super(message);
|
|
19
|
+
this.timestamp = new Date().toISOString();
|
|
20
|
+
this.type = type;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const CreditErrors = {
|
|
24
|
+
[CreditErrorType.NOT_ENOUGH]: 'Hello, in order to continue chatting, please first buy some credits in the link below.',
|
|
25
|
+
[CreditErrorType.UNKNOWN]: 'An unknown error occurred',
|
|
26
|
+
};
|
|
27
|
+
export class CreditError extends Error {
|
|
28
|
+
constructor(type, link) {
|
|
29
|
+
let message = CreditErrors[type] || CreditErrors[CreditErrorType.UNKNOWN];
|
|
30
|
+
if (type === CreditErrorType.NOT_ENOUGH && link) {
|
|
31
|
+
message += `\n\n${link}`;
|
|
32
|
+
}
|
|
33
|
+
super(message);
|
|
34
|
+
this.timestamp = new Date().toISOString();
|
|
35
|
+
this.type = type;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function isChatCompletionChunk(data) {
|
|
2
|
+
return typeof data.delta === 'object';
|
|
3
|
+
}
|
|
4
|
+
export function isChatCompletionUsage(data) {
|
|
5
|
+
return typeof data.usage === 'object';
|
|
6
|
+
}
|
|
7
|
+
export function isChatCompletionError(data) {
|
|
8
|
+
return typeof data.error === 'object';
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/// <reference path="./auth.type.d.ts" />
|
|
2
|
+
import { DidType, fromPublicKey } from '@arcblock/did';
|
|
3
|
+
import { auth } from '@blocklet/sdk/lib/middlewares';
|
|
4
|
+
import getWallet from '@blocklet/sdk/lib/wallet';
|
|
5
|
+
import { getHasher, getSigner, types } from '@ocap/mcrypto';
|
|
6
|
+
import stringify from 'json-stable-stringify';
|
|
7
|
+
const TOKEN_EXPIRES_IN_SECONDS = 60 * 10;
|
|
8
|
+
export const wallet = getWallet();
|
|
9
|
+
const ADMIN_ROLES = ['owner', 'admin'];
|
|
10
|
+
export const ensureAdmin = auth({ roles: ADMIN_ROLES });
|
|
11
|
+
const signer = getSigner(DidType('default').pk);
|
|
12
|
+
function hashData({ appId, timestamp, data, userDid, }) {
|
|
13
|
+
const hasher = getHasher(DidType('default').hash);
|
|
14
|
+
return hasher(stringify({ appId, timestamp, data: data || {}, userDid }), 1);
|
|
15
|
+
}
|
|
16
|
+
export function appIdFromPublicKey(publicKey) {
|
|
17
|
+
return fromPublicKey(publicKey, DidType({ role: types.RoleType.ROLE_APPLICATION, pk: types.KeyType.ED25519, hash: types.HashType.SHA3 }));
|
|
18
|
+
}
|
|
19
|
+
export function verifyRemoteComponentCall({ appId, timestamp, data, sig, pk, userDid, expiresIn = TOKEN_EXPIRES_IN_SECONDS, }) {
|
|
20
|
+
if (Math.abs(Date.now() / 1000 - timestamp) > expiresIn)
|
|
21
|
+
throw new Error('signature expired');
|
|
22
|
+
return signer.verify(hashData({ appId, timestamp, data, userDid }), sig, pk);
|
|
23
|
+
}
|
|
24
|
+
export function signRemoteComponentCall({ data, userDid }) {
|
|
25
|
+
const appId = wallet.address;
|
|
26
|
+
const timestamp = Math.round(Date.now() / 1000);
|
|
27
|
+
return {
|
|
28
|
+
appId,
|
|
29
|
+
timestamp,
|
|
30
|
+
userDid,
|
|
31
|
+
sig: signer.sign(hashData({ appId, timestamp, data, userDid }), wallet.secretKey),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function getRemoteComponentCallHeaders(data, userDid) {
|
|
35
|
+
const { appId, timestamp, sig } = signRemoteComponentCall({ data, userDid });
|
|
36
|
+
return {
|
|
37
|
+
'x-app-id': appId,
|
|
38
|
+
'x-timestamp': timestamp.toString(),
|
|
39
|
+
'x-component-sig': sig,
|
|
40
|
+
'x-app-user-did': userDid || '',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function ensureRemoteComponentCall(getPublicKey, fallback) {
|
|
44
|
+
return async (req, res, next) => {
|
|
45
|
+
try {
|
|
46
|
+
const sig = req.get('x-component-sig');
|
|
47
|
+
const appId = req.get('x-app-id');
|
|
48
|
+
const timestamp = req.get('x-timestamp');
|
|
49
|
+
const userDid = req.get('x-app-user-did'); // Get user did
|
|
50
|
+
if (!sig || !appId || !timestamp) {
|
|
51
|
+
throw new Error('Missing required headers x-component-sig/x-app-id/x-timestamp');
|
|
52
|
+
}
|
|
53
|
+
const pk = await getPublicKey(appId);
|
|
54
|
+
if (appIdFromPublicKey(pk) !== appId)
|
|
55
|
+
throw new Error('appId and public key not match');
|
|
56
|
+
if (!verifyRemoteComponentCall({
|
|
57
|
+
appId,
|
|
58
|
+
sig,
|
|
59
|
+
timestamp: parseInt(timestamp, 10),
|
|
60
|
+
data: req.body,
|
|
61
|
+
pk,
|
|
62
|
+
userDid,
|
|
63
|
+
})) {
|
|
64
|
+
throw new Error('Validate signature error');
|
|
65
|
+
}
|
|
66
|
+
req.appClient = {
|
|
67
|
+
appId,
|
|
68
|
+
userDid,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!fallback)
|
|
73
|
+
throw error;
|
|
74
|
+
fallback(req, res, next);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
next();
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ReadableStream, TextDecoderStream, TransformStream } from 'stream/web';
|
|
2
|
+
import { createParser } from 'eventsource-parser';
|
|
3
|
+
import logger from '../../libs/logger';
|
|
4
|
+
export function readableToWeb(readable) {
|
|
5
|
+
return new ReadableStream({
|
|
6
|
+
async start(controller) {
|
|
7
|
+
for await (const chunk of readable) {
|
|
8
|
+
controller.enqueue(chunk);
|
|
9
|
+
}
|
|
10
|
+
controller.close();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export class EventSourceParserStream extends TransformStream {
|
|
15
|
+
constructor() {
|
|
16
|
+
let parser;
|
|
17
|
+
super({
|
|
18
|
+
start(controller) {
|
|
19
|
+
parser = createParser((event) => {
|
|
20
|
+
if (event.type === 'event') {
|
|
21
|
+
try {
|
|
22
|
+
const json = JSON.parse(event.data);
|
|
23
|
+
controller.enqueue(json);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
logger.error('parse chunk error', { error, data: event.data });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
transform(chunk) {
|
|
32
|
+
parser === null || parser === void 0 ? void 0 : parser.feed(chunk);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function tryParseJsonFromResponseStream(data) {
|
|
38
|
+
let text = '';
|
|
39
|
+
let json;
|
|
40
|
+
try {
|
|
41
|
+
for await (const chunk of readableToWeb(data).pipeThrough(new TextDecoderStream())) {
|
|
42
|
+
text += chunk;
|
|
43
|
+
}
|
|
44
|
+
json = JSON.parse(text);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger.error('parse json from response error', { text, error });
|
|
48
|
+
}
|
|
49
|
+
return json;
|
|
50
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar, Box, CircularProgress } from '@mui/material';
|
|
3
|
+
import isNil from 'lodash/isNil';
|
|
4
|
+
import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
|
5
|
+
import ImagePreview from '../image-preview';
|
|
6
|
+
import SubscribeErrorAlert from '../subscribe/alert';
|
|
7
|
+
import Message from './message';
|
|
8
|
+
import Prompt from './prompt';
|
|
9
|
+
export default function Conversation({ ref, messages, onSubmit, customActions = () => [], renderAvatar = undefined, maxWidth = 1000, scrollContainer = undefined, promptProps = {}, ...props }) {
|
|
10
|
+
const scroller = useRef(scrollContainer !== null && scrollContainer !== void 0 ? scrollContainer : null);
|
|
11
|
+
const { element, scrollToBottom } = useAutoScrollToBottom({ scroller });
|
|
12
|
+
useImperativeHandle(ref, () => ({
|
|
13
|
+
scrollToBottom,
|
|
14
|
+
}), [scrollToBottom]);
|
|
15
|
+
return (_jsx(Box, { ...props, ref: scrollContainer ? undefined : scroller, sx: {
|
|
16
|
+
flexGrow: 1,
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
overflow: 'auto',
|
|
20
|
+
...props.sx,
|
|
21
|
+
}, children: _jsxs(Box, { sx: { mt: 2, mx: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }, children: [_jsxs(Box, { sx: { flexGrow: 1, width: '100%', mx: 'auto', maxWidth }, children: [messages.map((msg) => {
|
|
22
|
+
var _a, _b;
|
|
23
|
+
const actions = customActions === null || customActions === void 0 ? void 0 : customActions(msg);
|
|
24
|
+
return (_jsxs(Box, { id: `conversation-${msg.id}`, children: [!isNil(msg.prompt) && (_jsx(Message, { avatar: (_a = renderAvatar === null || renderAvatar === void 0 ? void 0 : renderAvatar(msg, false)) !== null && _a !== void 0 ? _a : _jsx(Avatar, { sx: { bgcolor: 'secondary.main' }, children: "\uD83E\uDDD1" }), message: msg.prompt, actions: actions === null || actions === void 0 ? void 0 : actions[0] })), (!isNil(msg.response) || !isNil(msg.loading) || !isNil(msg.error)) && (_jsxs(Message, { my: 1, id: `response-${msg.id}`, loading: msg.loading && !!msg.response, message: typeof msg.response === 'string' ? msg.response : undefined, avatar: (_b = renderAvatar === null || renderAvatar === void 0 ? void 0 : renderAvatar(msg, true)) !== null && _b !== void 0 ? _b : _jsx(Avatar, { sx: { bgcolor: 'primary.main' }, children: "\uD83E\uDD16\uFE0F" }), actions: actions === null || actions === void 0 ? void 0 : actions[1], children: [Array.isArray(msg.response) && (_jsx(ImagePreview, { itemWidth: 100, dataSource: msg.response.map(({ url }) => {
|
|
25
|
+
return {
|
|
26
|
+
src: url,
|
|
27
|
+
onLoad: () => scrollToBottom(),
|
|
28
|
+
};
|
|
29
|
+
}) })), msg.error ? (_jsx(SubscribeErrorAlert, { error: msg.error })) : (msg.loading &&
|
|
30
|
+
!msg.response && (_jsx(Box, { sx: {
|
|
31
|
+
minHeight: 24,
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
}, children: _jsx(CircularProgress, { size: 16 }) })))] }))] }, msg.id));
|
|
35
|
+
}), element] }), _jsxs(Box, { sx: { mx: 'auto', width: '100%', maxWidth, position: 'sticky', bottom: 0 }, children: [_jsx(Box, { sx: {
|
|
36
|
+
height: 16,
|
|
37
|
+
pointerEvents: 'none',
|
|
38
|
+
background: (theme) => `linear-gradient(transparent, ${theme.palette.background.paper})`,
|
|
39
|
+
} }), _jsx(Box, { sx: {
|
|
40
|
+
pb: 2,
|
|
41
|
+
bgcolor: 'background.paper',
|
|
42
|
+
}, children: _jsx(Prompt, { onSubmit: onSubmit, ...promptProps }) })] })] }) }));
|
|
43
|
+
}
|
|
44
|
+
const STICKY_SCROLL_BOTTOM_GAP = 5;
|
|
45
|
+
const useAutoScrollToBottom = ({ scroller }) => {
|
|
46
|
+
const element = useRef(null);
|
|
47
|
+
const enableAutoScrollBottom = useRef(true);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const e = scroller.current;
|
|
50
|
+
if (!e) {
|
|
51
|
+
return () => { };
|
|
52
|
+
}
|
|
53
|
+
const listener = () => {
|
|
54
|
+
enableAutoScrollBottom.current = e.clientHeight + e.scrollTop >= e.scrollHeight - STICKY_SCROLL_BOTTOM_GAP;
|
|
55
|
+
};
|
|
56
|
+
e.addEventListener('scroll', listener);
|
|
57
|
+
return () => e.removeEventListener('scroll', listener);
|
|
58
|
+
}, [scroller]);
|
|
59
|
+
const scrollToBottom = useCallback(({ force } = {}) => {
|
|
60
|
+
if (force || enableAutoScrollBottom.current) {
|
|
61
|
+
setTimeout(() => { var _a, _b; return (_b = (_a = element.current) === null || _a === void 0 ? void 0 : _a.scrollIntoViewIfNeeded) === null || _b === void 0 ? void 0 : _b.call(_a); });
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
return { element: _jsx("div", { ref: element }), scrollToBottom };
|
|
65
|
+
};
|