Support own OpenAI API Key
Support own OpenAI API Key fixed openai check public cache permision when install check public cache permision when install Fixed user id Fixed user id Fixed api wip wip API Key wip fixed openai
This commit is contained in:
parent
7bf19e2c92
commit
8ab99143b9
|
|
@ -18,13 +18,15 @@ class Input extends Component
|
|||
|
||||
public string $placeholder;
|
||||
|
||||
public string $description;
|
||||
|
||||
public string $type;
|
||||
|
||||
public string $step;
|
||||
|
||||
public bool $required;
|
||||
|
||||
public function __construct(string $name, string $title, string $value, bool $required = false, string $error = '', string $width = '400', string $type = 'text', string $step = '', string $placeholder = '')
|
||||
public function __construct(string $name, string $title, string $value, bool $required = false, string $error = '', string $width = '400', string $type = 'text', string $step = '', string $placeholder = '', string $description = '')
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->title = $title;
|
||||
|
|
@ -35,6 +37,7 @@ class Input extends Component
|
|||
$this->type = $type;
|
||||
$this->step = $step;
|
||||
$this->required = $required;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
|
|
|||
|
|
@ -50,10 +50,12 @@ return [
|
|||
|
|
||||
*/
|
||||
'permissions' => [
|
||||
'storage/framework/' => '755',
|
||||
'storage/logs/' => '755',
|
||||
'bootstrap/cache/' => '755',
|
||||
'.env' => '755',
|
||||
'.env' => '755',
|
||||
'bootstrap/cache/' => '755',
|
||||
'public/cache/' => '755',
|
||||
'public/plugin/' => '755',
|
||||
'storage/framework/' => '755',
|
||||
'storage/logs/' => '755',
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -79,9 +79,17 @@ class PluginRepo
|
|||
{
|
||||
$migrationPath = "{$bPlugin->getPath()}/Migrations";
|
||||
if (is_dir($migrationPath)) {
|
||||
Artisan::call('migrate', [
|
||||
'--force' => true,
|
||||
]);
|
||||
$files = glob($migrationPath . '/*');
|
||||
asort($files);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = str_replace(base_path(), '', $file);
|
||||
Artisan::call('migrate', [
|
||||
'--force' => true,
|
||||
'--step' => 1,
|
||||
'--path' => $file,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +99,8 @@ class PluginRepo
|
|||
*/
|
||||
public static function uninstallPlugin(BPlugin $bPlugin)
|
||||
{
|
||||
self::removeStaticFiles($bPlugin);
|
||||
// self::removeStaticFiles($bPlugin);
|
||||
self::rollbackDatabase($bPlugin);
|
||||
$type = $bPlugin->type;
|
||||
$code = $bPlugin->code;
|
||||
Plugin::query()
|
||||
|
|
@ -114,6 +123,27 @@ class PluginRepo
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库回滚
|
||||
*/
|
||||
public static function rollbackDatabase(BPlugin $bPlugin)
|
||||
{
|
||||
$migrationPath = "{$bPlugin->getPath()}/Migrations";
|
||||
if (is_dir($migrationPath)) {
|
||||
$files = glob($migrationPath . '/*');
|
||||
arsort($files);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = str_replace(base_path(), '', $file);
|
||||
Artisan::call('migrate:rollback', [
|
||||
'--force' => true,
|
||||
'--step' => 1,
|
||||
'--path' => $file,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin by code
|
||||
*
|
||||
|
|
|
|||
|
|
@ -12,17 +12,79 @@
|
|||
namespace Plugin\Openai\Controllers;
|
||||
|
||||
use Beike\Admin\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Plugin\Openai\Services\OpenAIService;
|
||||
|
||||
class OpenaiController extends Controller
|
||||
{
|
||||
/**
|
||||
* OpenAI home page.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$plugin = app('plugin')->getPlugin('openai');
|
||||
$data = [
|
||||
|
||||
$error = '';
|
||||
$baseUrl = config('beike.api_url') . '/api/openai';
|
||||
$apiType = plugin_setting('openai.api_type');
|
||||
if ($apiType == 'own') {
|
||||
$apiKey = plugin_setting('openai.api_key');
|
||||
if (empty($apiKey)) {
|
||||
$error = trans('Openai::common.empty_api_key');
|
||||
}
|
||||
$baseUrl = config('app.url') . '/admin/openai';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $plugin->getLocaleName(),
|
||||
'description' => $plugin->getLocaleDescription(),
|
||||
'type' => $apiType,
|
||||
'base' => $baseUrl,
|
||||
'error' => $error,
|
||||
];
|
||||
|
||||
return view('Openai::admin.openai', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send chat completions with OpenAI API
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array|mixed
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function completions(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new OpenAIService())->requestAI($request->all());
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get histories
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function histories(Request $request)
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 10);
|
||||
$result = (new OpenAIService())->getOpenaiLogs($perPage);
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,4 +20,8 @@ return [
|
|||
'qa_q' => 'ask',
|
||||
'qa_a' => 'answer',
|
||||
'number_free' => 'The remaining free times of the day',
|
||||
'api_type' => 'API Method',
|
||||
'own' => 'Own Key',
|
||||
'beikeshop' => 'BeikeShop',
|
||||
'empty_api_key' => 'API Key is empty, please go to the plugin settings - OpenAI - Edit and fill in the API Key first.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,4 +20,8 @@ return [
|
|||
'qa_q' => '问',
|
||||
'qa_a' => '答',
|
||||
'number_free' => '当日剩余免费次数',
|
||||
'api_type' => 'API 方式',
|
||||
'own' => '自有Key',
|
||||
'beikeshop' => 'BeikeShop平台',
|
||||
'empty_api_key' => 'API Key 为空, 请先到插件设置 - OpenAI - 编辑 填写API Key',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,4 +20,8 @@ return [
|
|||
'qa_q' => '問',
|
||||
'qa_a' => '答',
|
||||
'number_free' => '當日剩餘免費次數',
|
||||
'api_type' => 'API 方式',
|
||||
'own' => '自有 Key',
|
||||
'beikeshop' => 'BeikeShop 平台',
|
||||
'empty_api_key' => 'API Key 為空,請先到插件設置 - OpenAI - 編輯 填寫 API Key',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
/**
|
||||
* OpenAI.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-02-22 20:31:42
|
||||
* @modified 2023-02-22 20:31:42
|
||||
*/
|
||||
|
||||
namespace Plugin\Openai\Libraries\OpenAI;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Base
|
||||
{
|
||||
/**
|
||||
* @var string|bool|mixed
|
||||
*/
|
||||
private string $apiKey = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $maxTokens = 1000;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected float $temperature = 0.5;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $number = 1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $prompt;
|
||||
|
||||
/**
|
||||
* OpenAI constructor.
|
||||
* @param string|null $apiKey
|
||||
*/
|
||||
public function __construct(?string $apiKey = '')
|
||||
{
|
||||
if ($apiKey) {
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
if (empty($this->apiKey)) {
|
||||
$this->apiKey = env('OPENAI_API_KEY');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenAI instance.
|
||||
*
|
||||
* @param string|null $apiKey
|
||||
* @return Base
|
||||
*/
|
||||
public static function getInstance(?string $apiKey = ''): static
|
||||
{
|
||||
return new self($apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 max_tokens的值
|
||||
* 一般来说,max_tokens值越大,模型的表现就越好,
|
||||
* 但是需要考虑到计算资源的限制,max_tokens值不宜过大。
|
||||
* 一般来说,max_tokens值可以设置在比较合理的范围内,比如500到1000之间。
|
||||
*
|
||||
* @param int $maxTokens
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxTokens(int $maxTokens): static
|
||||
{
|
||||
$this->maxTokens = $maxTokens;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置temperature参数值, 参数的范围是0.0到2.0之间。
|
||||
* 在较低的温度下,模型会生成更加安全的文本,
|
||||
* 而在较高的温度下,模型会生成更加创新的文本。
|
||||
*
|
||||
* @param float $temperature
|
||||
* @return $this
|
||||
*/
|
||||
public function setTemperature(float $temperature): static
|
||||
{
|
||||
$this->temperature = $temperature;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 n 参数值
|
||||
* 指定了返回结果的数量。n参数越大,返回的结果数量也越多。
|
||||
*
|
||||
* @param int $number
|
||||
* @return $this
|
||||
*/
|
||||
public function setNumber(int $number): static
|
||||
{
|
||||
$this->number = $number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 prompt 参数值
|
||||
* 用于指定一段文本,用于提供给模型参考,以便于生成更加相关的文本
|
||||
*
|
||||
* @param string $prompt
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setPrompt(string $prompt): static
|
||||
{
|
||||
$this->prompt = trim($prompt);
|
||||
if (empty($this->prompt)) {
|
||||
throw new Exception('prompt 不能为空!');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求到 OpenAI
|
||||
*
|
||||
* @param $url
|
||||
* @param $data
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function request($url, $data): mixed
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $this->apiKey,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if ($response === false) {
|
||||
throw new \Exception(curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* Chat.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-03-02 14:30:10
|
||||
* @modified 2023-03-02 14:30:10
|
||||
*/
|
||||
|
||||
namespace Plugin\Openai\Libraries\OpenAI;
|
||||
|
||||
class Chat extends Base
|
||||
{
|
||||
/**
|
||||
* @var array 聊天上下文
|
||||
*/
|
||||
private array $messages;
|
||||
|
||||
/**
|
||||
* @param string|null $apiKey
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance(?string $apiKey = ''): static
|
||||
{
|
||||
return new self($apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://platform.openai.com/docs/guides/chat/introduction
|
||||
*
|
||||
* @param $messages
|
||||
* @param $prompt
|
||||
* @return Chat
|
||||
*/
|
||||
public function setMessages($messages, $prompt): self
|
||||
{
|
||||
$messages[] = ['role' => 'user', 'content' => $prompt];
|
||||
$this->messages = $messages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求到 OpenAI
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(): mixed
|
||||
{
|
||||
$model = 'gpt-3.5-turbo';
|
||||
$url = 'https://api.openai.com/v1/chat/completions';
|
||||
$data = [
|
||||
'messages' => $this->messages,
|
||||
'max_tokens' => $this->maxTokens,
|
||||
'temperature' => $this->temperature,
|
||||
'n' => $this->number,
|
||||
'stop' => '\n',
|
||||
'model' => $model,
|
||||
];
|
||||
|
||||
return $this->request($url, $data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* Completion.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-03-02 14:37:15
|
||||
* @modified 2023-03-02 14:37:15
|
||||
*/
|
||||
|
||||
namespace Plugin\Openai\Libraries\OpenAI;
|
||||
|
||||
class Completion extends Base
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$model = 'text-davinci-003';
|
||||
$url = 'https://api.openai.com/v1/completions';
|
||||
$data = [
|
||||
'prompt' => $this->prompt,
|
||||
'max_tokens' => $this->maxTokens,
|
||||
'temperature' => $this->temperature,
|
||||
'n' => $this->number,
|
||||
'stop' => '\n',
|
||||
'model' => $model,
|
||||
];
|
||||
$this->request($url, $data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('openai_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('user_id')->index('user_id');
|
||||
$table->text('question');
|
||||
$table->text('answer');
|
||||
$table->string('request_ip');
|
||||
$table->text('user_agent');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('openai_logs');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* OpenaiLog.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-03-13 16:48:17
|
||||
* @modified 2023-03-13 16:48:17
|
||||
*/
|
||||
|
||||
namespace Plugin\Openai\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OpenaiLog extends Model
|
||||
{
|
||||
public $timestamps = true;
|
||||
|
||||
protected $table = 'openai_logs';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id', 'question', 'answer', 'request_ip', 'user_agent',
|
||||
];
|
||||
|
||||
protected $appends = ['created_format'];
|
||||
|
||||
public function getCreatedFormatAttribute()
|
||||
{
|
||||
return $this->created_at->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
|
|
@ -13,3 +13,6 @@ use Illuminate\Support\Facades\Route;
|
|||
use Plugin\Openai\Controllers\OpenaiController;
|
||||
|
||||
Route::middleware('can:openai_index')->get('/openai', [OpenaiController::class, 'index'])->name('openai.index');
|
||||
|
||||
Route::middleware('can:openai_index')->get('/openai/histories', [OpenaiController::class, 'histories'])->name('openai.histories');
|
||||
Route::middleware('can:openai_index')->post('/openai/completions', [OpenaiController::class, 'completions'])->name('openai.completions');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
/**
|
||||
* OpenAIService.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-03-13 16:42:52
|
||||
* @modified 2023-03-13 16:42:52
|
||||
*/
|
||||
|
||||
namespace Plugin\Openai\Services;
|
||||
|
||||
use Plugin\Openai\Libraries\OpenAI\Chat;
|
||||
use Plugin\Openai\Models\OpenaiLog;
|
||||
|
||||
class OpenAIService
|
||||
{
|
||||
/**
|
||||
* 发起 OpenAI 请求
|
||||
*
|
||||
* @param $data
|
||||
* @return mixed
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function requestAI($data)
|
||||
{
|
||||
$prompt = $data['prompt'] ?? '';
|
||||
$apiKey = plugin_setting('openai.api_key');
|
||||
|
||||
$openAI = Chat::getInstance($apiKey);
|
||||
$messages = $this->getChatMessages();
|
||||
$response = $openAI->setMessages($messages, $prompt)->create();
|
||||
|
||||
$result['prompt'] = $prompt;
|
||||
|
||||
$error = trim($response['error']['message'] ?? '');
|
||||
if ($error) {
|
||||
$result['error'] = $error;
|
||||
} else {
|
||||
$content = trim($response['choices'][0]['message']['content'] ?? '');
|
||||
|
||||
$response['choices'][0]['text'] = $content;
|
||||
|
||||
$result['response'] = $response;
|
||||
$newLog = $this->createOpenaiLog($prompt, $content);
|
||||
$result['created_format'] = $newLog->created_format;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $question
|
||||
* @param $answer
|
||||
* @return OpenaiLog
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function createOpenaiLog($question, $answer): OpenaiLog
|
||||
{
|
||||
$user = current_user();
|
||||
$newOpenaiLog = new OpenaiLog([
|
||||
'user_id' => $user->id ?? 0,
|
||||
'question' => trim($question),
|
||||
'answer' => trim($answer),
|
||||
'request_ip' => request()->getClientIp(),
|
||||
'user_agent' => request()->userAgent(),
|
||||
]);
|
||||
$newOpenaiLog->saveOrFail();
|
||||
|
||||
return $newOpenaiLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天记录
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOpenaiLogs(int $perPage = 10)
|
||||
{
|
||||
$user = current_user();
|
||||
|
||||
return OpenaiLog::query()
|
||||
->select(['user_id', 'question', 'answer', 'created_at'])
|
||||
->where('user_id', $user->id)
|
||||
->orderByDesc('created_at')
|
||||
->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://platform.openai.com/docs/guides/chat/introduction
|
||||
*
|
||||
* messages=[
|
||||
* {"role": "system", "content": "You are a helpful assistant."},
|
||||
* {"role": "user", "content": "Who won the world series in 2020?"},
|
||||
* {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
|
||||
* {"role": "user", "content": "Where was it played?"}
|
||||
* ]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChatMessages()
|
||||
{
|
||||
$logs = OpenaiLog::query()
|
||||
->select(['user_id', 'question', 'answer', 'created_at'])
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$messages[] = [
|
||||
'role' => 'system', 'content' => 'You are a helpful assistant.',
|
||||
];
|
||||
foreach ($logs as $log) {
|
||||
$messages[] = ['role' => 'user', 'content' => $log->question];
|
||||
$messages[] = ['role' => 'assistant', 'content' => $log->answer];
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,24 +9,32 @@
|
|||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-md-7 col-12 answer-wrap">
|
||||
<div class="border p-3 bg-white" id="answer">
|
||||
<div class="not-answer"><i class="bi bi-activity"></i> {{ __('Openai::common.no_question') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7 col-12 answer-wrap">
|
||||
<div class="border p-3 bg-white" id="answer">
|
||||
<div class="not-answer"><i class="bi bi-activity"></i> {{ __('Openai::common.no_question') }}</div>
|
||||
</div>
|
||||
<div class="input-group mb-3 mt-4">
|
||||
<input type="text" id="ai-input" class="form-control rounded-0 form-control-lg"
|
||||
placeholder="{{ __('Openai::common.enter_question') }}" aria-label="{{ __('Openai::common.enter_question') }}"
|
||||
placeholder="{{ __('Openai::common.enter_question') }}" {{ $error ? 'disabled' : '' }} aria-label="{{ __('Openai::common.enter_question') }}"
|
||||
aria-describedby="button-addon2">
|
||||
<button class="btn btn-primary px-4 rounded-0" type="button" id="ai-submit"><i class="bi bi-send-fill"></i>
|
||||
<button class="btn btn-primary px-4 rounded-0" {{ $error ? 'disabled' : '' }} type="button" id="ai-submit"><i class="bi bi-send-fill"></i>
|
||||
{{ __('common.confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 col-12">
|
||||
<div class="mb-2"><i class="bi bi-megaphone text-secondary fs-3"></i> </div>
|
||||
<div class="number-free mb-3 fs-5">{{ __('Openai::common.number_free') }}:
|
||||
<span>{{ __('Openai::common.loading') }}</span></div>
|
||||
@if ($type != 'own')
|
||||
<div class="number-free mb-3 fs-5">{{ __('Openai::common.number_free') }}:
|
||||
<span>{{ __('Openai::common.loading') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if ($error)
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
{{ $error }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="text-secondary">{{ $description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -71,7 +79,9 @@
|
|||
|
||||
$('#answer').height($(window).height() - 260);
|
||||
$(document).ready(function() {
|
||||
loadQuantities();
|
||||
@if ($type != 'own')
|
||||
loadQuantities();
|
||||
@endif
|
||||
loadHistories(1 , function() {
|
||||
// 获取 answer .answer-list 内容高度
|
||||
let height = 0;
|
||||
|
|
@ -105,7 +115,7 @@
|
|||
let html = '';
|
||||
|
||||
$.ajax({
|
||||
url: `${config.api_url}/api/openai/completions`,
|
||||
url: `{{ $base }}/completions`,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'token': '{{ system_setting('base.developer_token') ?? '' }}'
|
||||
|
|
@ -149,7 +159,7 @@
|
|||
|
||||
function loadQuantities() {
|
||||
$.ajax({
|
||||
url: `${config.api_url}/api/openai/quantities?domain=${config.app_url}`,
|
||||
url: `{{ $base }}/quantities?domain=${config.app_url}`,
|
||||
headers: {
|
||||
'token': '{{ system_setting('base.developer_token') ?? '' }}'
|
||||
},
|
||||
|
|
@ -161,7 +171,7 @@
|
|||
|
||||
function loadHistories(page = 1, callback = null) {
|
||||
$.ajax({
|
||||
url: `${config.api_url}/api/openai/histories?domain=${config.app_url}&page=${page}`,
|
||||
url: `{{ $base }}/histories?domain=${config.app_url}&page=${page}`,
|
||||
headers: {
|
||||
'token': '{{ system_setting('base.developer_token') ?? '' }}'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* columns.php
|
||||
*
|
||||
* @copyright 2023 beikeshop.com - All Rights Reserved
|
||||
* @link https://beikeshop.com
|
||||
* @author Edward Yang <yangjin@guangda.work>
|
||||
* @created 2023-03-13 16:08:41
|
||||
* @modified 2023-03-13 16:08:41
|
||||
*/
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'api_type',
|
||||
'label_key' => 'common.api_type',
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
['value' => 'own', 'label_key' => 'common.own'],
|
||||
['value' => 'beikeshop', 'label_key' => 'common.beikeshop'],
|
||||
],
|
||||
'required' => true,
|
||||
'description' => '如果选择 BeikeShop 平台, 则 API Key 可以留空',
|
||||
],
|
||||
[
|
||||
'name' => 'api_key',
|
||||
'label' => 'API Key',
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
'description' => '<a target="_blank" href="https://platform.openai.com/account/api-keys">获取 API Key</a>',
|
||||
],
|
||||
];
|
||||
|
|
@ -2,6 +2,10 @@
|
|||
<input type="{{ $type }}" name="{{ $name }}"
|
||||
class="form-control wp-{{ $width }} {{ $error ? 'is-invalid' : '' }}" value="{{ $value }}"
|
||||
placeholder="{{ $placeholder ?: $title }}" @if ($required) required @endif @if ($step) step="{{ $step }}" @endif>
|
||||
@if ($description)
|
||||
<div class="help-text font-size-12 lh-base">{!! $description !!}</div>
|
||||
@endif
|
||||
|
||||
<span class="invalid-feedback" role="alert">
|
||||
@if ($error)
|
||||
{{ $error }}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
:name="$column['name']"
|
||||
:title="$column['label']"
|
||||
:placeholder="$column['placeholder'] ?? ''"
|
||||
:description="$column['description'] ?? ''"
|
||||
:error="$errors->first($column['name'])"
|
||||
:required="$column['required'] ? true : false"
|
||||
:value="old($column['name'], $column['value'] ?? '')" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue