mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-08 11:12:47 +08:00
feat(post): 新增手机图片排版与AI智能排版功能
- 新增手机图片排版功能,支持小红书/抖音尺寸输出 - 新增AI智能排版顾问,支持内容分析与优化推荐 - 新增AI供应商管理,支持多渠道配置与同步 - 新增文章输出管理页面,支持图片预览与批量下载 - 新增字体文件与排版样式配置
This commit is contained in:
228
app/common/tools/ai/provider/OpenAiCompatibleAdapter.php
Normal file
228
app/common/tools/ai/provider/OpenAiCompatibleAdapter.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\tools\ai\provider;
|
||||
|
||||
use app\common\tools\ai\AiProviderInterface;
|
||||
|
||||
/**
|
||||
* OpenAI 兼容适配器.
|
||||
*
|
||||
* 适用于所有兼容 OpenAI API 格式的供应商:
|
||||
* OpenAI / Zhipu / DeepSeek / Moonshot / Qwen / etc.
|
||||
*/
|
||||
class OpenAiCompatibleAdapter implements AiProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var string 供应商标识(如 zhipu, deepseek)
|
||||
*/
|
||||
protected $providerId;
|
||||
|
||||
/**
|
||||
* @var string 供应商显示名称
|
||||
*/
|
||||
protected $providerName;
|
||||
|
||||
/**
|
||||
* @param string $providerId 供应商标识
|
||||
* @param string $providerName 显示名称(可选)
|
||||
*/
|
||||
public function __construct(string $providerId, string $providerName = '')
|
||||
{
|
||||
$this->providerId = $providerId;
|
||||
$this->providerName = $providerName ?: ucfirst($providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chat(string $systemPrompt, string $userPrompt, array $options = []): string
|
||||
{
|
||||
$apiKey = get_system_config("ai_provider_{$this->providerId}_key", '');
|
||||
$baseUrl = get_system_config("ai_provider_{$this->providerId}_base_url", 'https://api.openai.com/v1');
|
||||
$model = $options['model'] ?? get_system_config('ai_default_model', 'gpt-3.5-turbo');
|
||||
|
||||
$url = rtrim($baseUrl, '/') . '/chat/completions';
|
||||
$data = [
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => $systemPrompt],
|
||||
['role' => 'user', 'content' => $userPrompt],
|
||||
],
|
||||
];
|
||||
|
||||
if (isset($options['temperature'])) {
|
||||
$data['temperature'] = (float) $options['temperature'];
|
||||
}
|
||||
if (isset($options['max_tokens'])) {
|
||||
$data['max_tokens'] = (int) $options['max_tokens'];
|
||||
}
|
||||
|
||||
$response = $this->httpPost($url, $data, [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
|
||||
if ($response === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
if (!$result || isset($result['error'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $result['choices'][0]['message']['content'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chatJson(string $systemPrompt, string $userPrompt, array $options = []): array
|
||||
{
|
||||
$jsonInstruction = "\n\n请严格按照JSON格式返回结果,不要包含任何其他文字说明。";
|
||||
$content = $this->chat($systemPrompt . $jsonInstruction, $userPrompt, $options);
|
||||
|
||||
if (empty($content)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 尝试提取JSON内容(可能包裹在markdown代码块中)
|
||||
if (preg_match('/```(?:json)?\s*([\s\S]*?)```/', $content, $matches)) {
|
||||
$content = $matches[1];
|
||||
}
|
||||
|
||||
$decoded = json_decode(trim($content), true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProviderName(): string
|
||||
{
|
||||
return $this->providerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getModels(): array
|
||||
{
|
||||
$apiKey = get_system_config("ai_provider_{$this->providerId}_key", '');
|
||||
$baseUrl = get_system_config("ai_provider_{$this->providerId}_base_url", 'https://api.openai.com/v1');
|
||||
|
||||
if (empty($apiKey)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$url = rtrim($baseUrl, '/') . '/models';
|
||||
$response = $this->httpGet($url, [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
]);
|
||||
|
||||
if ($response === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
if (!$result || !isset($result['data'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$models = [];
|
||||
foreach ($result['data'] as $model) {
|
||||
$models[] = [
|
||||
'id' => $model['id'] ?? '',
|
||||
'name' => $model['id'] ?? '',
|
||||
'owned_by' => $model['owned_by'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testConnection(): bool
|
||||
{
|
||||
$apiKey = get_system_config("ai_provider_{$this->providerId}_key", '');
|
||||
if (empty($apiKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->chat(
|
||||
'You are a test assistant.',
|
||||
'Reply with exactly: OK',
|
||||
['max_tokens' => 5]
|
||||
);
|
||||
|
||||
return !empty($result);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST 请求.
|
||||
*
|
||||
* @param string $url 请求地址
|
||||
* @param array $data 请求数据
|
||||
* @param array $headers 请求头
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function httpPost(string $url, array $data, array $headers = [])
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET 请求.
|
||||
*
|
||||
* @param string $url 请求地址
|
||||
* @param array $headers 请求头
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function httpGet(string $url, array $headers = [])
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user