mirror of
https://github.com/CodePhiliaX/Chat2DB.git
synced 2025-07-31 11:42:41 +08:00
feature: add dify-chat-ai type in config
This commit is contained in:
@ -14,6 +14,7 @@ const AITypeName = {
|
|||||||
[AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'),
|
[AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'),
|
||||||
[AIType.OPENAI]: 'Open AI',
|
[AIType.OPENAI]: 'Open AI',
|
||||||
[AIType.AZUREAI]: 'Azure AI',
|
[AIType.AZUREAI]: 'Azure AI',
|
||||||
|
[AIType.DIFYCHAT]: 'Dify AI',
|
||||||
[AIType.RESTAI]: i18n('setting.tab.custom'),
|
[AIType.RESTAI]: i18n('setting.tab.custom'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,6 +54,10 @@ const AIFormConfig: Record<AIType, IAiConfigBooleans> = {
|
|||||||
apiHost: true,
|
apiHost: true,
|
||||||
model: true,
|
model: true,
|
||||||
},
|
},
|
||||||
|
[AIType.DIFYCHAT]: {
|
||||||
|
apiKey: true,
|
||||||
|
apiHost: true,
|
||||||
|
},
|
||||||
[AIType.RESTAI]: {
|
[AIType.RESTAI]: {
|
||||||
apiKey: true,
|
apiKey: true,
|
||||||
apiHost: true,
|
apiHost: true,
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
export enum AIType {
|
export enum AIType {
|
||||||
CHAT2DBAI = 'CHAT2DBAI',
|
CHAT2DBAI = 'CHAT2DBAI',
|
||||||
ZHIPUAI = 'ZHIPUAI',
|
ZHIPUAI = 'ZHIPUAI',
|
||||||
BAICHUANAI='BAICHUANAI',
|
BAICHUANAI = 'BAICHUANAI',
|
||||||
WENXINAI='WENXINAI',
|
WENXINAI = 'WENXINAI',
|
||||||
// TONGYIQIANWENAI='TONGYIQIANWENAI',
|
// TONGYIQIANWENAI='TONGYIQIANWENAI',
|
||||||
OPENAI = 'OPENAI',
|
OPENAI = 'OPENAI',
|
||||||
AZUREAI = 'AZUREAI',
|
AZUREAI = 'AZUREAI',
|
||||||
RESTAI = 'RESTAI',
|
RESTAI = 'RESTAI',
|
||||||
|
DIFYCHAT = 'DIFYCHAT',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRemainingUse {
|
export interface IRemainingUse {
|
||||||
|
@ -62,6 +62,11 @@ public enum AiSqlSourceEnum implements BaseEnum<String> {
|
|||||||
*/
|
*/
|
||||||
FASTCHATAI("FAST CHAT AI"),
|
FASTCHATAI("FAST CHAT AI"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FAST CHAT AI
|
||||||
|
*/
|
||||||
|
DIFYCHAT("DIFY CHAT AI"),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
final String description;
|
final String description;
|
||||||
|
@ -363,6 +363,41 @@ public class ChatController {
|
|||||||
return sseEmitter;
|
return sseEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* chat with azure openai
|
||||||
|
*
|
||||||
|
* @param queryRequest
|
||||||
|
* @param sseEmitter
|
||||||
|
* @param uid
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private SseEmitter chatWithDifyChat(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException {
|
||||||
|
String prompt = buildPrompt(queryRequest);
|
||||||
|
if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) {
|
||||||
|
log.error("提示语超出最大长度:{},输入长度:{}, 请重新输入", MAX_PROMPT_LENGTH,
|
||||||
|
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
|
||||||
|
throw new ParamBusinessException();
|
||||||
|
}
|
||||||
|
List<AzureChatMessage> messages = (List<AzureChatMessage>)LocalCache.CACHE.get(uid);
|
||||||
|
if (CollectionUtils.isNotEmpty(messages)) {
|
||||||
|
if (messages.size() >= contextLength) {
|
||||||
|
messages = messages.subList(1, contextLength);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messages = Lists.newArrayList();
|
||||||
|
}
|
||||||
|
AzureChatMessage currentMessage = new AzureChatMessage(AzureChatRole.USER).setContent(prompt);
|
||||||
|
messages.add(currentMessage);
|
||||||
|
|
||||||
|
buildSseEmitter(sseEmitter, uid);
|
||||||
|
|
||||||
|
AzureOpenAIEventSourceListener sourceListener = new AzureOpenAIEventSourceListener(sseEmitter);
|
||||||
|
AzureOpenAIClient.getInstance().streamCompletions(messages, sourceListener);
|
||||||
|
LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT);
|
||||||
|
return sseEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* chat with fast chat openai
|
* chat with fast chat openai
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package ai.chat2db.server.web.api.controller.ai.dify.client;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DifyChatAIClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZHIPU OPENAI KEY
|
||||||
|
*/
|
||||||
|
public static final String DIFYCHAT_API_KEY = "difychat.apiKey";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZHIPU OPENAI HOST
|
||||||
|
*/
|
||||||
|
public static final String DIFYCHAT_HOST = "difychat.host";
|
||||||
|
|
||||||
|
|
||||||
|
public static void refresh() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package ai.chat2db.server.web.api.controller.ai.dify.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DifyChatMessage {
|
||||||
|
|
||||||
|
@JsonProperty(value = "query")
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
@JsonProperty(value = "inputs")
|
||||||
|
private Map<String,String> inputs;
|
||||||
|
|
||||||
|
@JsonProperty(value = "conversation_id")
|
||||||
|
private String conversation_id;
|
||||||
|
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -15,6 +15,7 @@ import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect;
|
|||||||
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient;
|
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient;
|
||||||
import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient;
|
import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient;
|
||||||
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient;
|
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient;
|
||||||
|
import ai.chat2db.server.web.api.controller.ai.dify.client.DifyChatAIClient;
|
||||||
import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient;
|
import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient;
|
||||||
import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient;
|
import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient;
|
||||||
import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient;
|
import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient;
|
||||||
@ -47,7 +48,7 @@ public class ConfigController {
|
|||||||
@PostMapping("/system_config")
|
@PostMapping("/system_config")
|
||||||
public ActionResult systemConfig(@RequestBody SystemConfigRequest request) {
|
public ActionResult systemConfig(@RequestBody SystemConfigRequest request) {
|
||||||
SystemConfigParam param = SystemConfigParam.builder().code(request.getCode()).content(request.getContent())
|
SystemConfigParam param = SystemConfigParam.builder().code(request.getCode()).content(request.getContent())
|
||||||
.build();
|
.build();
|
||||||
configService.createOrUpdate(param);
|
configService.createOrUpdate(param);
|
||||||
if (OpenAIClient.OPENAI_KEY.equals(request.getCode())) {
|
if (OpenAIClient.OPENAI_KEY.equals(request.getCode())) {
|
||||||
OpenAIClient.refresh();
|
OpenAIClient.refresh();
|
||||||
@ -72,7 +73,7 @@ public class ConfigController {
|
|||||||
aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI;
|
aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI;
|
||||||
}
|
}
|
||||||
SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource)
|
SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource)
|
||||||
.build();
|
.build();
|
||||||
configService.createOrUpdate(param);
|
configService.createOrUpdate(param);
|
||||||
|
|
||||||
switch (Objects.requireNonNull(aiSqlSourceEnum)) {
|
switch (Objects.requireNonNull(aiSqlSourceEnum)) {
|
||||||
@ -103,10 +104,23 @@ public class ConfigController {
|
|||||||
case ZHIPUAI:
|
case ZHIPUAI:
|
||||||
saveZhipuChatAIConfig(request);
|
saveZhipuChatAIConfig(request);
|
||||||
break;
|
break;
|
||||||
|
case DIFYCHAT:
|
||||||
|
saveDifyChatAIConfig(request);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return ActionResult.isSuccess();
|
return ActionResult.isSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveDifyChatAIConfig(AIConfigCreateRequest request) {
|
||||||
|
SystemConfigParam param = SystemConfigParam.builder().code(DifyChatAIClient.DIFYCHAT_API_KEY).content(
|
||||||
|
request.getApiKey()).build();
|
||||||
|
configService.createOrUpdate(param);
|
||||||
|
SystemConfigParam hostParam = SystemConfigParam.builder().code(DifyChatAIClient.DIFYCHAT_HOST).content(
|
||||||
|
request.getApiHost()).build();
|
||||||
|
configService.createOrUpdate(hostParam);
|
||||||
|
DifyChatAIClient.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save chat2db ai config
|
* save chat2db ai config
|
||||||
*
|
*
|
||||||
@ -114,10 +128,10 @@ public class ConfigController {
|
|||||||
*/
|
*/
|
||||||
private void saveChat2dbAIConfig(AIConfigCreateRequest request) {
|
private void saveChat2dbAIConfig(AIConfigCreateRequest request) {
|
||||||
SystemConfigParam param = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(
|
SystemConfigParam param = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(
|
||||||
request.getApiKey()).build();
|
request.getApiKey()).build();
|
||||||
configService.createOrUpdate(param);
|
configService.createOrUpdate(param);
|
||||||
SystemConfigParam hostParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST).content(
|
SystemConfigParam hostParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST).content(
|
||||||
request.getApiHost()).build();
|
request.getApiHost()).build();
|
||||||
configService.createOrUpdate(hostParam);
|
configService.createOrUpdate(hostParam);
|
||||||
SystemConfigParam modelParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL).content(
|
SystemConfigParam modelParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL).content(
|
||||||
request.getModel()).build();
|
request.getModel()).build();
|
||||||
@ -132,16 +146,16 @@ public class ConfigController {
|
|||||||
*/
|
*/
|
||||||
private void saveOpenAIConfig(AIConfigCreateRequest request) {
|
private void saveOpenAIConfig(AIConfigCreateRequest request) {
|
||||||
SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content(
|
SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content(
|
||||||
request.getApiKey()).build();
|
request.getApiKey()).build();
|
||||||
configService.createOrUpdate(param);
|
configService.createOrUpdate(param);
|
||||||
SystemConfigParam hostParam = SystemConfigParam.builder().code(OpenAIClient.OPENAI_HOST).content(
|
SystemConfigParam hostParam = SystemConfigParam.builder().code(OpenAIClient.OPENAI_HOST).content(
|
||||||
request.getApiHost()).build();
|
request.getApiHost()).build();
|
||||||
configService.createOrUpdate(hostParam);
|
configService.createOrUpdate(hostParam);
|
||||||
SystemConfigParam httpProxyHostParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_HOST).content(
|
SystemConfigParam httpProxyHostParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_HOST).content(
|
||||||
request.getHttpProxyHost()).build();
|
request.getHttpProxyHost()).build();
|
||||||
configService.createOrUpdate(httpProxyHostParam);
|
configService.createOrUpdate(httpProxyHostParam);
|
||||||
SystemConfigParam httpProxyPortParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_PORT).content(
|
SystemConfigParam httpProxyPortParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_PORT).content(
|
||||||
request.getHttpProxyPort()).build();
|
request.getHttpProxyPort()).build();
|
||||||
configService.createOrUpdate(httpProxyPortParam);
|
configService.createOrUpdate(httpProxyPortParam);
|
||||||
OpenAIClient.refresh();
|
OpenAIClient.refresh();
|
||||||
}
|
}
|
||||||
@ -153,10 +167,10 @@ public class ConfigController {
|
|||||||
*/
|
*/
|
||||||
private void saveRestAIConfig(AIConfigCreateRequest request) {
|
private void saveRestAIConfig(AIConfigCreateRequest request) {
|
||||||
SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content(
|
SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content(
|
||||||
request.getApiHost()).build();
|
request.getApiHost()).build();
|
||||||
configService.createOrUpdate(restParam);
|
configService.createOrUpdate(restParam);
|
||||||
SystemConfigParam methodParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_STREAM_OUT).content(
|
SystemConfigParam methodParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_STREAM_OUT).content(
|
||||||
request.getStream().toString()).build();
|
request.getStream().toString()).build();
|
||||||
configService.createOrUpdate(methodParam);
|
configService.createOrUpdate(methodParam);
|
||||||
RestAIClient.refresh();
|
RestAIClient.refresh();
|
||||||
}
|
}
|
||||||
@ -168,16 +182,16 @@ public class ConfigController {
|
|||||||
*/
|
*/
|
||||||
private void saveAzureAIConfig(AIConfigCreateRequest request) {
|
private void saveAzureAIConfig(AIConfigCreateRequest request) {
|
||||||
SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY)
|
SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY)
|
||||||
.content(
|
.content(
|
||||||
request.getApiKey()).build();
|
request.getApiKey()).build();
|
||||||
configService.createOrUpdate(apikeyParam);
|
configService.createOrUpdate(apikeyParam);
|
||||||
SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT)
|
SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT)
|
||||||
.content(
|
.content(
|
||||||
request.getApiHost()).build();
|
request.getApiHost()).build();
|
||||||
configService.createOrUpdate(endpointParam);
|
configService.createOrUpdate(endpointParam);
|
||||||
SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID)
|
SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID)
|
||||||
.content(
|
.content(
|
||||||
request.getModel()).build();
|
request.getModel()).build();
|
||||||
configService.createOrUpdate(modelParam);
|
configService.createOrUpdate(modelParam);
|
||||||
AzureOpenAIClient.refresh();
|
AzureOpenAIClient.refresh();
|
||||||
}
|
}
|
||||||
@ -308,9 +322,9 @@ public class ConfigController {
|
|||||||
config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : "");
|
config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : "");
|
||||||
config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : "");
|
config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : "");
|
||||||
config.setHttpProxyHost(
|
config.setHttpProxyHost(
|
||||||
Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : "");
|
Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : "");
|
||||||
config.setHttpProxyPort(
|
config.setHttpProxyPort(
|
||||||
Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : "");
|
Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : "");
|
||||||
break;
|
break;
|
||||||
case CHAT2DBAI:
|
case CHAT2DBAI:
|
||||||
DataResult<Config> chat2dbApiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY);
|
DataResult<Config> chat2dbApiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY);
|
||||||
@ -318,7 +332,7 @@ public class ConfigController {
|
|||||||
DataResult<Config> chat2dbModel = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL);
|
DataResult<Config> chat2dbModel = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL);
|
||||||
config.setApiKey(Objects.nonNull(chat2dbApiKey.getData()) ? chat2dbApiKey.getData().getContent() : "");
|
config.setApiKey(Objects.nonNull(chat2dbApiKey.getData()) ? chat2dbApiKey.getData().getContent() : "");
|
||||||
config.setApiHost(
|
config.setApiHost(
|
||||||
Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : "");
|
Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : "");
|
||||||
config.setModel(Objects.nonNull(chat2dbModel.getData()) ? chat2dbModel.getData().getContent() : "");
|
config.setModel(Objects.nonNull(chat2dbModel.getData()) ? chat2dbModel.getData().getContent() : "");
|
||||||
break;
|
break;
|
||||||
case AZUREAI:
|
case AZUREAI:
|
||||||
@ -334,7 +348,7 @@ public class ConfigController {
|
|||||||
DataResult<Config> restAiHttpMethod = configService.find(RestAIClient.REST_AI_STREAM_OUT);
|
DataResult<Config> restAiHttpMethod = configService.find(RestAIClient.REST_AI_STREAM_OUT);
|
||||||
config.setApiHost(Objects.nonNull(restAiUrl.getData()) ? restAiUrl.getData().getContent() : "");
|
config.setApiHost(Objects.nonNull(restAiUrl.getData()) ? restAiUrl.getData().getContent() : "");
|
||||||
config.setStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf(
|
config.setStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf(
|
||||||
restAiHttpMethod.getData().getContent()) : Boolean.TRUE);
|
restAiHttpMethod.getData().getContent()) : Boolean.TRUE);
|
||||||
break;
|
break;
|
||||||
case FASTCHATAI:
|
case FASTCHATAI:
|
||||||
DataResult<Config> fastChatApiKey = configService.find(FastChatAIClient.FASTCHAT_API_KEY);
|
DataResult<Config> fastChatApiKey = configService.find(FastChatAIClient.FASTCHAT_API_KEY);
|
||||||
@ -376,6 +390,12 @@ public class ConfigController {
|
|||||||
config.setApiHost(Objects.nonNull(zhipuApiHost.getData()) ? zhipuApiHost.getData().getContent() : "");
|
config.setApiHost(Objects.nonNull(zhipuApiHost.getData()) ? zhipuApiHost.getData().getContent() : "");
|
||||||
config.setModel(Objects.nonNull(zhipuModel.getData()) ? zhipuModel.getData().getContent() : "");
|
config.setModel(Objects.nonNull(zhipuModel.getData()) ? zhipuModel.getData().getContent() : "");
|
||||||
break;
|
break;
|
||||||
|
case DIFYCHAT:
|
||||||
|
DataResult<Config> difyChatApiKey = configService.find(DifyChatAIClient.DIFYCHAT_API_KEY);
|
||||||
|
DataResult<Config> difyChatApiHost = configService.find(DifyChatAIClient.DIFYCHAT_HOST);
|
||||||
|
config.setApiKey(Objects.nonNull(difyChatApiKey.getData()) ? difyChatApiKey.getData().getContent() : "");
|
||||||
|
config.setApiHost(Objects.nonNull(difyChatApiHost.getData()) ? difyChatApiHost.getData().getContent() : "");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user