mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-07 09:02:49 +08:00
feat(post): 新增手机图片排版与AI智能排版功能
- 新增手机图片排版功能,支持小红书/抖音尺寸输出 - 新增AI智能排版顾问,支持内容分析与优化推荐 - 新增AI供应商管理,支持多渠道配置与同步 - 新增文章输出管理页面,支持图片预览与批量下载 - 新增字体文件与排版样式配置
This commit is contained in:
237
app/common/tools/ai/ModelsDevSync.php
Normal file
237
app/common/tools/ai/ModelsDevSync.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\tools\ai;
|
||||
|
||||
/**
|
||||
* models.dev 数据同步与缓存.
|
||||
*
|
||||
* 从 https://models.dev/api.json 获取AI模型供应商和模型目录数据,
|
||||
* 缓存到 runtime/ai/models_dev_cache.json, 有效期24小时.
|
||||
*/
|
||||
class ModelsDevSync
|
||||
{
|
||||
/**
|
||||
* @var string models.dev API地址
|
||||
*/
|
||||
protected $apiUrl = 'https://models.dev/api.json';
|
||||
|
||||
/**
|
||||
* @var string 缓存文件路径
|
||||
*/
|
||||
protected $cachePath;
|
||||
|
||||
/**
|
||||
* @var int 缓存有效期(秒), 默认24小时
|
||||
*/
|
||||
protected $cacheTtl = 86400;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cachePath = app()->getRuntimePath() . 'ai' . DIRECTORY_SEPARATOR . 'models_dev_cache.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步供应商数据到本地缓存.
|
||||
*
|
||||
* @return array 同步结果统计
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function syncProviders(): array
|
||||
{
|
||||
$data = $this->fetchApi();
|
||||
|
||||
if ($data === false) {
|
||||
throw new \Exception('无法连接 models.dev API');
|
||||
}
|
||||
|
||||
$parsed = $this->parseApiData($data);
|
||||
|
||||
$this->writeCache($parsed);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有供应商列表.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProviders(): array
|
||||
{
|
||||
$cache = $this->readCache();
|
||||
if ($cache === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $cache['providers'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定供应商的模型列表.
|
||||
*
|
||||
* @param string $providerId 供应商标识
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModelsByProvider(string $providerId): array
|
||||
{
|
||||
$cache = $this->readCache();
|
||||
if ($cache === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$models = $cache['models'][$providerId] ?? [];
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存状态信息.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCacheStatus(): array
|
||||
{
|
||||
$exists = file_exists($this->cachePath);
|
||||
$mtime = $exists ? filemtime($this->cachePath) : 0;
|
||||
$expired = ($mtime + $this->cacheTtl) < time();
|
||||
|
||||
return [
|
||||
'exists' => $exists,
|
||||
'last_sync' => $mtime > 0 ? date('Y-m-d H:i:s', $mtime) : '从未同步',
|
||||
'expired' => $expired,
|
||||
'provider_count' => count($this->getProviders()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从API获取原始数据.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function fetchApi()
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || $response === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析API数据为统一格式.
|
||||
*
|
||||
* @param string $raw JSON原始数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseApiData(string $raw): array
|
||||
{
|
||||
$data = json_decode($raw, true);
|
||||
if (!is_array($data)) {
|
||||
return ['providers' => [], 'models' => []];
|
||||
}
|
||||
|
||||
$providers = [];
|
||||
$models = [];
|
||||
|
||||
foreach ($data as $providerId => $providerData) {
|
||||
if (!is_array($providerData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$provider = [
|
||||
'id' => $providerId,
|
||||
'name' => $providerData['name'] ?? ucfirst($providerId),
|
||||
'url' => $providerData['url'] ?? '',
|
||||
'docs_url' => $providerData['docs_url'] ?? '',
|
||||
];
|
||||
|
||||
// 提取API基础地址(如果有)
|
||||
if (isset($providerData['api_base_url'])) {
|
||||
$provider['api_base_url'] = $providerData['api_base_url'];
|
||||
}
|
||||
|
||||
$providers[$providerId] = $provider;
|
||||
|
||||
// 提取模型列表
|
||||
$providerModels = [];
|
||||
if (isset($providerData['models']) && is_array($providerData['models'])) {
|
||||
foreach ($providerData['models'] as $modelId => $modelData) {
|
||||
$model = [
|
||||
'id' => $modelId,
|
||||
'name' => $modelData['name'] ?? $modelId,
|
||||
'description' => $modelData['description'] ?? '',
|
||||
];
|
||||
|
||||
if (isset($modelData['context_length'])) {
|
||||
$model['context_length'] = (int) $modelData['context_length'];
|
||||
}
|
||||
if (isset($modelData['pricing'])) {
|
||||
$model['pricing'] = $modelData['pricing'];
|
||||
}
|
||||
|
||||
$providerModels[] = $model;
|
||||
}
|
||||
}
|
||||
|
||||
$models[$providerId] = $providerModels;
|
||||
}
|
||||
|
||||
return [
|
||||
'providers' => $providers,
|
||||
'models' => $models,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存文件.
|
||||
*
|
||||
* @param array $data 缓存数据
|
||||
*/
|
||||
protected function writeCache(array $data): void
|
||||
{
|
||||
$dir = dirname($this->cachePath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($this->cachePath, json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存文件(支持过期降级).
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function readCache(): ?array
|
||||
{
|
||||
if (!file_exists($this->cachePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents($this->cachePath);
|
||||
$data = json_decode($content, true);
|
||||
|
||||
if (!is_array($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user