更新
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller\api;
|
||||
|
||||
use app\model\Conversation as ConversationModel;
|
||||
use app\model\Message;
|
||||
use app\service\OpenAIService;
|
||||
use app\service\PermissionService;
|
||||
use app\service\SettingsService;
|
||||
use think\exception\HttpResponseException;
|
||||
|
||||
class Chat extends BaseApi
|
||||
{
|
||||
public function completions()
|
||||
{
|
||||
$user = $this->authUser();
|
||||
PermissionService::checkDailyLimit($user);
|
||||
|
||||
$input = $this->request->post();
|
||||
$conversationId = (int) ($input['conversation_id'] ?? 0);
|
||||
$content = trim($input['content'] ?? '');
|
||||
$attachments = $input['attachments'] ?? [];
|
||||
$stream = ($input['stream'] ?? true) !== false;
|
||||
|
||||
if (!$conversationId) {
|
||||
return $this->error('缺少 conversation_id');
|
||||
}
|
||||
if (!$content && empty($attachments)) {
|
||||
return $this->error('消息内容不能为空');
|
||||
}
|
||||
|
||||
$conversation = ConversationModel::where('id', $conversationId)
|
||||
->where('user_id', $user['id'])
|
||||
->whereNull('deleted_at')
|
||||
->find();
|
||||
|
||||
if (!$conversation) {
|
||||
return $this->error('会话不存在', 404);
|
||||
}
|
||||
|
||||
$contentType = SettingsService::isFeatureEnabled('markdown') ? 'markdown' : 'text';
|
||||
if (!empty($attachments)) {
|
||||
$contentType = 'mixed';
|
||||
}
|
||||
|
||||
Message::create([
|
||||
'conversation_id' => $conversationId,
|
||||
'role' => 'user',
|
||||
'content' => $content,
|
||||
'content_type' => $contentType,
|
||||
'attachments' => $attachments,
|
||||
]);
|
||||
|
||||
PermissionService::incrementDailyCount((int) $user['id']);
|
||||
|
||||
$history = Message::where('conversation_id', $conversationId)
|
||||
->field('role,content,attachments')
|
||||
->order('created_at', 'asc')
|
||||
->limit(50)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$apiMessages = $this->buildApiMessages($history);
|
||||
$model = OpenAIService::getModel($conversation->model_id ? (int) $conversation->model_id : null);
|
||||
|
||||
if ((int) $conversation->message_count === 0 && $content) {
|
||||
$conversation->save(['title' => mb_substr($content, 0, 30)]);
|
||||
}
|
||||
|
||||
$conversation->inc('message_count')->update(['updated_at' => date('Y-m-d H:i:s')]);
|
||||
|
||||
if ($stream) {
|
||||
$this->streamResponse($conversationId, $model, $apiMessages, $user);
|
||||
}
|
||||
|
||||
return $this->syncResponse($conversationId, $model, $apiMessages, $user);
|
||||
}
|
||||
|
||||
private function streamResponse(int $conversationId, $model, array $apiMessages, array $user): void
|
||||
{
|
||||
OpenAIService::sseHeaders();
|
||||
$fullContent = '';
|
||||
|
||||
OpenAIService::streamChat($model, $apiMessages, function ($chunk) use (&$fullContent) {
|
||||
$delta = $chunk['choices'][0]['delta']['content'] ?? '';
|
||||
if ($delta) {
|
||||
$fullContent .= $delta;
|
||||
OpenAIService::sseEvent('message', ['content' => $delta]);
|
||||
}
|
||||
});
|
||||
|
||||
Message::create([
|
||||
'conversation_id' => $conversationId,
|
||||
'role' => 'assistant',
|
||||
'content' => $fullContent,
|
||||
'content_type' => 'markdown',
|
||||
]);
|
||||
|
||||
ConversationModel::where('id', $conversationId)->inc('message_count')->update([
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
PermissionService::incrementDailyCount((int) $user['id']);
|
||||
OpenAIService::sseEvent('done', ['content' => $fullContent]);
|
||||
exit;
|
||||
}
|
||||
|
||||
private function syncResponse(int $conversationId, $model, array $apiMessages, array $user)
|
||||
{
|
||||
$result = OpenAIService::chat($model, $apiMessages);
|
||||
$content = $result['choices'][0]['message']['content'] ?? '';
|
||||
$tokens = $result['usage']['total_tokens'] ?? 0;
|
||||
|
||||
Message::create([
|
||||
'conversation_id' => $conversationId,
|
||||
'role' => 'assistant',
|
||||
'content' => $content,
|
||||
'content_type' => 'markdown',
|
||||
'tokens_used' => $tokens,
|
||||
]);
|
||||
|
||||
ConversationModel::where('id', $conversationId)->inc('message_count')->update([
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
PermissionService::incrementDailyCount((int) $user['id']);
|
||||
|
||||
return $this->success(['content' => $content, 'tokens' => $tokens]);
|
||||
}
|
||||
|
||||
private function buildApiMessages(array $history): array
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
foreach ($history as $msg) {
|
||||
$content = $msg['content'];
|
||||
$attachments = $msg['attachments'] ?? [];
|
||||
if (is_string($attachments)) {
|
||||
$attachments = json_decode($attachments, true) ?: [];
|
||||
}
|
||||
|
||||
if (!empty($attachments) && SettingsService::isFeatureEnabled('image')) {
|
||||
$parts = [];
|
||||
if ($content) {
|
||||
$parts[] = ['type' => 'text', 'text' => $content];
|
||||
}
|
||||
foreach ($attachments as $att) {
|
||||
if (($att['type'] ?? '') === 'image') {
|
||||
$parts[] = [
|
||||
'type' => 'image_url',
|
||||
'image_url' => ['url' => $this->absoluteUrl($att['url'])],
|
||||
];
|
||||
}
|
||||
}
|
||||
$messages[] = ['role' => $msg['role'], 'content' => $parts];
|
||||
} else {
|
||||
$messages[] = ['role' => $msg['role'], 'content' => $content];
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function absoluteUrl(string $url): string
|
||||
{
|
||||
if (str_starts_with($url, 'http')) {
|
||||
return $url;
|
||||
}
|
||||
$scheme = $this->request->scheme();
|
||||
$host = $this->request->host();
|
||||
return "{$scheme}://{$host}{$url}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user