mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-12 13:50:59 +00:00
Merge branch 'master' into feat/maas-support
This commit is contained in:
@@ -23,12 +23,21 @@ xml_template = """
|
|||||||
|
|
||||||
|
|
||||||
class OAClient:
|
class OAClient:
|
||||||
def __init__(self, token: str, EncodingAESKey: str, AppID: str, Appsecret: str, logger: None, unified_mode: bool = False):
|
def __init__(
|
||||||
|
self,
|
||||||
|
token: str,
|
||||||
|
EncodingAESKey: str,
|
||||||
|
AppID: str,
|
||||||
|
Appsecret: str,
|
||||||
|
logger: None,
|
||||||
|
unified_mode: bool = False,
|
||||||
|
api_base_url: str = 'https://api.weixin.qq.com',
|
||||||
|
):
|
||||||
self.token = token
|
self.token = token
|
||||||
self.aes = EncodingAESKey
|
self.aes = EncodingAESKey
|
||||||
self.appid = AppID
|
self.appid = AppID
|
||||||
self.appsecret = Appsecret
|
self.appsecret = Appsecret
|
||||||
self.base_url = 'https://api.weixin.qq.com'
|
self.base_url = api_base_url
|
||||||
self.access_token = ''
|
self.access_token = ''
|
||||||
self.unified_mode = unified_mode
|
self.unified_mode = unified_mode
|
||||||
self.app = Quart(__name__)
|
self.app = Quart(__name__)
|
||||||
@@ -208,12 +217,13 @@ class OAClientForLongerResponse:
|
|||||||
LoadingMessage: str,
|
LoadingMessage: str,
|
||||||
logger: None,
|
logger: None,
|
||||||
unified_mode: bool = False,
|
unified_mode: bool = False,
|
||||||
|
api_base_url: str = 'https://api.weixin.qq.com',
|
||||||
):
|
):
|
||||||
self.token = token
|
self.token = token
|
||||||
self.aes = EncodingAESKey
|
self.aes = EncodingAESKey
|
||||||
self.appid = AppID
|
self.appid = AppID
|
||||||
self.appsecret = Appsecret
|
self.appsecret = Appsecret
|
||||||
self.base_url = 'https://api.weixin.qq.com'
|
self.base_url = api_base_url
|
||||||
self.access_token = ''
|
self.access_token = ''
|
||||||
self.unified_mode = unified_mode
|
self.unified_mode = unified_mode
|
||||||
self.app = Quart(__name__)
|
self.app = Quart(__name__)
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ class WecomClient:
|
|||||||
contacts_secret: str,
|
contacts_secret: str,
|
||||||
logger: None,
|
logger: None,
|
||||||
unified_mode: bool = False,
|
unified_mode: bool = False,
|
||||||
|
api_base_url: str = 'https://qyapi.weixin.qq.com/cgi-bin',
|
||||||
):
|
):
|
||||||
self.corpid = corpid
|
self.corpid = corpid
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.access_token_for_contacts = ''
|
self.access_token_for_contacts = ''
|
||||||
self.token = token
|
self.token = token
|
||||||
self.aes = EncodingAESKey
|
self.aes = EncodingAESKey
|
||||||
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
|
self.base_url = api_base_url
|
||||||
self.access_token = ''
|
self.access_token = ''
|
||||||
self.secret_for_contacts = contacts_secret
|
self.secret_for_contacts = contacts_secret
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
@@ -56,7 +57,7 @@ class WecomClient:
|
|||||||
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
|
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
|
||||||
|
|
||||||
async def get_access_token(self, secret):
|
async def get_access_token(self, secret):
|
||||||
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
url = f'{self.base_url}/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url)
|
response = await client.get(url)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|||||||
@@ -13,13 +13,22 @@ import aiofiles
|
|||||||
|
|
||||||
|
|
||||||
class WecomCSClient:
|
class WecomCSClient:
|
||||||
def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str, logger: None, unified_mode: bool = False):
|
def __init__(
|
||||||
|
self,
|
||||||
|
corpid: str,
|
||||||
|
secret: str,
|
||||||
|
token: str,
|
||||||
|
EncodingAESKey: str,
|
||||||
|
logger: None,
|
||||||
|
unified_mode: bool = False,
|
||||||
|
api_base_url: str = 'https://qyapi.weixin.qq.com/cgi-bin',
|
||||||
|
):
|
||||||
self.corpid = corpid
|
self.corpid = corpid
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.access_token_for_contacts = ''
|
self.access_token_for_contacts = ''
|
||||||
self.token = token
|
self.token = token
|
||||||
self.aes = EncodingAESKey
|
self.aes = EncodingAESKey
|
||||||
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
|
self.base_url = api_base_url
|
||||||
self.access_token = ''
|
self.access_token = ''
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.unified_mode = unified_mode
|
self.unified_mode = unified_mode
|
||||||
@@ -66,7 +75,7 @@ class WecomCSClient:
|
|||||||
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
|
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
|
||||||
|
|
||||||
async def get_access_token(self, secret):
|
async def get_access_token(self, secret):
|
||||||
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
url = f'{self.base_url}/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url)
|
response = await client.get(url)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
@@ -172,7 +181,7 @@ class WecomCSClient:
|
|||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
|
|
||||||
url = f'https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token={self.access_token}'
|
url = f'{self.base_url}/kf/send_msg?access_token={self.access_token}'
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'touser': external_userid,
|
'touser': external_userid,
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAd
|
|||||||
AppID=config['AppID'],
|
AppID=config['AppID'],
|
||||||
logger=logger,
|
logger=logger,
|
||||||
unified_mode=True,
|
unified_mode=True,
|
||||||
|
api_base_url=config.get('api_base_url', 'https://api.weixin.qq.com'),
|
||||||
)
|
)
|
||||||
elif config['Mode'] == 'passive':
|
elif config['Mode'] == 'passive':
|
||||||
bot = OAClientForLongerResponse(
|
bot = OAClientForLongerResponse(
|
||||||
@@ -86,6 +87,7 @@ class OfficialAccountAdapter(abstract_platform_adapter.AbstractMessagePlatformAd
|
|||||||
LoadingMessage=config.get('LoadingMessage', ''),
|
LoadingMessage=config.get('LoadingMessage', ''),
|
||||||
logger=logger,
|
logger=logger,
|
||||||
unified_mode=True,
|
unified_mode=True,
|
||||||
|
api_base_url=config.get('api_base_url', 'https://api.weixin.qq.com'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise KeyError('请设置微信公众号通信模式')
|
raise KeyError('请设置微信公众号通信模式')
|
||||||
|
|||||||
@@ -53,6 +53,16 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
default: "AI正在思考中,请发送任意内容获取回复。"
|
default: "AI正在思考中,请发送任意内容获取回复。"
|
||||||
|
- name: api_base_url
|
||||||
|
label:
|
||||||
|
en_US: API Base URL
|
||||||
|
zh_Hans: API 基础 URL
|
||||||
|
description:
|
||||||
|
en_US: API Base URL, used for accessing the Official Account API. If you are deploying in an internal network environment and accessing the Official Account API through a reverse proxy, please fill in this item according to the documentation.
|
||||||
|
zh_Hans: 可选,若您部署在内网环境并通过反向代理访问微信公众号 API,可根据文档修改此项
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: "https://api.weixin.qq.com"
|
||||||
execution:
|
execution:
|
||||||
python:
|
python:
|
||||||
path: ./officialaccount.py
|
path: ./officialaccount.py
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ class WecomAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
contacts_secret=config['contacts_secret'],
|
contacts_secret=config['contacts_secret'],
|
||||||
logger=logger,
|
logger=logger,
|
||||||
unified_mode=True,
|
unified_mode=True,
|
||||||
|
api_base_url=config.get('api_base_url', 'https://qyapi.weixin.qq.com/cgi-bin'),
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
default: ""
|
default: ""
|
||||||
|
- name: api_base_url
|
||||||
|
label:
|
||||||
|
en_US: API Base URL
|
||||||
|
zh_Hans: API 基础 URL
|
||||||
|
description:
|
||||||
|
en_US: API Base URL, used for accessing the WeCom API. If you are deploying in an internal network environment and accessing the WeCom Customer Service API through a reverse proxy, please fill in this item according to the documentation.
|
||||||
|
zh_Hans: 可选,若您部署在内网环境并通过反向代理访问企业微信 API,可根据文档填写此项
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: "https://qyapi.weixin.qq.com/cgi-bin"
|
||||||
execution:
|
execution:
|
||||||
python:
|
python:
|
||||||
path: ./wecom.py
|
path: ./wecom.py
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ class WecomCSAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
EncodingAESKey=config['EncodingAESKey'],
|
EncodingAESKey=config['EncodingAESKey'],
|
||||||
logger=logger,
|
logger=logger,
|
||||||
unified_mode=True,
|
unified_mode=True,
|
||||||
|
api_base_url=config.get('api_base_url', 'https://qyapi.weixin.qq.com/cgi-bin'),
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|||||||
@@ -39,6 +39,16 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
default: ""
|
default: ""
|
||||||
|
- name: api_base_url
|
||||||
|
label:
|
||||||
|
en_US: API Base URL
|
||||||
|
zh_Hans: API 基础 URL
|
||||||
|
description:
|
||||||
|
en_US: API Base URL, used for accessing the WeCom API. If you are deploying in an internal network environment and accessing the WeCom Customer Service API through a reverse proxy, please fill in this item according to the documentation.
|
||||||
|
zh_Hans: 可选,若您部署在内网环境并通过反向代理访问企业微信 API,可根据文档修改此项
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: "https://qyapi.weixin.qq.com/cgi-bin"
|
||||||
execution:
|
execution:
|
||||||
python:
|
python:
|
||||||
path: ./wecomcs.py
|
path: ./wecomcs.py
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Copy, Check } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -116,6 +117,7 @@ export default function BotForm({
|
|||||||
const [, setIsLoading] = useState<boolean>(false);
|
const [, setIsLoading] = useState<boolean>(false);
|
||||||
const [webhookUrl, setWebhookUrl] = useState<string>('');
|
const [webhookUrl, setWebhookUrl] = useState<string>('');
|
||||||
const webhookInputRef = React.useRef<HTMLInputElement>(null);
|
const webhookInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
const [copied, setCopied] = useState<boolean>(false);
|
||||||
|
|
||||||
// Watch adapter and adapter_config for filtering
|
// Watch adapter and adapter_config for filtering
|
||||||
const currentAdapter = form.watch('adapter');
|
const currentAdapter = form.watch('adapter');
|
||||||
@@ -153,7 +155,6 @@ export default function BotForm({
|
|||||||
const inputElement = webhookInputRef.current;
|
const inputElement = webhookInputRef.current;
|
||||||
if (!inputElement) {
|
if (!inputElement) {
|
||||||
console.error('[Copy] Input element not found');
|
console.error('[Copy] Input element not found');
|
||||||
toast.error(t('common.copyFailed'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +179,8 @@ export default function BotForm({
|
|||||||
console.log('[Copy] Clipboard API success');
|
console.log('[Copy] Clipboard API success');
|
||||||
inputElement.blur(); // 取消选中
|
inputElement.blur(); // 取消选中
|
||||||
inputElement.readOnly = true;
|
inputElement.readOnly = true;
|
||||||
toast.success(t('bots.webhookUrlCopied'));
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -191,9 +193,8 @@ export default function BotForm({
|
|||||||
inputElement.blur();
|
inputElement.blur();
|
||||||
inputElement.readOnly = true;
|
inputElement.readOnly = true;
|
||||||
if (successful) {
|
if (successful) {
|
||||||
toast.success(t('bots.webhookUrlCopied'));
|
setCopied(true);
|
||||||
} else {
|
setTimeout(() => setCopied(false), 2000);
|
||||||
toast.error(t('common.copyFailed'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -207,15 +208,13 @@ export default function BotForm({
|
|||||||
inputElement.blur();
|
inputElement.blur();
|
||||||
inputElement.readOnly = true;
|
inputElement.readOnly = true;
|
||||||
if (successful) {
|
if (successful) {
|
||||||
toast.success(t('bots.webhookUrlCopied'));
|
setCopied(true);
|
||||||
} else {
|
setTimeout(() => setCopied(false), 2000);
|
||||||
toast.error(t('common.copyFailed'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Copy] Copy failed:', err);
|
console.error('[Copy] Copy failed:', err);
|
||||||
inputElement.readOnly = true;
|
inputElement.readOnly = true;
|
||||||
toast.error(t('common.copyFailed'));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -548,6 +547,11 @@ export default function BotForm({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
>
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-4 w-4 text-green-600 mr-2" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4 mr-2" />
|
||||||
|
)}
|
||||||
{t('common.copy')}
|
{t('common.copy')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { BotLog } from '@/app/infra/http/requestParam/bots/GetBotLogsResponse';
|
import { BotLog } from '@/app/infra/http/requestParam/bots/GetBotLogsResponse';
|
||||||
import styles from './botLog.module.css';
|
import styles from './botLog.module.css';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { PhotoProvider } from 'react-photo-view';
|
import { PhotoProvider } from 'react-photo-view';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { toast } from 'sonner';
|
import { Check } from 'lucide-react';
|
||||||
|
|
||||||
export function BotLogCard({ botLog }: { botLog: BotLog }) {
|
export function BotLogCard({ botLog }: { botLog: BotLog }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const baseURL = httpClient.getBaseUrl();
|
const baseURL = httpClient.getBaseUrl();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
function formatTime(timestamp: number) {
|
function formatTime(timestamp: number) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -75,42 +77,47 @@ export function BotLogCard({ botLog }: { botLog: BotLog }) {
|
|||||||
</div>
|
</div>
|
||||||
{botLog.message_session_id && (
|
{botLog.message_session_id && (
|
||||||
<div
|
<div
|
||||||
className={`${styles.tag} ${styles.chatTag}`}
|
className={`${styles.tag} ${styles.chatTag} relative`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(botLog.message_session_id)
|
.writeText(botLog.message_session_id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(t('common.copySuccess'));
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
title={t('common.clickToCopy')}
|
title={t('common.clickToCopy')}
|
||||||
>
|
>
|
||||||
<svg
|
{copied ? (
|
||||||
className="icon"
|
<Check className="w-4 h-4 text-green-600" />
|
||||||
viewBox="0 0 1024 1024"
|
) : (
|
||||||
version="1.1"
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
className="icon"
|
||||||
p-id="1664"
|
viewBox="0 0 1024 1024"
|
||||||
width="16"
|
version="1.1"
|
||||||
height="16"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="currentColor"
|
p-id="1664"
|
||||||
>
|
width="16"
|
||||||
<path
|
height="16"
|
||||||
d="M96.1 575.7a32.2 32.1 0 1 0 64.4 0 32.2 32.1 0 1 0-64.4 0Z"
|
|
||||||
p-id="1665"
|
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
></path>
|
>
|
||||||
<path
|
<path
|
||||||
d="M742.1 450.7l-269.5-2.1c-14.3-0.1-26 13.8-26 31s11.7 31.3 26 31.4l269.5 2.1c14.3 0.1 26-13.8 26-31s-11.7-31.3-26-31.4zM742.1 577.7l-269.5-2.1c-14.3-0.1-26 13.8-26 31s11.7 31.3 26 31.4l269.5 2.1c14.3 0.2 26-13.8 26-31s-11.7-31.3-26-31.4z"
|
d="M96.1 575.7a32.2 32.1 0 1 0 64.4 0 32.2 32.1 0 1 0-64.4 0Z"
|
||||||
p-id="1666"
|
p-id="1665"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
></path>
|
></path>
|
||||||
<path
|
<path
|
||||||
d="M736.1 63.9H417c-70.4 0-128 57.6-128 128h-64.9c-70.4 0-128 57.6-128 128v128c-0.1 17.7 14.4 32 32.2 32 17.8 0 32.2-14.4 32.2-32.1V320c0-35.2 28.8-64 64-64H289v447.8c0 70.4 57.6 128 128 128h255.1c-0.1 35.2-28.8 63.8-64 63.8H224.5c-35.2 0-64-28.8-64-64V703.5c0-17.7-14.4-32.1-32.2-32.1-17.8 0-32.3 14.4-32.3 32.1v128.3c0 70.4 57.6 128 128 128h384.1c70.4 0 128-57.6 128-128h65c70.4 0 128-57.6 128-128V255.9l-193-192z m0.1 63.4l127.7 128.3H800c-35.2 0-64-28.8-64-64v-64.3h0.2z m64 641H416.1c-35.2 0-64-28.8-64-64v-513c0-35.2 28.8-64 64-64H671V191c0 70.4 57.6 128 128 128h65.2v385.3c0 35.2-28.8 64-64 64z"
|
d="M742.1 450.7l-269.5-2.1c-14.3-0.1-26 13.8-26 31s11.7 31.3 26 31.4l269.5 2.1c14.3 0.1 26-13.8 26-31s-11.7-31.3-26-31.4zM742.1 577.7l-269.5-2.1c-14.3-0.1-26 13.8-26 31s11.7 31.3 26 31.4l269.5 2.1c14.3 0.2 26-13.8 26-31s-11.7-31.3-26-31.4z"
|
||||||
p-id="1667"
|
p-id="1666"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
<path
|
||||||
|
d="M736.1 63.9H417c-70.4 0-128 57.6-128 128h-64.9c-70.4 0-128 57.6-128 128v128c-0.1 17.7 14.4 32 32.2 32 17.8 0 32.2-14.4 32.2-32.1V320c0-35.2 28.8-64 64-64H289v447.8c0 70.4 57.6 128 128 128h255.1c-0.1 35.2-28.8 63.8-64 63.8H224.5c-35.2 0-64-28.8-64-64V703.5c0-17.7-14.4-32.1-32.2-32.1-17.8 0-32.3 14.4-32.3 32.1v128.3c0 70.4 57.6 128 128 128h384.1c70.4 0 128-57.6 128-128h65c70.4 0 128-57.6 128-128V255.9l-193-192z m0.1 63.4l127.7 128.3H800c-35.2 0-64-28.8-64-64v-64.3h0.2z m64 641H416.1c-35.2 0-64-28.8-64-64v-513c0-35.2 28.8-64 64-64H671V191c0 70.4 57.6 128 128 128h65.2v385.3c0 35.2-28.8 64-64 64z"
|
||||||
|
p-id="1667"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
<span className={`${styles.chatId}`}>
|
<span className={`${styles.chatId}`}>
|
||||||
{getSubChatId(botLog.message_session_id)}
|
{getSubChatId(botLog.message_session_id)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Copy, Trash2, Plus } from 'lucide-react';
|
import { Copy, Check, Trash2, Plus } from 'lucide-react';
|
||||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -87,6 +87,7 @@ export default function ApiIntegrationDialog({
|
|||||||
const [newWebhookDescription, setNewWebhookDescription] = useState('');
|
const [newWebhookDescription, setNewWebhookDescription] = useState('');
|
||||||
const [newWebhookEnabled, setNewWebhookEnabled] = useState(true);
|
const [newWebhookEnabled, setNewWebhookEnabled] = useState(true);
|
||||||
const [deleteWebhookId, setDeleteWebhookId] = useState<number | null>(null);
|
const [deleteWebhookId, setDeleteWebhookId] = useState<number | null>(null);
|
||||||
|
const [copiedKey, setCopiedKey] = useState<string | null>(null);
|
||||||
|
|
||||||
// Sync URL with dialog state
|
// Sync URL with dialog state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -182,7 +183,8 @@ export default function ApiIntegrationDialog({
|
|||||||
|
|
||||||
const handleCopyKey = (key: string) => {
|
const handleCopyKey = (key: string) => {
|
||||||
navigator.clipboard.writeText(key);
|
navigator.clipboard.writeText(key);
|
||||||
toast.success(t('common.apiKeyCopied'));
|
setCopiedKey(key);
|
||||||
|
setTimeout(() => setCopiedKey(null), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const maskApiKey = (key: string) => {
|
const maskApiKey = (key: string) => {
|
||||||
@@ -352,7 +354,11 @@ export default function ApiIntegrationDialog({
|
|||||||
onClick={() => handleCopyKey(key.key)}
|
onClick={() => handleCopyKey(key.key)}
|
||||||
title={t('common.copyApiKey')}
|
title={t('common.copyApiKey')}
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4" />
|
{copiedKey === key.key ? (
|
||||||
|
<Check className="h-4 w-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -543,7 +549,11 @@ export default function ApiIntegrationDialog({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4" />
|
{copiedKey === createdKey?.key ? (
|
||||||
|
<Check className="h-4 w-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -640,7 +650,11 @@ export default function ApiIntegrationDialog({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4" />
|
{copiedKey === createdKey?.key ? (
|
||||||
|
<Check className="h-4 w-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
Code,
|
Code,
|
||||||
Copy,
|
Copy,
|
||||||
|
Check,
|
||||||
Bug,
|
Bug,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -118,6 +119,8 @@ export default function PluginConfigPage() {
|
|||||||
plugin_debug_key: string;
|
plugin_debug_key: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [debugPopoverOpen, setDebugPopoverOpen] = useState(false);
|
const [debugPopoverOpen, setDebugPopoverOpen] = useState(false);
|
||||||
|
const [copiedDebugUrl, setCopiedDebugUrl] = useState(false);
|
||||||
|
const [copiedDebugKey, setCopiedDebugKey] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPluginSystemStatus = async () => {
|
const fetchPluginSystemStatus = async () => {
|
||||||
@@ -398,9 +401,15 @@ export default function PluginConfigPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyDebugInfo = (text: string) => {
|
const handleCopyDebugInfo = (text: string, type: 'url' | 'key') => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
toast.success(t('plugins.copiedToClipboard'));
|
if (type === 'url') {
|
||||||
|
setCopiedDebugUrl(true);
|
||||||
|
setTimeout(() => setCopiedDebugUrl(false), 2000);
|
||||||
|
} else {
|
||||||
|
setCopiedDebugKey(true);
|
||||||
|
setTimeout(() => setCopiedDebugKey(false), 2000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPluginDisabledState = () => (
|
const renderPluginDisabledState = () => (
|
||||||
@@ -536,10 +545,14 @@ export default function PluginConfigPage() {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 shrink-0"
|
className="h-8 w-8 shrink-0"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopyDebugInfo(debugInfo?.debug_url || '')
|
handleCopyDebugInfo(debugInfo?.debug_url || '', 'url')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Copy className="w-3.5 h-3.5" />
|
{copiedDebugUrl ? (
|
||||||
|
<Check className="w-3.5 h-3.5 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -564,11 +577,16 @@ export default function PluginConfigPage() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopyDebugInfo(
|
handleCopyDebugInfo(
|
||||||
debugInfo?.plugin_debug_key || '',
|
debugInfo?.plugin_debug_key || '',
|
||||||
|
'key',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
disabled={!debugInfo?.plugin_debug_key}
|
disabled={!debugInfo?.plugin_debug_key}
|
||||||
>
|
>
|
||||||
<Copy className="w-3.5 h-3.5" />
|
{copiedDebugKey ? (
|
||||||
|
<Check className="w-3.5 h-3.5 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{!debugInfo?.plugin_debug_key && (
|
{!debugInfo?.plugin_debug_key && (
|
||||||
|
|||||||
Reference in New Issue
Block a user