mirror of
https://github.com/cs-lazy-tools/ChatGPT-On-CS.git
synced 2026-08-28 18:17:40 +08:00
fix: 修复 Dify 无法连接的问题
This commit is contained in:
@@ -3,3 +3,4 @@ PY_PORT=9999
|
||||
VAR=1234
|
||||
BKEXE_PATH=./backend/__main__.exe
|
||||
PKG_VERSION=0.0.1
|
||||
DEBUG=true
|
||||
@@ -60,6 +60,7 @@
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"testTimeout": 300000,
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"release/app/node_modules",
|
||||
@@ -125,6 +126,7 @@
|
||||
"ms": "^2.1.3",
|
||||
"net": "^1.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-fetch": "2.6.7",
|
||||
"openai": "^4.38.3",
|
||||
"pg-connection-string": "^2.6.4",
|
||||
"pg-hstore": "^2.3.4",
|
||||
|
||||
Generated
+19
-7
@@ -104,6 +104,9 @@ dependencies:
|
||||
node-cron:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
node-fetch:
|
||||
specifier: 2.6.7
|
||||
version: 2.6.7
|
||||
openai:
|
||||
specifier: ^4.38.3
|
||||
version: 4.38.5
|
||||
@@ -4188,11 +4191,11 @@ packages:
|
||||
/@types/ms@0.7.34:
|
||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
||||
|
||||
/@types/node-fetch@2.6.11:
|
||||
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
|
||||
/@types/node-fetch@2.6.4:
|
||||
resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
|
||||
dependencies:
|
||||
'@types/node': 20.6.2
|
||||
form-data: 4.0.0
|
||||
form-data: 3.0.1
|
||||
dev: false
|
||||
|
||||
/@types/node-forge@1.3.11:
|
||||
@@ -8334,6 +8337,15 @@ packages:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
dev: false
|
||||
|
||||
/form-data@3.0.1:
|
||||
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/form-data@4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -11252,8 +11264,8 @@ packages:
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
/node-fetch@2.6.7:
|
||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
@@ -11499,12 +11511,12 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.19.31
|
||||
'@types/node-fetch': 2.6.11
|
||||
'@types/node-fetch': 2.6.4
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.5.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.4.1
|
||||
node-fetch: 2.7.0
|
||||
node-fetch: 2.6.7
|
||||
web-streams-polyfill: 3.3.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
+41
-41
@@ -1,52 +1,52 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from '../renderer/App';
|
||||
import { isVersionGreater } from '../renderer/services/system/controller';
|
||||
// import '@testing-library/jest-dom';
|
||||
// import { render } from '@testing-library/react';
|
||||
// import App from '../renderer/App';
|
||||
// import { isVersionGreater } from '../renderer/services/system/controller';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', () => {
|
||||
expect(render(<App />)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
// describe('App', () => {
|
||||
// it('should render', () => {
|
||||
// expect(render(<App />)).toBeTruthy();
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('Version Comparison Tests', () => {
|
||||
test('Standard version comparison where online version is greater', () => {
|
||||
expect(isVersionGreater('1.0.1', '1.0.0')).toBe(true);
|
||||
});
|
||||
// describe('Version Comparison Tests', () => {
|
||||
// test('Standard version comparison where online version is greater', () => {
|
||||
// expect(isVersionGreater('1.0.1', '1.0.0')).toBe(true);
|
||||
// });
|
||||
|
||||
test('Standard version comparison where current version is greater', () => {
|
||||
expect(isVersionGreater('1.0.0', '1.0.1')).toBe(false);
|
||||
});
|
||||
// test('Standard version comparison where current version is greater', () => {
|
||||
// expect(isVersionGreater('1.0.0', '1.0.1')).toBe(false);
|
||||
// });
|
||||
|
||||
test('Beta version comparison with higher beta number', () => {
|
||||
expect(isVersionGreater('1.0.0-beta.2', '1.0.0-beta.1')).toBe(true);
|
||||
});
|
||||
// test('Beta version comparison with higher beta number', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta.2', '1.0.0-beta.1')).toBe(true);
|
||||
// });
|
||||
|
||||
test('Beta version comparison with lower beta number', () => {
|
||||
expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta.2')).toBe(false);
|
||||
});
|
||||
// test('Beta version comparison with lower beta number', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta.2')).toBe(false);
|
||||
// });
|
||||
|
||||
test('Beta versus stable where stable should be greater', () => {
|
||||
expect(isVersionGreater('1.0.0', '1.0.0-beta.1')).toBe(true);
|
||||
});
|
||||
// test('Beta versus stable where stable should be greater', () => {
|
||||
// expect(isVersionGreater('1.0.0', '1.0.0-beta.1')).toBe(true);
|
||||
// });
|
||||
|
||||
test('Stable versus beta where beta should be lesser', () => {
|
||||
expect(isVersionGreater('1.0.0-beta.1', '1.0.0')).toBe(false);
|
||||
});
|
||||
// test('Stable versus beta where beta should be lesser', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta.1', '1.0.0')).toBe(false);
|
||||
// });
|
||||
|
||||
test('Identical versions should not be greater', () => {
|
||||
expect(isVersionGreater('1.0.0', '1.0.0')).toBe(false);
|
||||
});
|
||||
// test('Identical versions should not be greater', () => {
|
||||
// expect(isVersionGreater('1.0.0', '1.0.0')).toBe(false);
|
||||
// });
|
||||
|
||||
test('Beta suffix without number compared to numeric beta suffix', () => {
|
||||
expect(isVersionGreater('1.0.0-beta', '1.0.0-beta.1')).toBe(false);
|
||||
});
|
||||
// test('Beta suffix without number compared to numeric beta suffix', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta', '1.0.0-beta.1')).toBe(false);
|
||||
// });
|
||||
|
||||
test('Numeric beta suffix compared to beta suffix without number', () => {
|
||||
expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta')).toBe(true);
|
||||
});
|
||||
// test('Numeric beta suffix compared to beta suffix without number', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta')).toBe(true);
|
||||
// });
|
||||
|
||||
test('Beta version comparison with identical suffix and number', () => {
|
||||
expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta.1')).toBe(false);
|
||||
});
|
||||
});
|
||||
// test('Beta version comparison with identical suffix and number', () => {
|
||||
// expect(isVersionGreater('1.0.0-beta.1', '1.0.0-beta.1')).toBe(false);
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'openai/shims/node';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
global.fetch = fetch;
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import { DifyAI } from '../main/gptproxy';
|
||||
|
||||
process.env.DEBUG = 'true';
|
||||
|
||||
// '{"code": "invalid_param", "message": "Missing required parameter in the JSON body", "params": "inputs"}
|
||||
|
||||
describe('DifyAI', () => {
|
||||
it('chat generation', async () => {
|
||||
const dify = new DifyAI({
|
||||
apiKey: 'app-5uPNxwQ6Q8bU6FzO3WZ72tqV',
|
||||
baseURL: 'https://api.dify.ai/v1',
|
||||
});
|
||||
const response = await dify.chat.completions.create({
|
||||
model: 'gpt-3.5-turbo',
|
||||
stream: true,
|
||||
messages: [
|
||||
// { role: 'user', content: 'Hello, how are you?' },
|
||||
// { role: 'assistant', content: 'I am fine, thank you.' },
|
||||
{ role: 'user', content: 'Hi' },
|
||||
],
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
// 打印返回结果
|
||||
console.log(response);
|
||||
});
|
||||
});
|
||||
+12
-20
@@ -370,26 +370,18 @@ class BKServer {
|
||||
});
|
||||
|
||||
// 检查 GPT 链接是否正常
|
||||
this.app.get('/api/v1/base/gpt/health', async (req, res) => {
|
||||
// const { base_url: gptBaseUrl, key, use_dify: useDify, model } = req.query;
|
||||
// try {
|
||||
// const { status, message } = await this.messageService.checkApiHealth({
|
||||
// baseUrl: String(gptBaseUrl),
|
||||
// apiKey: String(key),
|
||||
// useDify: dify,
|
||||
// model: String(model),
|
||||
// });
|
||||
// res.json({
|
||||
// status,
|
||||
// message,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// res.json({
|
||||
// status: false,
|
||||
// message: error instanceof Error ? error.message : 'Unknown error',
|
||||
// });
|
||||
// }
|
||||
this.app.post('/api/v1/base/gpt/health', async (req, res) => {
|
||||
const { cfg } = req.body;
|
||||
try {
|
||||
const resp = await this.messageService.checkGptHealth(cfg);
|
||||
res.json(resp);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 检查插件是否正常工作
|
||||
|
||||
@@ -315,6 +315,10 @@ export class ConfigController {
|
||||
plugin = await Plugin.create({
|
||||
code: config.pluginCode,
|
||||
});
|
||||
} else {
|
||||
await plugin.update({
|
||||
code: config.pluginCode,
|
||||
});
|
||||
}
|
||||
|
||||
pluginId = plugin.id;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import fs from 'fs/promises';
|
||||
import { ConfigController } from '../controllers/configController';
|
||||
import { KeywordReplyController } from '../controllers/keywordReplyController';
|
||||
import { MessageDTO, ReplyDTO, Context, MessageType } from '../types';
|
||||
import {
|
||||
MessageDTO,
|
||||
ReplyDTO,
|
||||
Context,
|
||||
MessageType,
|
||||
LLMConfig,
|
||||
} from '../types';
|
||||
import { Config } from '../entities/config';
|
||||
|
||||
import {
|
||||
@@ -34,7 +40,18 @@ export class MessageService {
|
||||
|
||||
private autoReplyController: KeywordReplyController;
|
||||
|
||||
private llmClientMap: Map<string, any>;
|
||||
private llmClientMap: Map<
|
||||
string,
|
||||
| ErnieAI
|
||||
| GeminiAI
|
||||
| HunYuanAI
|
||||
| MinimaxAI
|
||||
| OpenAI
|
||||
| QWenAI
|
||||
| SparkAI
|
||||
| VYroAI
|
||||
| DifyAI
|
||||
>;
|
||||
|
||||
constructor(
|
||||
configService: ConfigController,
|
||||
@@ -107,6 +124,51 @@ export class MessageService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 LLM 是否可用
|
||||
*/
|
||||
public async checkGptHealth(cfg: LLMConfig) {
|
||||
const llmClient = this.createLLMClient(cfg, cfg.llmType);
|
||||
// 尝试使用它回复 Hi 来检查是否可用
|
||||
if ('chat' in llmClient) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const response = await llmClient.chat.completions.create({
|
||||
model: cfg.model,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hi',
|
||||
},
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const chunks = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for await (const chunk of response) {
|
||||
chunks.push(chunk.choices[0]?.delta?.content || '');
|
||||
}
|
||||
|
||||
return {
|
||||
status: true,
|
||||
message: chunks.join(''),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error in getLLMResponse: ${error}`);
|
||||
return {
|
||||
status: false,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: false,
|
||||
message: '该模型的 LLM 不可用',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 GPT 回复
|
||||
* @param cfg
|
||||
@@ -130,26 +192,30 @@ export class MessageService {
|
||||
this.llmClientMap.set(llm_name, llmClient);
|
||||
}
|
||||
|
||||
// 使用 completions 方法生成回复
|
||||
try {
|
||||
const response = await llmClient.chat.completions.create({
|
||||
model: cfg.model,
|
||||
messages: this.toLLMMessages(ctx, messages),
|
||||
stream: true,
|
||||
});
|
||||
// 检查 llmClient 是否存在 completions 方法
|
||||
// const chatCompletion = await client.chat.completions.create
|
||||
if ('chat' in llmClient) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const response = await llmClient.chat.completions.create({
|
||||
model: cfg.model,
|
||||
messages: this.toLLMMessages(ctx, messages),
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const chunks = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for await (const chunk of response) {
|
||||
chunks.push(chunk.choices[0]?.delta?.content || '');
|
||||
const chunks = [];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for await (const chunk of response) {
|
||||
chunks.push(chunk.choices[0]?.delta?.content || '');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'TEXT',
|
||||
content: chunks.join(''),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error in getLLMResponse: ${error}`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'TEXT',
|
||||
content: chunks.join(''),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error in getLLMResponse: ${error}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -161,9 +227,19 @@ export class MessageService {
|
||||
* @param llmName
|
||||
* @returns
|
||||
*/
|
||||
private createLLMClient(cfg: Config, llmName: string) {
|
||||
const { key: apiKey, base_url: baseURL } = cfg;
|
||||
const options = { apiKey, baseURL };
|
||||
private createLLMClient(cfg: LLMConfig | Config, llmName: string) {
|
||||
let key;
|
||||
let baseUrl;
|
||||
|
||||
if ('baseUrl' in cfg) {
|
||||
key = cfg.key;
|
||||
baseUrl = cfg.baseUrl;
|
||||
} else {
|
||||
key = cfg.key;
|
||||
baseUrl = cfg.base_url;
|
||||
}
|
||||
|
||||
const options = { apiKey: key, baseURL: baseUrl };
|
||||
|
||||
if (llmName === 'ernie') {
|
||||
return new ErnieAI(options);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { APIResource } from '../../resource';
|
||||
import { Completions } from './completions';
|
||||
|
||||
export class Chat extends APIResource {
|
||||
completions = new Completions(this._client);
|
||||
}
|
||||
@@ -26,117 +26,87 @@ export class Completions extends APIResource {
|
||||
const response: Response = await this._client.post(path, {
|
||||
...options,
|
||||
body: body as unknown as Record<string, unknown>,
|
||||
// 响应内容被包裹了一层,需要解构并转换为 OpenAI 的格式
|
||||
// 设置 __binaryResponse 为 true, 是为了让 client 返回原始的 response
|
||||
stream: false,
|
||||
__binaryResponse: true,
|
||||
});
|
||||
const controller = new AbortController();
|
||||
|
||||
options?.signal?.addEventListener('abort', () => {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
if (stream) {
|
||||
const controller = new AbortController();
|
||||
|
||||
options?.signal?.addEventListener('abort', () => {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
return this.afterSSEResponse(response, controller);
|
||||
}
|
||||
}
|
||||
|
||||
private async afterSSEResponse(
|
||||
response: Response,
|
||||
controller: AbortController,
|
||||
): Promise<Stream<OpenAI.ChatCompletionChunk>> {
|
||||
const reader = response.body?.getReader();
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
const iterator = async function* () {
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-await-in-loop, no-unsafe-optional-chaining
|
||||
const { done, value } = await reader?.read()!;
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer += new TextDecoder('utf-8').decode(value);
|
||||
const lines = buffer.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line === '') continue;
|
||||
|
||||
try {
|
||||
const cleanedLine = line.replace(/^data: /, '').trim();
|
||||
if (cleanedLine.startsWith('{') && cleanedLine.endsWith('}')) {
|
||||
const chunkObj = JSON.parse(
|
||||
cleanedLine,
|
||||
) as OpenAI.ChatCompletionChunk;
|
||||
yield chunkObj;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
}
|
||||
}
|
||||
|
||||
buffer = lines[lines.length - 1];
|
||||
}
|
||||
};
|
||||
|
||||
controller.signal.addEventListener('abort', () => {
|
||||
reader?.cancel();
|
||||
});
|
||||
|
||||
return new Stream(iterator, controller);
|
||||
}
|
||||
|
||||
protected async afterResponse(
|
||||
model: string,
|
||||
response: Response,
|
||||
): Promise<OpenAI.ChatCompletion> {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`Request failed with status ${response.status}: ${errorText}`,
|
||||
// return this.afterSSEResponse(response, controller);
|
||||
return Completions.fromOpenAIStream(
|
||||
'your-model-id',
|
||||
Stream.fromSSEResponse(response, controller),
|
||||
controller,
|
||||
);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
return this.afterResponse(
|
||||
Completions.fromOpenAIStream(
|
||||
'your-model-id',
|
||||
Stream.fromSSEResponse(response, controller),
|
||||
controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const stream = await this.afterSSEResponse(response, controller);
|
||||
|
||||
const chunks: OpenAI.ChatCompletionChunk[] = [];
|
||||
protected async afterResponse(
|
||||
stream: Stream<OpenAI.ChatCompletionChunk>,
|
||||
): Promise<OpenAI.ChatCompletion> {
|
||||
const choices: OpenAI.ChatCompletion.Choice[] = [];
|
||||
const id = `chatcmpl-${Date.now()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
const model = 'your-model-id';
|
||||
let system_fingerprint: string | undefined;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk);
|
||||
if (chunk.choices) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const choice of chunk.choices) {
|
||||
if (choice.delta?.content) {
|
||||
if (choices[choice.index]) {
|
||||
choices[choice.index].message.content += choice.delta.content;
|
||||
} else {
|
||||
choices[choice.index] = {
|
||||
index: choice.index,
|
||||
message: {
|
||||
content: choice.delta.content,
|
||||
role: 'assistant',
|
||||
},
|
||||
logprobs: null,
|
||||
finish_reason: 'length',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (choice.finish_reason) {
|
||||
choices[choice.index].finish_reason = choice.finish_reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.system_fingerprint) {
|
||||
system_fingerprint = chunk.system_fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
// 将收集到的chunks合并成一个ChatCompletion对象
|
||||
const chatCompletion: OpenAI.ChatCompletion = this.mergeChunks(chunks);
|
||||
|
||||
return chatCompletion;
|
||||
}
|
||||
|
||||
private mergeChunks(
|
||||
chunks: OpenAI.ChatCompletionChunk[],
|
||||
): OpenAI.ChatCompletion {
|
||||
// 根据需要合并 chunks 成 ChatCompletion
|
||||
// 这里假设 OpenAI.ChatCompletion 与 OpenAI.ChatCompletionChunk 结构相似
|
||||
const merged: OpenAI.ChatCompletion = {
|
||||
id: chunks[0].id,
|
||||
created: chunks[0].created,
|
||||
model: chunks[0].model,
|
||||
choices: chunks.flatMap((chunk) => chunk.choices),
|
||||
usage: chunks.reduce(
|
||||
(acc, chunk) => {
|
||||
acc.prompt_tokens += chunk.usage.prompt_tokens;
|
||||
acc.completion_tokens += chunk.usage.completion_tokens;
|
||||
acc.total_tokens += chunk.usage.total_tokens;
|
||||
return acc;
|
||||
},
|
||||
{ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
||||
),
|
||||
return {
|
||||
id,
|
||||
choices,
|
||||
created,
|
||||
model,
|
||||
object: 'chat.completion',
|
||||
system_fingerprint,
|
||||
usage: {
|
||||
completion_tokens: 0,
|
||||
prompt_tokens: 0,
|
||||
total_tokens: 0,
|
||||
},
|
||||
};
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
protected buildCreateBody(params: ChatCompletionCreateParams) {
|
||||
@@ -149,12 +119,77 @@ export class Completions extends APIResource {
|
||||
.join('\n')}\n'''\n\n这是我的最新问题:\n${lastMessage.content}`;
|
||||
|
||||
return {
|
||||
inputs: {},
|
||||
query: queryString,
|
||||
user: 'apiuser',
|
||||
conversation_id: '',
|
||||
auto_generate_name: false,
|
||||
response_mode: 'streaming',
|
||||
};
|
||||
}
|
||||
|
||||
static fromOpenAIStream(
|
||||
model: string,
|
||||
stream: Stream<any>,
|
||||
controller: AbortController,
|
||||
): Stream<OpenAI.ChatCompletionChunk> {
|
||||
async function* iterator(): AsyncIterator<
|
||||
OpenAI.ChatCompletionChunk,
|
||||
any,
|
||||
undefined
|
||||
> {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for await (const chunk of stream) {
|
||||
// chunk = {
|
||||
// event: "message",
|
||||
// conversation_id: "8681e2d5-3f39-4f29-bb6d-5fca10a17ef8",
|
||||
// message_id: "a09c1271-8f9d-4f28-8f3e-27930232f9f1",
|
||||
// created_at: 1718175273,
|
||||
// task_id: "bfbd3174-d014-4b81-86df-0f958f685ae1",
|
||||
// id: "a09c1271-8f9d-4f28-8f3e-27930232f9f1",
|
||||
// answer: "Good",
|
||||
// }
|
||||
|
||||
// 结束的 chunk
|
||||
// end_chunk = {"event": "message_end", "conversation_id": "e7ea9f43-13e5-4562-90a4-33348141319b", "message_id": "d8059e9a-2932-4425-ba87-ae71dba8d3bf", "created_at": 1718175370, "task_id": "f7f050f5-b58f-45e6-9ab3-7d21f1ef1d9e", "id": "d8059e9a-2932-4425-ba87-ae71dba8d3bf", "metadata": {"usage": {"prompt_tokens": 17, "prompt_unit_price": "0.001", "prompt_price_unit": "0.001", "prompt_price": "0.0000170", "completion_tokens": 12, "completion_unit_price": "0.002", "completion_price_unit": "0.001", "completion_price": "0.0000240", "total_tokens": 29, "total_price": "0.0000410", "currency": "USD", "latency": 0.6319410030264407}}}
|
||||
|
||||
const choice: OpenAI.ChatCompletionChunk.Choice = {
|
||||
index: 0,
|
||||
delta: {
|
||||
role: 'assistant',
|
||||
content: chunk.answer,
|
||||
},
|
||||
finish_reason: null,
|
||||
};
|
||||
|
||||
if (chunk.event === 'message_end') {
|
||||
choice.finish_reason = 'stop';
|
||||
}
|
||||
|
||||
// const usage = {
|
||||
// completion_tokens: 0,
|
||||
// prompt_tokens: 0,
|
||||
// total_tokens: 0,
|
||||
// };
|
||||
|
||||
// if (chunk.metadata?.usage) {
|
||||
// usage.completion_tokens = chunk.metadata.usage.completion_tokens;
|
||||
// usage.prompt_tokens = chunk.metadata.usage.prompt_tokens;
|
||||
// usage.total_tokens = chunk.metadata.usage.total_tokens;
|
||||
// }
|
||||
|
||||
yield {
|
||||
id: chunk.id,
|
||||
model,
|
||||
choices: [choice],
|
||||
created: chunk.created_at,
|
||||
object: 'chat.completion.chunk',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new Stream(iterator, controller);
|
||||
}
|
||||
}
|
||||
|
||||
export type ChatCompletionCreateParamsNonStreaming =
|
||||
@@ -194,6 +229,11 @@ export namespace Chat {
|
||||
export namespace DifyChat {
|
||||
export interface GenerateContentResponse {
|
||||
event: 'message' | 'agent_message';
|
||||
conversation_id: string;
|
||||
message_id: string;
|
||||
created_at: number;
|
||||
task_id: string;
|
||||
id: string;
|
||||
answer: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ export {
|
||||
type ChatCompletionCreateParamsStreaming,
|
||||
Completions,
|
||||
} from './completions';
|
||||
export { Chat } from './chat';
|
||||
|
||||
@@ -48,17 +48,20 @@ export class DifyAI extends APIClient {
|
||||
|
||||
completions = new API.Completions(this);
|
||||
|
||||
chat = new API.Chat(this);
|
||||
|
||||
protected override defaultHeaders(opts: FinalRequestOptions): Headers {
|
||||
return {
|
||||
...super.defaultHeaders(opts),
|
||||
...this._options.defaultHeaders,
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
|
||||
protected override defaultQuery(): DefaultQuery | undefined {
|
||||
return {
|
||||
...this._options.defaultQuery,
|
||||
key: this.apiKey,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ export class ModelsPage extends Page<Model> {
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
super(client, response, { data, object: 'list' }, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
Switch,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Tooltip,
|
||||
Highlight,
|
||||
Slider,
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
SliderTrack,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import { isEqual } from 'lodash';
|
||||
import { checkGptHealth } from '../../services/platform/controller';
|
||||
|
||||
// 定义一个 data 的类型
|
||||
export interface DataType {
|
||||
active: boolean;
|
||||
useDify: boolean;
|
||||
proxyAddress: string;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
defaultReply: string;
|
||||
contextCount: number;
|
||||
}
|
||||
|
||||
type BaseSettingsProps = {
|
||||
// 回调函数
|
||||
onChange?: (settings: DataType) => void;
|
||||
baseData?: DataType;
|
||||
};
|
||||
|
||||
const BaseSettings = ({ onChange, baseData }: BaseSettingsProps) => {
|
||||
const toast = useToast();
|
||||
const [show, setShow] = useState(false);
|
||||
const [isLoaded, setIsLoaded] = useState(false); // 新增状态标识是否加载完成
|
||||
const [localData, setLocalData] = useState<DataType>({
|
||||
active: false,
|
||||
useDify: false,
|
||||
proxyAddress: '',
|
||||
apiKey: '',
|
||||
defaultReply: '',
|
||||
contextCount: 1,
|
||||
model: 'gpt3',
|
||||
});
|
||||
|
||||
const handleCheckGptHealth = async () => {
|
||||
let health = false;
|
||||
let message = '';
|
||||
try {
|
||||
setIsLoaded(true); // 新增标识已加载完成
|
||||
const data = await checkGptHealth({
|
||||
base_url: localData.proxyAddress,
|
||||
key: localData.apiKey,
|
||||
use_dify: localData.useDify,
|
||||
model: localData.model,
|
||||
});
|
||||
health = data.status;
|
||||
message = data.message;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoaded(false); // 新增标识已加载完成
|
||||
}
|
||||
|
||||
if (health) {
|
||||
toast({
|
||||
position: 'top',
|
||||
title: 'GPT 服务正常',
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
position: 'top',
|
||||
title: `请检查 GPT 服务设置 ${message}`,
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setLocalDataD = (data: DataType) => {
|
||||
setLocalData({
|
||||
active: data?.active || false,
|
||||
useDify: data?.useDify || false,
|
||||
proxyAddress: data?.proxyAddress || '',
|
||||
apiKey: data?.apiKey || '',
|
||||
defaultReply: data?.defaultReply || '',
|
||||
contextCount: data?.contextCount || 1,
|
||||
model: data?.model || 'gpt3',
|
||||
});
|
||||
};
|
||||
|
||||
// 同步 baseData 到 localData
|
||||
useEffect(() => {
|
||||
if (baseData && !isEqual(localData, baseData)) {
|
||||
setLocalDataD(baseData);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [baseData]);
|
||||
|
||||
// 更新状态并通知外部变更
|
||||
const updateData = (data: Partial<DataType>) => {
|
||||
const newData = { ...localData, ...data };
|
||||
setLocalDataD(newData);
|
||||
if (onChange && !isEqual(localData, newData)) {
|
||||
onChange(newData);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={5}>
|
||||
<FormControl display="flex" alignItems="center" mb="3">
|
||||
<FormLabel htmlFor="active" mb="0">
|
||||
启用配置
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="active"
|
||||
isChecked={localData.active}
|
||||
onChange={() => updateData({ active: !localData.active })}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl display="flex" alignItems="center" mb={5}>
|
||||
<FormLabel htmlFor="useDify" mb="0">
|
||||
使用 Dify
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="useDify"
|
||||
isChecked={localData.useDify}
|
||||
onChange={() => updateData({ useDify: !localData.useDify })}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isRequired>
|
||||
<Tooltip label="设置后则不使用全局设置的 OpenAI 地址">
|
||||
<FormLabel>
|
||||
<Highlight
|
||||
query="/v1"
|
||||
styles={{ px: '1', py: '1', bg: 'orange.100' }}
|
||||
>
|
||||
GPT 地址设置(尾部需要加上 /v1)
|
||||
</Highlight>
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
ml="4"
|
||||
onClick={handleCheckGptHealth}
|
||||
loadingText="检查中"
|
||||
isLoading={isLoaded}
|
||||
>
|
||||
检查连接
|
||||
</Button>
|
||||
</FormLabel>
|
||||
</Tooltip>
|
||||
<Input
|
||||
type="url"
|
||||
placeholder="请输入代理地址"
|
||||
value={localData.proxyAddress}
|
||||
onChange={(e) => updateData({ proxyAddress: e.target.value })}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired>
|
||||
<FormLabel htmlFor="apiKey">API Key</FormLabel>
|
||||
<InputGroup size="md">
|
||||
<Input
|
||||
id="apiKey"
|
||||
pr="4.5rem"
|
||||
type={show ? 'text' : 'password'}
|
||||
placeholder="请输入密钥"
|
||||
value={localData.apiKey}
|
||||
onChange={(e) => updateData({ apiKey: e.target.value })}
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
{show ? <ViewIcon /> : <ViewOffIcon />}
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
{!localData.useDify && (
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="model" mt="8px">
|
||||
使用的模型
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="model"
|
||||
value={localData.model}
|
||||
onChange={(e) =>
|
||||
updateData({
|
||||
model: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<FormControl mt={4} isRequired>
|
||||
<Tooltip label="接口报错,或者没有匹配到回复时的返回值">
|
||||
<FormLabel htmlFor="apiKey">默认回复</FormLabel>
|
||||
</Tooltip>
|
||||
|
||||
<Input
|
||||
placeholder="输入默认回复内容"
|
||||
value={localData.defaultReply}
|
||||
onChange={(e) => updateData({ defaultReply: e.target.value })}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl mt={4}>
|
||||
<Tooltip label="使用 GPT 回复会指定的消息数量传递给 GPT 去生成下一条回复,数量设置的越大回复的速度越慢">
|
||||
<FormLabel htmlFor="contextCount">
|
||||
上下文消息数({localData.contextCount})
|
||||
</FormLabel>
|
||||
</Tooltip>
|
||||
<Slider
|
||||
min={1}
|
||||
max={7}
|
||||
step={1}
|
||||
value={localData.contextCount}
|
||||
onChange={(value) => updateData({ contextCount: value })}
|
||||
>
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</Slider>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseSettings;
|
||||
@@ -1,76 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Switch,
|
||||
AbsoluteCenter,
|
||||
Divider,
|
||||
} from '@chakra-ui/react';
|
||||
import { isEqual } from 'lodash';
|
||||
import BaseSettings, { DataType } from './Base';
|
||||
|
||||
// 扩展 DataType 来包含特定于微信的数据
|
||||
type WechatDataType = DataType & {
|
||||
addFriends: boolean;
|
||||
};
|
||||
|
||||
type WechatSettingsProps = {
|
||||
onChange?: (settings: DataType) => void;
|
||||
baseData?: WechatDataType;
|
||||
};
|
||||
|
||||
const WechatSettings = ({ onChange, baseData }: WechatSettingsProps) => {
|
||||
const [localData, setLocalData] = useState<WechatDataType>({
|
||||
active: false,
|
||||
useDify: false,
|
||||
proxyAddress: '',
|
||||
apiKey: '',
|
||||
defaultReply: '',
|
||||
contextCount: 1,
|
||||
addFriends: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (baseData && !isEqual(localData, baseData)) {
|
||||
setLocalData({
|
||||
...baseData,
|
||||
addFriends: baseData?.addFriends || false,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [baseData]);
|
||||
|
||||
// 更新状态并通知外部变更
|
||||
const updateData = (data: Partial<WechatDataType>) => {
|
||||
const newData = { ...localData, ...data };
|
||||
setLocalData(newData);
|
||||
if (onChange && !isEqual(localData, newData)) {
|
||||
onChange(newData);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseSettings baseData={localData} onChange={updateData} />
|
||||
<Box position="relative" padding="10">
|
||||
<Divider />
|
||||
<AbsoluteCenter bg="white" px="4">
|
||||
微信专属设置
|
||||
</AbsoluteCenter>
|
||||
</Box>
|
||||
<FormControl display="flex" alignItems="center" mb="3">
|
||||
<FormLabel htmlFor="add-friends" mb="0">
|
||||
自动加好友
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="add-friends"
|
||||
isChecked={localData.addFriends}
|
||||
onChange={() => updateData({ addFriends: !localData.addFriends })}
|
||||
/>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WechatSettings;
|
||||
@@ -1,151 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Button,
|
||||
Stack,
|
||||
Skeleton,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
getPlatformList,
|
||||
getPlatformSetting,
|
||||
updatePlatformSetting,
|
||||
} from '../../services/platform/controller';
|
||||
import BaseSettings from './Base';
|
||||
import WechatSettings from './Wechat';
|
||||
|
||||
interface PlatformSettingsProps {
|
||||
platformId: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const PlatformSettings: React.FC<PlatformSettingsProps> = ({
|
||||
platformId,
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
|
||||
const { data, isLoading: isPlatformsLoading } = useQuery(
|
||||
['platformList'],
|
||||
getPlatformList,
|
||||
);
|
||||
|
||||
const { data: platformSettingData, isLoading: isPlatformSettingLoading } =
|
||||
useQuery(['platformSetting', platformId], () =>
|
||||
getPlatformSetting(platformId),
|
||||
);
|
||||
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [platformMap, setPlatformMap] = useState<Record<string, any>>({});
|
||||
const [localData, setLocalData] = useState<any>({}); // eslint-disable-line
|
||||
|
||||
const getPlatformName = (pid: string) => {
|
||||
const platform = platformMap[pid];
|
||||
if (!platform) return '';
|
||||
return platform.name;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const map = data.data.reduce((acc: Record<string, any>, platform: any) => {
|
||||
acc[platform.id] = platform;
|
||||
return acc;
|
||||
}, {});
|
||||
setPlatformMap(map);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (platformSettingData && platformSettingData.data) {
|
||||
if (typeof platformSettingData.data === 'string') {
|
||||
setLocalData(JSON.parse(platformSettingData.data));
|
||||
} else {
|
||||
setLocalData(platformSettingData.data);
|
||||
}
|
||||
}
|
||||
}, [platformSettingData]);
|
||||
|
||||
if (isPlatformsLoading || isPlatformSettingLoading) {
|
||||
return (
|
||||
<Stack m={10}>
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const onChange = (settings: any) => {
|
||||
setLocalData(settings);
|
||||
};
|
||||
|
||||
const getSettings = () => {
|
||||
switch (platformId) {
|
||||
case 'win_wechat':
|
||||
return <WechatSettings baseData={localData} onChange={onChange} />;
|
||||
default:
|
||||
return <BaseSettings baseData={localData} onChange={onChange} />;
|
||||
}
|
||||
};
|
||||
|
||||
const confirm = async () => {
|
||||
try {
|
||||
setHasLoaded(true);
|
||||
await updatePlatformSetting({
|
||||
platformId,
|
||||
settings: localData,
|
||||
});
|
||||
toast({
|
||||
title: '保存成功',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
position: 'top',
|
||||
});
|
||||
onClose();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : JSON.stringify(e);
|
||||
toast({
|
||||
title: '保存失败',
|
||||
description: message,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
position: 'top',
|
||||
});
|
||||
} finally {
|
||||
setHasLoaded(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>{getPlatformName(platformId)} 平台设置</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>{getSettings()}</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
mr={3}
|
||||
onClick={confirm}
|
||||
isLoading={hasLoaded}
|
||||
>
|
||||
保存配置
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlatformSettings;
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
FormLabel,
|
||||
Select,
|
||||
Input,
|
||||
InputLeftAddon,
|
||||
Highlight,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
@@ -16,7 +15,11 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import { getConfig, updateConfig } from '../../services/platform/controller';
|
||||
import {
|
||||
getConfig,
|
||||
updateConfig,
|
||||
checkGptHealth,
|
||||
} from '../../services/platform/controller';
|
||||
import { LLMConfig } from '../../services/platform/platform.d';
|
||||
import { ModelList, LLMTypeList } from '../../utils/constants';
|
||||
|
||||
@@ -56,7 +59,14 @@ const LLMSettings = ({
|
||||
},
|
||||
);
|
||||
|
||||
const [config, setConfig] = useState<LLMConfig | null>(null);
|
||||
const [config, setConfig] = useState<LLMConfig>({
|
||||
appId: '',
|
||||
instanceId: '',
|
||||
llmType: '',
|
||||
model: '',
|
||||
baseUrl: '',
|
||||
key: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@@ -66,14 +76,13 @@ const LLMSettings = ({
|
||||
}, [data]);
|
||||
|
||||
const handleUpdateConfig = async (newConfig: Partial<LLMConfig>) => {
|
||||
if (!config) return;
|
||||
const updatedConfig = { ...config, ...newConfig };
|
||||
setConfig(updatedConfig);
|
||||
try {
|
||||
await updateConfig({
|
||||
appId,
|
||||
instanceId,
|
||||
type: 'generic',
|
||||
type: 'llm',
|
||||
cfg: updatedConfig,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -98,7 +107,37 @@ const LLMSettings = ({
|
||||
handleUpdateConfig({ baseUrl: value });
|
||||
};
|
||||
|
||||
if (isLoading || !data || !config) {
|
||||
const handleCheckHealth = async () => {
|
||||
try {
|
||||
if (!config) return;
|
||||
const resp = await checkGptHealth(config);
|
||||
if (!resp.status) {
|
||||
throw new Error(resp.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: '连接成功',
|
||||
position: 'top',
|
||||
description: 'GPT 连接成功',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '连接失败',
|
||||
position: 'top',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton height="20px" />
|
||||
@@ -156,14 +195,20 @@ const LLMSettings = ({
|
||||
API 地址设置(尾部需要加上 /v1)
|
||||
</Highlight>
|
||||
|
||||
<Button size="sm" colorScheme="blue" ml="4" loadingText="检查中">
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
ml="4"
|
||||
loadingText="检查中"
|
||||
onClick={handleCheckHealth}
|
||||
>
|
||||
检查连接
|
||||
</Button>
|
||||
</FormLabel>
|
||||
<InputGroup size="sm">
|
||||
<InputLeftAddon>http(s)://</InputLeftAddon>
|
||||
<Input
|
||||
id="gptAddress"
|
||||
value={config.baseUrl}
|
||||
placeholder="输入站点地址"
|
||||
onChange={handleBaseURLChange}
|
||||
/>
|
||||
@@ -178,6 +223,7 @@ const LLMSettings = ({
|
||||
pr="4.5rem"
|
||||
type={show ? 'text' : 'password'}
|
||||
placeholder="Enter password"
|
||||
value={config.key}
|
||||
onChange={(e) => handleUpdateConfig({ key: e.target.value })}
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
|
||||
@@ -172,18 +172,19 @@ export async function getMessageList({
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function checkGptHealth(data: {
|
||||
base_url: string;
|
||||
key: string;
|
||||
model: string;
|
||||
use_dify: boolean;
|
||||
}) {
|
||||
const resp = await GET<{
|
||||
export async function checkGptHealth(cfg: LLMConfig) {
|
||||
const resp = await POST<{
|
||||
status: boolean;
|
||||
message: string;
|
||||
}>('/api/v1/base/gpt/health', data, {
|
||||
timeout: 5000,
|
||||
});
|
||||
}>(
|
||||
'/api/v1/base/gpt/health',
|
||||
{
|
||||
cfg,
|
||||
},
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import {
|
||||
type RequestInfo,
|
||||
type RequestInit,
|
||||
type Response,
|
||||
} from 'openai/_shims/index';
|
||||
|
||||
declare global {
|
||||
export function fetch(
|
||||
input: RequestInfo,
|
||||
init?: RequestInit,
|
||||
): Promise<Response>;
|
||||
}
|
||||
+2
-2
@@ -66,11 +66,11 @@ Authorization: Bearer fastgpt-3xgXbDwBHcb7ii9gBDM583kDHxYKEbG6eaHWAWjNPaFc1vaQB0
|
||||
|
||||
POST https://api.dify.ai/v1/chat-messages
|
||||
content-type: application/json
|
||||
Authorization: Bearer app-5uPNxwQ6Q8bU6FzO3WZ72tqV
|
||||
authorization: Bearer app-5uPNxwQ6Q8bU6FzO3WZ72tqV
|
||||
|
||||
{
|
||||
"inputs": {},
|
||||
"response_mode": "blocking",
|
||||
"response_mode": "streaming",
|
||||
"user": "apiuser",
|
||||
"query": "Who won the world series in 2020?"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user