'multipart/form-data', 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; if ($aiSetting['ai_proxy']) { $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $cacheKey = "openAItranscriptions::" . md5($filePath . '_' . Base::array2json($extra) . '_' . Base::array2json($extParams)); $result = Cache::remember($cacheKey, Carbon::now()->addDays(), function () use ($aiSetting, $extra, $extParams, $filePath) { $post = array_merge($extParams, [ 'file' => new \CURLFile($filePath), 'model' => 'whisper-1', ]); $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/audio/transcriptions', $post, $extra, 15); if (Base::isError($res)) { return Base::retError("语音转文字失败", $res); } $resData = Base::json2array($res['data']); if (empty($resData['text'])) { return Base::retError("语音转文字失败", $resData); } return Base::retSuccess("success", [ 'file' => $filePath, 'text' => $resData['text'], ]); }); if (Base::isError($result)) { Cache::forget($cacheKey); } return $result; } /** * 通过 openAI 翻译 * @param string $text 需要翻译的文本内容 * @param string $targetLanguage 目标语言(如:English, 简体中文, 日本語等) * @return array */ public static function openAItranslations($text, $targetLanguage) { $systemSetting = Base::setting('system'); $aiSetting = Base::setting('aiSetting'); if ($systemSetting['translation'] !== 'open' || !Setting::AIOpen()) { return Base::retError("翻译功能未开启"); } $extra = [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; if ($aiSetting['ai_proxy']) { $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $cacheKey = "openAItranslations::" . md5($text . '_' . $targetLanguage . '_' . ($aiSetting['ai_api_key'] ?? '')); $result = Cache::remember($cacheKey, Carbon::now()->addDays(7), function () use ($aiSetting, $extra, $text, $targetLanguage) { $post = json_encode([ "model" => "gpt-4.1-nano", "messages" => [ [ "role" => "system", "content" => << "user", "content" => "请将以下内容翻译为 {$targetLanguage}:\n\n{$text}" ] ], "temperature" => 0.2, "max_tokens" => max(1000, intval(mb_strlen($text) * 1.5)) ]); $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/chat/completions', $post, $extra, 60); if (Base::isError($res)) { return Base::retError("翻译请求失败", $res); } $resData = Base::json2array($res['data']); if (empty($resData['choices'])) { return Base::retError("翻译响应格式错误", $resData); } $translatedText = $resData['choices'][0]['message']['content']; $translatedText = trim($translatedText); if (empty($translatedText)) { return Base::retError("翻译结果为空"); } return Base::retSuccess("success", [ 'translated_text' => $translatedText, 'target_language' => $targetLanguage, 'translated_at' => date('Y-m-d H:i:s') ]); }); if (Base::isError($result)) { Cache::forget($cacheKey); } return $result; } /** * 通过 openAI 生成标题 * @param string $text 需要生成标题的文本内容 * @return array */ public static function openAIGenerateTitle($text) { $aiSetting = Base::setting('aiSetting'); if (!Setting::AIOpen()) { return Base::retError("AI接口未配置"); } $extra = [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; if ($aiSetting['ai_proxy']) { $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $cacheKey = "openAIGenerateTitle::" . md5($text . '_' . Base::array2json($extra)); $result = Cache::remember($cacheKey, Carbon::now()->addHours(24), function () use ($aiSetting, $extra, $text) { $post = json_encode([ "model" => "gpt-4.1-nano", "messages" => [ [ "role" => "system", "content" => << "user", "content" => "请为以下内容生成一个合适的标题:\n\n" . $text ] ], "temperature" => 0.3, "max_tokens" => 100 ]); $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/chat/completions', $post, $extra, 10); if (Base::isError($res)) { return Base::retError("标题生成失败", $res); } $resData = Base::json2array($res['data']); if (empty($resData['choices'])) { return Base::retError("标题生成失败", $resData); } $result = $resData['choices'][0]['message']['content']; $result = trim($result); if (empty($result)) { return Base::retError("生成的标题为空"); } return Base::retSuccess("success", [ 'title' => $result, 'length' => mb_strlen($result), 'generated_at' => date('Y-m-d H:i:s') ]); }); if (Base::isError($result)) { Cache::forget($cacheKey); } return $result; } /** * 通过 openAI 生成职场笑话、心灵鸡汤 * @return array 返回20个笑话和20个心灵鸡汤 */ public static function openAIGenJokeAndSoup() { $aiSetting = Base::setting('aiSetting'); if (!Setting::AIOpen()) { return Base::retError("AI接口未配置"); } $extra = [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $aiSetting['ai_api_key'], ]; if ($aiSetting['ai_proxy']) { $extra['CURLOPT_PROXY'] = $aiSetting['ai_proxy']; $extra['CURLOPT_PROXYTYPE'] = str_contains($aiSetting['ai_proxy'], 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $cacheKey = "openAIJokeAndSoup::" . md5(date('Y-m-d')); $result = Cache::remember($cacheKey, Carbon::now()->addHours(6), function () use ($aiSetting, $extra) { $post = json_encode([ "model" => "gpt-4.1-nano", "messages" => [ [ "role" => "system", "content" => << "user", "content" => "请生成20个职场笑话和20个心灵鸡汤" ] ], "temperature" => 0.8 ]); $res = Ihttp::ihttp_request(($aiSetting['ai_api_url'] ?: 'https://api.openai.com/v1') . '/chat/completions', $post, $extra, 120); if (Base::isError($res)) { return Base::retError("生成失败", $res); } $resData = Base::json2array($res['data']); if (empty($resData['choices'])) { return Base::retError("生成失败", $resData); } // 清理可能的markdown代码块标记 $content = $resData['choices'][0]['message']['content']; $content = preg_replace('/^\s*```json\s*/', '', $content); $content = preg_replace('/\s*```\s*$/', '', $content); $content = trim($content); // 解析JSON $parsedData = Base::json2array($content); if (!$parsedData || !isset($parsedData['jokes']) || !isset($parsedData['soups'])) { return Base::retError("生成内容格式错误", $content); } // 验证数据完整性 if (!is_array($parsedData['jokes']) || !is_array($parsedData['soups'])) { return Base::retError("生成内容格式错误", $parsedData); } // 过滤空内容并确保有内容 $jokes = array_filter(array_map('trim', $parsedData['jokes'])); $soups = array_filter(array_map('trim', $parsedData['soups'])); if (empty($jokes) || empty($soups)) { return Base::retError("生成内容为空", $parsedData); } return Base::retSuccess("success", [ 'jokes' => array_values($jokes), // 重新索引数组 'soups' => array_values($soups), 'total_jokes' => count($jokes), 'total_soups' => count($soups), 'generated_at' => date('Y-m-d H:i:s') ]); }); if (Base::isError($result)) { Cache::forget($cacheKey); } return $result; } /** * 获取 ollama 模型 * @param $baseUrl * @param $key * @param $agency * @return array */ public static function ollamaModels($baseUrl, $key = null, $agency = null) { $extra = [ 'Content-Type' => 'application/json', ]; if ($key) { $extra['Authorization'] = 'Bearer ' . $key; } if ($agency) { $extra['CURLOPT_PROXY'] = $agency; $extra['CURLOPT_PROXYTYPE'] = str_contains($agency, 'socks') ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP; } $res = Ihttp::ihttp_request(rtrim($baseUrl, '/') . '/api/tags', [], $extra, 15); if (Base::isError($res)) { return Base::retError("获取失败", $res); } $resData = Base::json2array($res['data']); if (empty($resData['models'])) { return Base::retError("获取失败", $resData); } $models = []; foreach ($resData['models'] as $model) { if ($model['name'] !== $model['model']) { $models[] = "{$model['model']} | {$model['name']}"; } else { $models[] = $model['model']; } } return Base::retSuccess("success", [ 'models' => $models, 'original' => $resData['models'] ]); } /** * 判断是否工作日 * @param string $Ymd 年月日(如:20220102) * @return int * 0: 工作日 * 1: 非工作日 * 2: 获取不到远程数据的非工作日(周六、日) * 所以可以用>0来判断是否工作日 */ public static function isHoliday(string $Ymd): int { $time = strtotime($Ymd . " 00:00:00"); $holidayKey = "holiday::" . date("Ym", $time); $holidayData = Cache::remember($holidayKey, now()->addMonth(), function () use ($time) { $apiMonth = date("Ym", $time); $apiResult = Ihttp::ihttp_request("https://api.apihubs.cn/holiday/get?field=date&month={$apiMonth}&workday=2&size=31", [], [], 20); if (Base::isError($apiResult)) { info('[holiday] get error'); return []; } $apiResult = Base::json2array($apiResult['data']); if ($apiResult['code'] !== 0) { info('[holiday] result error'); return []; } return array_map(function ($item) { return $item['date']; }, $apiResult['data']['list']); }); if (empty($holidayData)) { Cache::forget($holidayKey); return in_array(date("w", $time), [0, 6]) ? 2 : 0; } return in_array($Ymd, $holidayData) ? 1 : 0; } /** * Drawio 图标搜索 * @param $query * @param $page * @param $size * @return array */ public static function drawioIconSearch($query, $page, $size): array { $result = self::curl("https://app.diagrams.net/iconSearch?q={$query}&p={$page}&c={$size}", 15 * 86400); if ($result = Base::json2array($result)) { return $result; } return [ 'icons' => [], 'total_count' => 0 ]; } /** * 获取搜狗表情包 * @param $keyword * @return array */ public static function sticker($keyword) { $data = self::curl("https://pic.sogou.com/napi/wap/searchlist", 1800, 15, [], [ 'CURLOPT_CUSTOMREQUEST' => 'POST', 'CURLOPT_POSTFIELDS' => json_encode([ "initQuery" => $keyword . " 表情", "queryFrom" => "wap", "ie" => "utf8", "keyword" => $keyword . " 表情", // "mode" => 20, "showMode" => 0, "start" => 1, "reqType" => "client", "reqFrom" => "wap_result", "prevIsRedis" => "n", "pagetype" => 0, "amsParams" => [] ]), 'CURLOPT_HTTPHEADER' => [ 'Content-Type: application/json', 'Referer: https://pic.sogou.com/' ] ]); $data = Base::json2array($data); if ($data['status'] === 0 && $data['data']['picResult']['items']) { $data = $data['data']['picResult']['items']; $data = array_filter($data, function ($item) { return intval($item['thumbHeight']) > 10 && intval($item['thumbWidth']) > 10; }); return array_map(function ($item) { return [ 'name' => $item['title'], 'src' => $item['thumbUrl'], 'height' => $item['thumbHeight'], 'width' => $item['thumbWidth'], ]; }, $data); } return []; } /** * @param $url * @param int $cacheSecond 缓存时间(秒),如果结果为空则缓存有效30秒 * @param int $timeout * @param array $post * @param array $extra * @return string */ private static function curl($url, int $cacheSecond = 0, int $timeout = 15, array $post = [], array $extra = []): string { if ($cacheSecond > 0) { $key = "curlCache::" . md5($url) . "::" . md5(json_encode($post)) . "::" . md5(json_encode($extra)); $content = Cache::remember($key, Carbon::now()->addSeconds($cacheSecond), function () use ($extra, $post, $cacheSecond, $key, $timeout, $url) { $result = Ihttp::ihttp_request($url, $post, $extra, $timeout); $content = Base::isSuccess($result) ? trim($result['data']) : ''; if (empty($content) && $cacheSecond > 30) { Cache::put($key, "", Carbon::now()->addSeconds(30)); } return $content; }); } else { $result = Ihttp::ihttp_request($url, $post, $extra, $timeout); $content = Base::isSuccess($result) ? trim($result['data']) : ''; } // return $content; } }