前几天看到一个AI工具觉得挺有意思了,琢磨了下也弄了个大概的样子
就是一次提问多个AI回答,APIKEY用的是第三方的,找个服务器部署下直接就可以用了,页面
目前集成了:deepseek,通义千问,文心一言,腾讯混元大模型
使用的是JQuery,php,html5,css3实现,直接在各大AI平台申请密钥就可以使用了
目录结构:
贴上源码:
api:
get_config.php
<?php
header('Content-Type: application/json');
// 获取配置
$config = include '../config.php';
// 只返回前端需要的配置
echo json_encode([
'ty_enabled' => $config['ty_enabled'],
'tx_enabled' => $config['tx_enabled'],
'dp_enabled' => $config['dp_enabled'],
'wx_enabled' => $config['wx_enabled'],
'ty_api_url' => $config['ty_api_url'],
'tx_api_url' => $config['tx_api_url'],
'dp_api_url' => $config['dp_api_url'],
'wx_api_url' => $config['wx_api_url'],
'ty_model' => $config['ty_model'],
'dp_model' => $config['dp_model']
]);
send_to_ai.php
<?php
header('Content-Type: application/json');
// 启用错误日志
ini_set('display_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', '../error.log');
// 检查请求方法
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'error' => '请使用POST方法']);
exit;
}
// 获取用户消息
$userMessage = isset($_POST['message']) ? $_POST['message'] : '';
if (empty($userMessage)) {
echo json_encode(['success' => false, 'error' => '消息不能为空']);
exit;
}
// 获取配置
$config = include '../config.php';
// 初始化回复数组
$replies = [
'ty' => null,
'tx' => null,
'dp' => null,
'wx' => null
];
// 处理通义千问请求
if ($config['ty_enabled'] === '1' && !empty($config['ty_api_key'])) {
$replies['ty'] = callTongyi($userMessage, $config);
}
// 处理腾讯元宝请求
if ($config['tx_enabled'] === '1' && !empty($config['tx_api_key'])) {
$replies['tx'] = callTencent($userMessage, $config);
}
// 处理DeepSeek请求
if ($config['dp_enabled'] === '1' && !empty($config['dp_api_key'])) {
$replies['dp'] = callDeepSeek($userMessage, $config);
}
// 处理文心一言请求
if ($config['wx_enabled'] === '1' && !empty($config['wx_api_key']) && !empty($config['wx_secret_key'])) {
$replies['wx'] = callWenxin($userMessage, $config);
}
// 返回结果
echo json_encode([
'success' => true,
'replies' => $replies
]);
// 通义千问API调用
// 通义千问API调用
function callTongyi($message, $config) {
try {
$url = $config['ty_api_url'];
$apiKey = $config['ty_api_key'];
$model = $config['ty_model'];
$data = [
'model' => $model,
'messages' => [
[
'role' => 'system',
'content' => '你是一个有帮助的助手。'
],
[
'role' => 'user',
'content' => $message
]
]
];
// 记录请求
error_log("通义千问请求: " . json_encode($data));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 记录响应
error_log("通义千问响应: " . $response);
if ($error) {
error_log("通义千问CURL错误: " . $error);
return '通义千问请求错误: ' . $error;
}
if ($httpCode === 200) {
$result = json_decode($response, true);
if (isset($result['choices'][0]['message']['content'])) {
return $result['choices'][0]['message']['content'];
} else {
error_log("通义千问响应格式错误: " . $response);
return '通义千问响应格式错误,请检查API文档';
}
} else {
// 解析错误响应
$errorData = json_decode($response, true);
$errorMsg = '';
// 尝试从不同的错误格式中提取错误信息
if (isset($errorData['error']['message'])) {
$errorMsg = $errorData['error']['message'];
} elseif (isset($errorData['message'])) {
$errorMsg = $errorData['message'];
} elseif (isset($errorData['error'])) {
$errorMsg = is_string($errorData['error']) ? $errorData['error'] : json_encode($errorData['error']);
}
// 针对400错误提供更具体的提示
if ($httpCode === 400) {
$errorMsg .= ". 这可能是因为请求参数格式不正确,请检查API密钥和请求格式。";
}
$errorMessage = "通义千问HTTP错误: " . $httpCode . ($errorMsg ? ", 错误信息: " . $errorMsg : "");
error_log($errorMessage);
return $errorMessage;
}
} catch (Exception $e) {
error_log("通义千问异常: " . $e->getMessage());
return '通义千问异常: ' . $e->getMessage();
}
}
// 腾讯元宝API调用
function callTencent($message, $config) {
try {
$apiKey = $config['tx_api_key']; // 使用API密钥
$endpoint = isset($config['tx_api_url']) ? $config['tx_api_url'] : 'api.hunyuan.cloud.tencent.com/v1/chat/completions';
$model = isset($config['tx_model']) ? $config['tx_model'] : 'hunyuan-turbo'; // 默认使用hunyuan-turbo模型
// 构建请求参数 - 按照新的API格式
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $message
]
],
'enable_enhancement' => true
];
// 记录请求
error_log("腾讯元宝请求: " . json_encode($data));
// 发送请求
$ch = curl_init("https://" . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 记录响应
error_log("腾讯元宝响应: " . $response);
if ($error) {
error_log("腾讯元宝CURL错误: " . $error);
return '腾讯元宝请求错误: ' . $error;
}
if ($httpCode === 200) {
$result = json_decode($response, true);
// 详细记录响应结构,帮助调试
error_log("腾讯元宝响应结构: " . print_r($result, true));
// 检查响应中是否包含错误信息
if (isset($result['error'])) {
$errorMsg = $result['error']['message'] ?? '未知错误';
error_log("腾讯元宝API错误: " . $errorMsg);
return "腾讯元宝API错误: " . $errorMsg;
}
// 尝试获取不同的响应格式
if (isset($result['choices'][0]['message']['content'])) {
return $result['choices'][0]['message']['content'];
} elseif (isset($result['response'])) {
return $result['response'];
} elseif (isset($result['output'])) {
return $result['output'];
} else {
// 输出完整响应结构以便调试
error_log("腾讯元宝响应格式错误,完整响应: " . json_encode($result));
return '腾讯元宝响应格式错误,请检查API文档。响应结构: ' . json_encode($result);
}
} else {
// 尝试解析错误响应
$errorInfo = json_decode($response, true);
$errorMsg = '';
if (isset($errorInfo['error']['message'])) {
$errorMsg = $errorInfo['error']['message'];
}
error_log("腾讯元宝HTTP错误: " . $httpCode . ", 响应: " . $response . ", " . $errorMsg);
return "腾讯元宝HTTP错误: {$httpCode}" . ($errorMsg ? ", 错误信息: {$errorMsg}" : "");
}
} catch (Exception $e) {
error_log("腾讯元宝异常: " . $e->getMessage());
return '腾讯元宝异常: ' . $e->getMessage();
}
}
// DeepSeek API调用
function callDeepSeek($message, $config) {
try {
$url = $config['dp_api_url'];
$apiKey = $config['dp_api_key'];
$model = $config['dp_model'];
$data = [
'model' => $model,
'messages' => [
[
'role' => 'system',
'content' => '你是一个有帮助的助手。'
],
[
'role' => 'user',
'content' => $message
]
],
'stream' => false
];
// 记录请求
error_log("DeepSeek请求: " . json_encode($data));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 记录响应
error_log("DeepSeek响应: " . $response);
if ($error) {
error_log("DeepSeek CURL错误: " . $error);
return 'DeepSeek请求错误: ' . $error;
}
if ($httpCode === 200) {
$result = json_decode($response, true);
if (isset($result['choices'][0]['message']['content'])) {
return $result['choices'][0]['message']['content'];
} else {
error_log("DeepSeek响应格式错误: " . $response);
return 'DeepSeek响应格式错误,请检查API文档';
}
} else {
// 针对特定错误码提供更详细的错误信息
$errorMessage = "DeepSeek HTTP错误: " . $httpCode;
// 解析错误响应
$errorData = json_decode($response, true);
if (isset($errorData['error']['message'])) {
$errorMessage .= ", 错误信息: " . $errorData['error']['message'];
}
// 针对402错误提供更具体的提示
if ($httpCode === 402) {
$errorMessage .= ". 这可能是因为API密钥余额不足或计费问题,请检查您的DeepSeek账户余额和计费状态。";
}
error_log($errorMessage);
return $errorMessage;
}
} catch (Exception $e) {
error_log("DeepSeek异常: " . $e->getMessage());
return 'DeepSeek异常: ' . $e->getMessage();
}
}
// ... 现有代码 ...
// 文心一言API调用
function callWenxin($message, $config) {
try {
$apiKey = $config['wx_api_key'];
$secretKey = $config['wx_secret_key'];
// 记录API密钥信息(不记录完整密钥)
error_log("文心一言API密钥长度: " . strlen($apiKey) . ", 密钥是否为空: " . (empty($apiKey) ? "是" : "否"));
error_log("文心一言Secret密钥长度: " . strlen($secretKey) . ", 密钥是否为空: " . (empty($secretKey) ? "是" : "否"));
// 获取访问令牌
$tokenUrl = "https://aip.baidubce.com/oauth/2.0/token";
$tokenParams = [
'grant_type' => 'client_credentials',
'client_id' => $apiKey,
'client_secret' => $secretKey
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tokenUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($tokenParams));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
]);
$tokenResponse = curl_exec($ch);
$tokenHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$tokenError = curl_error($ch);
curl_close($ch);
// 记录令牌响应
error_log("文心一言令牌请求URL: " . $tokenUrl);
error_log("文心一言令牌请求参数: grant_type=client_credentials&client_id=[隐藏]&client_secret=[隐藏]");
error_log("文心一言令牌响应状态码: " . $tokenHttpCode);
error_log("文心一言令牌响应: " . $tokenResponse);
if ($tokenError) {
error_log("文心一言令牌CURL错误: " . $tokenError);
return '文心一言令牌请求错误: ' . $tokenError;
}
if ($tokenHttpCode !== 200) {
$tokenErrorInfo = json_decode($tokenResponse, true);
$errorMsg = isset($tokenErrorInfo['error_description']) ? $tokenErrorInfo['error_description'] : '未知错误';
error_log("文心一言令牌HTTP错误: " . $tokenHttpCode . ", 错误信息: " . $errorMsg);
return "文心一言授权失败: {$errorMsg} (HTTP {$tokenHttpCode})";
}
$tokenResult = json_decode($tokenResponse, true);
if (!isset($tokenResult['access_token'])) {
error_log("文心一言令牌响应格式错误: " . $tokenResponse);
return '文心一言授权失败,响应中没有access_token字段。';
}
$accessToken = $tokenResult['access_token'];
error_log("文心一言获取到的访问令牌: " . substr($accessToken, 0, 10) . "...");
// 使用访问令牌调用API
$url = $config['wx_api_url'] . "?access_token={$accessToken}";
$data = [
'messages' => [
[
'role' => 'user',
'content' => $message
]
],
'temperature' => 0.8,
'top_p' => 0.8
];
// 记录请求
error_log("文心一言请求URL: " . $url);
error_log("文心一言请求内容: " . json_encode($data));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// 记录响应
error_log("文心一言响应状态码: " . $httpCode);
error_log("文心一言响应: " . $response);
if ($error) {
error_log("文心一言CURL错误: " . $error);
return '文心一言请求错误: ' . $error;
}
if ($httpCode === 200) {
$result = json_decode($response, true);
if (isset($result['result'])) {
return $result['result'];
} elseif (isset($result['results'][0]['content'])) {
return $result['results'][0]['content'];
} else {
error_log("文心一言响应格式错误: " . $response);
return '文心一言响应格式错误,完整响应: ' . $response;
}
} else {
$errorData = json_decode($response, true);
$errorMsg = '';
if (isset($errorData['error_msg'])) {
$errorMsg = $errorData['error_msg'];
} elseif (isset($errorData['error_description'])) {
$errorMsg = $errorData['error_description'];
}
error_log("文心一言HTTP错误: " . $httpCode . ", 响应: " . $response);
return "文心一言HTTP错误: {$httpCode}" . ($errorMsg ? ", 错误信息: {$errorMsg}" : "");
}
} catch (Exception $e) {
error_log("文心一言异常: " . $e->getMessage());
return '文心一言异常: ' . $e->getMessage();
}
}
css
style.less(请自行编译成css代码)
// 变量定义
@primary-color: #1890ff;
@background-color: #f5f5f5;
@text-color: #333;
@border-color: #e8e8e8;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #f5222d;
// 全局样式
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background-color: @background-color;
color: @text-color;
line-height: 1.6;
}
// 聊天容器
.chat-container {
max-width: 800px;
margin: 20px auto;
background: white;
border-radius: 2px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
height: calc(100vh - 40px);
}
// 聊天头部
.chat-header {
padding: 15px;
border-bottom: 1px solid @border-color;
display: flex;
justify-content: space-between;
align-items: center;
h1 {
font-size: 18px;
color: @primary-color;
margin: 0;
}
.header-actions {
a {
color: @primary-color;
text-decoration: none;
margin-left: 15px;
font-size: 14px;
&:hover {
text-decoration: underline;
}
}
}
}
// 聊天消息区域
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 15px;
background-color: #f9f9f9;
background-image: url('../images/chat-bg.png');
background-size: 100px;
}
// 消息样式
.message {
display: flex;
margin-bottom: 15px;
position: relative;
.avatar {
width: 40px;
height: 40px;
border-radius: 4px;
overflow: hidden;
margin-right: 10px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.message-content {
position: relative;
max-width: calc(100% - 50px);
padding-bottom: 25px;
.message-text {
background-color: white;
padding: 10px 15px;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
word-break: break-word;
p {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
margin: 10px 0;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 14px;
}
code {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
background-color: #f5f5f5;
padding: 2px 4px;
border-radius: 3px;
font-size: 14px;
}
}
.message-time {
font-size: 12px;
color: #999;
margin-top: 5px;
}
}
&.user-message {
flex-direction: row-reverse;
.avatar {
margin-right: 0;
margin-left: 10px;
}
.message-content {
.message-text {
background-color: @primary-color;
color: white;
code {
background-color: rgba(255, 255, 255, 0.2);
color: white;
}
}
.message-time {
text-align: right;
}
}
}
}
// 消息操作按钮样式
.message-actions {
display: none;
position: absolute;
left: 0;
min-width: 95px;
bottom: -10px;
transition: opacity 0.3s ease;
opacity: 0;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 4px;
padding: 2px 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
.message:hover .message-actions {
display: block;
opacity: 1;
}
.copy-btn, .delete-btn {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 12px;
padding: 2px 5px;
margin: 0 2px;
}
.copy-btn:hover, .delete-btn:hover {
color: @primary-color;
background-color: #f0f0f0;
border-radius: 3px;
}
// 聊天输入区域
.chat-input {
padding: 15px;
border-top: 1px solid @border-color;
display: flex;
align-items: center;
textarea {
flex: 1;
border: 1px solid @border-color;
border-radius: 4px;
padding: 10px;
resize: none;
height: 60px;
font-family: inherit;
font-size: 14px;
&:focus {
outline: none;
border-color: @primary-color;
}
}
button {
background-color: @primary-color;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
margin-left: 10px;
cursor: pointer;
height: 60px;
&:hover {
background-color: darken(@primary-color, 10%);
}
&:disabled {
background-color: #ccc;
cursor: not-allowed;
}
}
}
// AI选择器
.ai-selector {
display: flex;
padding: 10px 15px;
border-top: 1px solid @border-color;
.ai-option {
display: flex;
align-items: center;
margin-right: 15px;
cursor: pointer;
input {
margin-right: 5px;
}
img {
width: 20px;
height: 20px;
border-radius: 3px;
margin-right: 5px;
}
span {
font-size: 14px;
}
}
}
// 加载动画
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top-color: @primary-color;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
// 复制成功提示
.copy-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
&.show {
opacity: 1;
}
}
// 响应式设计
@media (max-width: 768px) {
.chat-container {
margin: 0;
height: 100vh;
border-radius: 0;
}
.message .message-content {
max-width: calc(100% - 40px);
}
.chat-input {
padding: 10px;
textarea {
height: 50px;
}
button {
padding: 10px 15px;
height: 50px;
}
}
.ai-selector {
flex-wrap: wrap;
.ai-option {
margin-bottom: 5px;
}
}
}
// 代码高亮
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #f0f0f0;
color: #444;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-section,
.hljs-link {
color: #aa0d91;
}
.hljs-string {
color: #008000;
}
.hljs-title,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #900;
}
.hljs-comment,
.hljs-quote {
color: #998;
font-style: italic;
}
.hljs-number {
color: #099;
}
.hljs-regexp,
.hljs-template-variable {
color: #009926;
}
.hljs-variable,
.hljs-template-tag {
color: #905;
}
.hljs-attr,
.hljs-attribute {
color: #0086b3;
}
.hljs-built_in {
color: #0086b3;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-meta {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
images
js
chat.js
// 初始化变量
let chatHistory = [];
let aiConfig = {};
// 页面加载完成后执行
$(document).ready(function() {
// 加载配置
loadConfig();
// 加载聊天历史
loadChatHistory();
// 绑定发送按钮点击事件
$('#sendBtn').click(sendMessage);
// 绑定输入框回车事件
$('#userInput').keypress(function(e) {
if (e.which === 13 && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// 绑定复制和删除按钮事件
$(document).on('click', '.copy-btn', function(e) {
e.stopPropagation();
const text = $(this).closest('.message-content').find('.message-text').text();
copyToClipboard(text);
});
$(document).on('click', '.delete-btn', function(e) {
e.stopPropagation();
$(this).closest('.message').remove();
saveChatHistory();
});
});
// 加载配置
function loadConfig() {
$.getJSON('api/get_config.php', function(data) {
aiConfig = data;
// 生成AI选择器
let selectorHtml = '';
if (aiConfig.ty_enabled === '1') {
selectorHtml += createAiOption('ty', '通义千问');
}
if (aiConfig.tx_enabled === '1') {
selectorHtml += createAiOption('tx', '腾讯元宝');
}
if (aiConfig.dp_enabled === '1') {
selectorHtml += createAiOption('dp', 'DeepSeek');
}
if (aiConfig.wx_enabled === '1') {
selectorHtml += createAiOption('wx', '文心一言');
}
$('#aiSelector').html(selectorHtml);
// 默认选中所有AI
$('.ai-checkbox').prop('checked', true);
});
}
// 创建AI选项
function createAiOption(type, name) {
return `
<div class="ai-option">
<input type="checkbox" id="${type}Checkbox" class="ai-checkbox" data-type="${type}">
<img src="images/${type}.png" alt="${name}">
<span>${name}</span>
</div>
`;
}
// 加载聊天历史
function loadChatHistory() {
const savedHistory = localStorage.getItem('chatHistory');
if (savedHistory) {
chatHistory = JSON.parse(savedHistory);
// 显示历史消息
chatHistory.forEach(item => {
if (item.sender === 'user') {
addMessage('user', item.text);
} else {
addMessage('ai', item.text, item.aiType);
}
});
// 滚动到底部
scrollToBottom();
}
}
// 保存聊天历史
function saveChatHistory() {
// 重新从DOM中获取聊天记录
chatHistory = [];
$('.message').each(function() {
const sender = $(this).data('sender');
const text = $(this).find('.message-text').text();
const aiType = $(this).data('ai-type') || '';
chatHistory.push({
sender: sender,
text: text,
aiType: aiType
});
});
localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
}
// 发送消息
function sendMessage() {
const userMessage = $('#userInput').val().trim();
if (userMessage === '') {
return;
}
// 添加用户消息到聊天区域
addMessage('user', userMessage);
// 清空输入框
$('#userInput').val('');
// 保存聊天历史
saveChatHistory();
// 获取选中的AI
const selectedAIs = [];
$('.ai-checkbox:checked').each(function() {
selectedAIs.push($(this).data('type'));
});
if (selectedAIs.length === 0) {
addMessage('ai', '请至少选择一个AI助手', 'system');
return;
}
// 为每个选中的AI添加思考中的消息
selectedAIs.forEach(aiType => {
const aiName = getAiName(aiType);
addThinkingMessage(aiType, aiName);
});
// 发送请求到服务器
$.ajax({
url: 'api/send_to_ai.php',
type: 'POST',
data: {
message: userMessage
},
success: function(response) {
// 移除所有思考中的消息
$('.thinking-message').remove();
if (response.success) {
// 逐个显示AI回复,每个AI之间有延迟
let delay = 0;
selectedAIs.forEach(aiType => {
if (response.replies[aiType]) {
setTimeout(function() {
addMessage('ai', response.replies[aiType], aiType);
saveChatHistory();
scrollToBottom();
}, delay);
// 每个AI回复之间间隔1秒
delay += 1000;
}
});
} else {
addMessage('ai', '发生错误: ' + response.error, 'system');
saveChatHistory();
}
},
error: function() {
// 移除所有思考中的消息
$('.thinking-message').remove();
addMessage('ai', '网络错误,请稍后再试', 'system');
saveChatHistory();
}
});
}
// 添加思考中的消息
function addThinkingMessage(aiType, aiName) {
const avatar = `images/${aiType}.png`;
const time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
const thinkingHtml = `
<div class="message thinking-message" data-ai-type="${aiType}">
<div class="avatar">
<img src="${avatar}" alt="${aiName}">
</div>
<div class="message-content">
<div class="message-text">
<div class="loading"></div>
<span>正在思考中...</span>
</div>
<div class="message-time">${aiName} · ${time}</div>
</div>
</div>
`;
$('#chatMessages').append(thinkingHtml);
scrollToBottom();
}
// 添加消息到聊天区域
function addMessage(sender, text, aiType = '') {
let messageClass = '';
let avatar = '';
let senderName = '';
if (sender === 'user') {
messageClass = 'user-message';
avatar = 'images/user.png';
senderName = '我';
} else {
messageClass = 'ai-message';
switch (aiType) {
case 'ty':
avatar = 'images/ty.png';
senderName = '通义千问';
break;
case 'tx':
avatar = 'images/tx.png';
senderName = '腾讯元宝';
break;
case 'dp':
avatar = 'images/dp.png';
senderName = 'DeepSeek';
break;
case 'wx':
avatar = 'images/wx.png';
senderName = '文心一言';
break;
default:
avatar = 'images/system.png';
senderName = '系统';
break;
}
}
const time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
const messageHtml = `
<div class="message ${messageClass}" data-sender="${sender}" data-ai-type="${aiType}">
<div class="avatar">
<img src="${avatar}" alt="${senderName}">
</div>
<div class="message-content">
<div class="message-text">${formatMessage(text)}</div>
<div class="message-time">${senderName} · ${time}</div>
<div class="message-actions">
<button class="copy-btn">复制</button>
<button class="delete-btn">删除</button>
</div>
</div>
</div>
`;
$('#chatMessages').append(messageHtml);
scrollToBottom();
}
// 格式化消息文本
function formatMessage(text) {
// 处理换行
text = text.replace(/\n/g, '<br>');
// 处理代码块
text = text.replace(/```([\s\S]*?)```/g, function(match, code) {
return '<pre><code>' + code + '</code></pre>';
});
// 处理行内代码
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
return text;
}
// 滚动到底部
function scrollToBottom() {
const chatMessages = document.getElementById('chatMessages');
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 复制到剪贴板
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
const successful = document.execCommand('copy');
showCopyToast(successful ? '复制成功' : '复制失败');
} catch (err) {
showCopyToast('复制失败');
}
document.body.removeChild(textarea);
}
// 显示复制成功提示
function showCopyToast(message) {
const toast = document.getElementById('copyToast');
toast.textContent = message;
toast.classList.add('show');
setTimeout(function() {
toast.classList.remove('show');
}, 2000);
}
// 获取AI名称
function getAiName(aiType) {
switch (aiType) {
case 'ty':
return '通义千问';
case 'tx':
return '腾讯元宝';
case 'dp':
return 'DeepSeek';
case 'wx':
return '文心一言';
default:
return '系统';
}
}
jquery-3.6.0.min.js(自行网上下载)
admin.php
<?php
// 检查是否提交了表单
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 获取当前配置
$config = include 'config.php';
// 更新配置
$config['ty_enabled'] = isset($_POST['ty_enabled']) ? '1' : '0';
$config['ty_api_key'] = $_POST['ty_api_key'];
$config['tx_enabled'] = isset($_POST['tx_enabled']) ? '1' : '0';
$config['tx_api_key'] = $_POST['tx_api_key']; // 修改为tx_api_key
$config['dp_enabled'] = isset($_POST['dp_enabled']) ? '1' : '0';
$config['dp_api_key'] = $_POST['dp_api_key'];
$config['wx_enabled'] = isset($_POST['wx_enabled']) ? '1' : '0';
$config['wx_api_key'] = $_POST['wx_api_key'];
$config['wx_secret_key'] = $_POST['wx_secret_key'];
// 保存配置
file_put_contents('config.php', '<?php return ' . var_export($config, true) . ';');
$message = '配置已成功保存!';
}
// 获取当前配置
$config = include 'config.php';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI助手配置</title>
<link rel="stylesheet" href="css/style.css">
<style>
body {
background-color: #f5f5f5;
padding: 20px;
}
.admin-container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #1890ff;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input[type="text"],
.form-group input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-check {
margin-bottom: 10px;
}
.form-check label {
margin-left: 5px;
}
.ai-section {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.ai-section h2 {
margin-top: 0;
font-size: 1.2rem;
color: #1890ff;
}
.submit-btn {
background: #1890ff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.submit-btn:hover {
background: #40a9ff;
}
.alert {
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.alert-success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
</style>
</head>
<body>
<div class="admin-container">
<h1>AI助手配置</h1>
<?php if (isset($message)): ?>
<div class="alert alert-success">
<?php echo $message; ?>
</div>
<?php endif; ?>
<form method="post">
<!-- 通义千问配置 -->
<div class="ai-section">
<h2>通义千问配置</h2>
<div class="form-check">
<input type="checkbox" id="ty_enabled" name="ty_enabled" <?php echo $config['ty_enabled'] === '1' ? 'checked' : ''; ?>>
<label for="ty_enabled">启用通义千问</label>
</div>
<div class="form-group">
<label for="ty_api_key">API密钥</label>
<input type="text" id="ty_api_key" name="ty_api_key" value="<?php echo htmlspecialchars($config['ty_api_key']); ?>">
</div>
</div>
<!-- 腾讯元宝配置 -->
<div class="ai-section">
<h2>腾讯元宝配置</h2>
<div class="form-check">
<input type="checkbox" id="tx_enabled" name="tx_enabled" <?php echo $config['tx_enabled'] === '1' ? 'checked' : ''; ?>>
<label for="tx_enabled">启用腾讯元宝</label>
</div>
<div class="form-group">
<label for="tx_api_key">API密钥</label>
<input type="text" id="tx_api_key" name="tx_api_key" value="<?php echo htmlspecialchars($config['tx_api_key'] ?? ''); ?>">
</div>
</div>
<!-- DeepSeek配置 -->
<div class="ai-section">
<h2>DeepSeek配置</h2>
<div class="form-check">
<input type="checkbox" id="dp_enabled" name="dp_enabled" <?php echo $config['dp_enabled'] === '1' ? 'checked' : ''; ?>>
<label for="dp_enabled">启用DeepSeek</label>
</div>
<div class="form-group">
<label for="dp_api_key">API密钥</label>
<input type="text" id="dp_api_key" name="dp_api_key" value="<?php echo htmlspecialchars($config['dp_api_key']); ?>">
</div>
</div>
<!-- 文心一言配置 -->
<div class="ai-section">
<h2>文心一言配置</h2>
<div class="form-check">
<input type="checkbox" id="wx_enabled" name="wx_enabled" <?php echo $config['wx_enabled'] === '1' ? 'checked' : ''; ?>>
<label for="wx_enabled">启用文心一言</label>
</div>
<div class="form-group">
<label for="wx_api_key">API密钥</label>
<input type="text" id="wx_api_key" name="wx_api_key" value="<?php echo htmlspecialchars($config['wx_api_key']); ?>">
</div>
<div class="form-group">
<label for="wx_secret_key">Secret Key</label>
<input type="password" id="wx_secret_key" name="wx_secret_key" value="<?php echo htmlspecialchars($config['wx_secret_key']); ?>">
</div>
</div>
<button type="submit" class="submit-btn">保存配置</button>
</form>
<p style="margin-top: 20px; text-align: center;">
<a href="index.html" style="color: #1890ff; text-decoration: none;">返回聊天界面</a>
</p>
</div>
</body>
</html>
config.php
<?php return array (
'ty_enabled' => '1',
'ty_api_key' => '密钥',
'ty_api_url' => 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
'ty_model' => 'qwen-plus',
'tx_enabled' => '1',
'tx_api_key' => '密钥,
'tx_api_url' => 'api.hunyuan.cloud.tencent.com/v1/chat/completions',
'tx_model' => 'hunyuan-turbo',
'tx_api_version' => '2023-09-01',
'dp_enabled' => '1',
'dp_api_key' => '密钥',
'dp_api_url' => 'https://api.deepseek.com/chat/completions',
'dp_model' => 'deepseek-chat',
'wx_enabled' => '1',
'wx_api_key' => '密钥',
'wx_secret_key' => '密钥',
'wx_api_url' => 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions',
'wx_model' => 'ernie-bot',
);
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI助手聊天工具</title>
<link rel="stylesheet" href="css/style.css">
<script src="./js/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1>AI助手聊天工具</h1>
<div class="header-actions">
<a href="admin.php">管理</a>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<!-- 聊天记录将在这里显示 -->
</div>
<div class="ai-selector" id="aiSelector">
<!-- AI选择器将在这里动态生成 -->
</div>
<div class="chat-input">
<textarea id="userInput" placeholder="请输入您的问题..."></textarea>
<button id="sendBtn">发送</button>
</div>
</div>
<!-- 复制成功提示 -->
<div class="copy-toast" id="copyToast"></div>
<script src="js/chat.js"></script>
</body>
</html>