121 lines
3.9 KiB
PHP
121 lines
3.9 KiB
PHP
<?php
|
||
|
||
namespace App\Controllers;
|
||
|
||
use App\Models\Config;
|
||
use App\Services\AIService;
|
||
|
||
class ChatController
|
||
{
|
||
public static function completions(): void
|
||
{
|
||
$input = json_decode(file_get_contents('php://input'), true);
|
||
|
||
$provider = $input['provider'] ?? '';
|
||
$model = $input['model'] ?? '';
|
||
$messages = $input['messages'] ?? [];
|
||
$stream = $input['stream'] ?? true;
|
||
$systemPrompt = $input['systemPrompt'] ?? '';
|
||
$thinkingMode = $input['thinkingMode'] ?? false;
|
||
|
||
if (!$provider || !$model || !$messages) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => '缺少必要参数(provider、model、messages)']);
|
||
return;
|
||
}
|
||
|
||
if (!is_array($messages) || empty($messages)) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => 'messages 必须是非空数组']);
|
||
return;
|
||
}
|
||
|
||
// 获取供应商配置
|
||
$configRow = Config::getByKey('providers');
|
||
if (!$configRow) {
|
||
http_response_code(500);
|
||
echo json_encode(['success' => false, 'message' => '未找到供应商配置']);
|
||
return;
|
||
}
|
||
|
||
$providers = json_decode($configRow['config_value'], true);
|
||
if (!is_array($providers)) {
|
||
http_response_code(500);
|
||
echo json_encode(['success' => false, 'message' => '供应商配置格式错误']);
|
||
return;
|
||
}
|
||
|
||
$providerKey = $provider;
|
||
if (!isset($providers[$providerKey])) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => '供应商不存在: ' . $providerKey]);
|
||
return;
|
||
}
|
||
|
||
$providerConfig = $providers[$providerKey];
|
||
if (empty($providerConfig['enabled'])) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => '供应商已禁用: ' . $providerKey]);
|
||
return;
|
||
}
|
||
|
||
$models = $providerConfig['models'] ?? [];
|
||
if (!empty($models) && !in_array($model, $models)) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => '模型不在供应商支持列表中: ' . $model]);
|
||
return;
|
||
}
|
||
|
||
// 非流式模式暂不支持
|
||
if (!$stream) {
|
||
http_response_code(400);
|
||
echo json_encode(['success' => false, 'message' => '当前仅支持流式响应']);
|
||
return;
|
||
}
|
||
|
||
// 设置 SSE 响应头
|
||
header('Content-Type: text/event-stream');
|
||
header('Cache-Control: no-cache');
|
||
header('Connection: keep-alive');
|
||
header('X-Accel-Buffering: no');
|
||
|
||
// 关闭输出缓冲
|
||
while (ob_get_level()) {
|
||
ob_end_flush();
|
||
}
|
||
|
||
// 使用供应商配置中的 type 字段确定实际驱动
|
||
$providerType = $providerConfig['type'] ?? 'newapi';
|
||
|
||
$options = [
|
||
'provider' => $providerConfig,
|
||
'systemPrompt' => $systemPrompt,
|
||
'thinkingMode' => (bool) $thinkingMode
|
||
];
|
||
|
||
try {
|
||
AIService::streamChat($providerType, $model, $messages, $options, function ($chunk, $type = 'content') {
|
||
if ($type === 'thinking') {
|
||
self::sendSSE(['thinking' => $chunk]);
|
||
} else {
|
||
self::sendSSE(['content' => $chunk]);
|
||
}
|
||
});
|
||
|
||
self::sendSSE('[DONE]');
|
||
} catch (\Throwable $e) {
|
||
self::sendSSE(['type' => 'error', 'message' => $e->getMessage()]);
|
||
}
|
||
}
|
||
|
||
private static function sendSSE($data): void
|
||
{
|
||
if (is_string($data)) {
|
||
echo "data: " . $data . "\n\n";
|
||
} else {
|
||
echo "data: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";
|
||
}
|
||
flush();
|
||
}
|
||
}
|