diff --git a/app/Http/Controllers/Api/AssistantController.php b/app/Http/Controllers/Api/AssistantController.php new file mode 100644 index 0000000..6af72f0 --- /dev/null +++ b/app/Http/Controllers/Api/AssistantController.php @@ -0,0 +1,78 @@ +success( + Assistant::whereUserId($request->user('api')->id)->get() + ); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'string|required', + 'description' => 'string|required', + 'prompt' => 'string|required|max:2000', + ]); + + $assistantModel = new Assistant(); + + $assistant = $assistantModel->create([ + 'name' => $request->input('name'), + 'description' => $request->input('description'), + 'prompt' => $request->input('prompt'), + 'user_id' => $request->user('api')->id, + ]); + + // $tools = $request->input('tools'); + + // 检测这些工具是否存在且是否属于用户 + // $tools = $assistantModel->whereIn('id', $tools)->whereUserId($request->user('api')->id)->get(); + // + // $assistant->tools()->sync($tools->pluck('id')->toArray()); + // + // + // $assistant->tools()->sync($request->input('tools')); + + return $this->created($assistant); + } + + /** + * Display the specified resource. + */ + public function show(AssistantRequest $request, Assistant $assistant) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(AssistantRequest $request, Assistant $assistant) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(AssistantRequest $request, Assistant $assistant) + { + // + } +} diff --git a/app/Http/Controllers/Api/AssistantToolController.php b/app/Http/Controllers/Api/AssistantToolController.php new file mode 100644 index 0000000..aa9cec6 --- /dev/null +++ b/app/Http/Controllers/Api/AssistantToolController.php @@ -0,0 +1,58 @@ +success( + $assistant->tools()->get() + ); + } + + /** + * Store a newly created resource in storage. + */ + public function store(AssistantRequest $request, Assistant $assistant) + { + $request->validate([ + 'tool_id' => 'required|exists:tools,id', + ]); + + $tool = Tool::find($request->input('tool_id')); + // 检测 tool是否属于user + if ($tool->user_id !== $request->user('api')->id) { + return $this->forbidden(); + } + + // 检测是否存在 + if ($assistant->tools()->where('tool_id', $tool->id)->exists()) { + return $this->conflict('The tool already exists'); + } + + $assistant->tools()->attach($tool->id); + + return $this->created($assistant->tools); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(AssistantRequest $request, Assistant $assistant, Tool $tool) + { + // 从 assistant_tools 表中删除 + $assistant->tools()->detach($tool->id); + + return $this->deleted(); + } +} diff --git a/app/Http/Controllers/Web/ChatController.php b/app/Http/Controllers/Api/ChatController.php similarity index 95% rename from app/Http/Controllers/Web/ChatController.php rename to app/Http/Controllers/Api/ChatController.php index 9641c86..2735ff1 100644 --- a/app/Http/Controllers/Web/ChatController.php +++ b/app/Http/Controllers/Api/ChatController.php @@ -1,6 +1,6 @@ success( + Tool::whereUserId($request->user('api')->id)->get() + ); + } + + /** + * Store a newly created resource in storage. + * + * @throws \Exception + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'string|required', + 'description' => 'string|nullable', + 'url' => 'string|required', + 'api_key' => 'string|nullable', + ]); + + $url = $request->input('url'); + + // 检测是否存在 + if (Tool::whereDiscoveryUrl($url)->exists()) { + return $this->conflict('The tool already exists'); + } + + $json = Http::get($url); + + $toolRepo = new ToolRepo($json->json()); + + $tool = new Tool(); + $tool->create([ + 'name' => $toolRepo->name, + 'description' => $toolRepo->description, + 'discovery_url' => $url, + 'api_key' => $request->input('api_key'), + 'data' => $toolRepo, + 'user_id' => $request->user('api')->id, + ]); + + return $this->created($tool); + } + + /** + * Display the specified resource. + */ + public function show(ToolRequest $request, Tool $tool) + { + return $this->success($tool); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, string $id) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ToolRequest $request, Tool $tool) + { + $tool->delete(); + + return $this->deleted(); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 8677cd5..d070c87 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,7 +2,123 @@ namespace App\Http\Controllers; +use Illuminate\Http\JsonResponse; + abstract class Controller { - // + public function success($data = [], $status = 200): JsonResponse + { + return $this->apiResponse($data, $status); + } + + public function apiResponse($data = [], $status = 200): JsonResponse + { + // if data is string, then it is error message + if (is_string($data)) { + $data = [ + 'message' => $data, + ]; + } + + return response()->json($data, $status)->setEncodingOptions(JSON_UNESCAPED_UNICODE); + } + + public function created($message = 'Created'): JsonResponse + { + return $this->success($message, 201); + } + + public function noContent($message = 'No content'): JsonResponse + { + return $this->success($message, 204); + } + + public function badRequest($message = 'Bad request'): JsonResponse + { + return $this->error($message); + } + + public function error($message = '', $code = 400): JsonResponse + { + return $this->apiResponse($message, $code); + } + + public function serviceUnavailable($message = 'Service unavailable'): JsonResponse + { + return $this->error($message, 503); + } + + public function forbidden($message = 'Forbidden'): JsonResponse + { + return $this->error($message, 403); + } + + public function notFound($message = 'Not found'): JsonResponse + { + return $this->error($message, 404); + } + + public function methodNotAllowed($message = 'Method not allowed'): JsonResponse + { + return $this->error($message, 405); + } + + public function tooManyRequests($message = 'Too many requests'): JsonResponse + { + return $this->error($message, 429); + } + + public function serverError($message = 'Server error'): JsonResponse + { + return $this->error($message, 500); + } + + public function failed($message = 'Failed', $code = 400): JsonResponse + { + return $this->apiResponse($message, $code); + } + + public function unauthorized($message = 'Unauthorized'): JsonResponse + { + return $this->error($message, 401); + } + + public function accepted($message = 'Accepted'): JsonResponse + { + return $this->success($message, 202); + } + + public function updated(mixed $message = 'Updated'): JsonResponse + { + if ($message instanceof Model) { + $message = $message->getChanges(); + } + + return $this->success($message); + } + + public function deleted($message = 'Deleted'): JsonResponse + { + return $this->success($message, 204); + } + + public function notAllowed($message = 'Not allowed'): JsonResponse + { + return $this->error($message, 405); + } + + public function conflict($message = 'Conflict'): JsonResponse + { + return $this->error($message, 409); + } + + public function notAcceptable($message = 'Not acceptable'): JsonResponse + { + return $this->error($message, 406); + } + + public function preconditionFailed($message = 'Precondition failed'): JsonResponse + { + return $this->error($message, 412); + } } diff --git a/app/Http/Controllers/Web/AssistantController.php b/app/Http/Controllers/Web/AssistantController.php deleted file mode 100644 index ab81b87..0000000 --- a/app/Http/Controllers/Web/AssistantController.php +++ /dev/null @@ -1,46 +0,0 @@ -callback_url = route('oauth.callback'); $this->openIDLogic = app(OpenIDLogic::class); } diff --git a/app/Http/Controllers/Web/ToolController.php b/app/Http/Controllers/Web/ToolController.php deleted file mode 100644 index f69a4d7..0000000 --- a/app/Http/Controllers/Web/ToolController.php +++ /dev/null @@ -1,75 +0,0 @@ -validate([ - 'name' => 'string|required', - 'description' => 'string|nullable', - 'url' => 'string|required', - 'api_key' => 'string|nullable', - ]); - - $url = $request->input('url'); - - - $url = "localhost:8081/fn.php"; - - $json = Http::get($url); - - - - $tool = new Tool($json->json()); - - dd($tool); - } - - /** - * Display the specified resource. - */ - public function show(string $id) - { - // - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, string $id) - { - // - } - - /** - * Remove the specified resource from storage. - */ - public function destroy(string $id) - { - // - } -} diff --git a/app/Http/Middleware/JSONRequest.php b/app/Http/Middleware/JSONRequest.php new file mode 100644 index 0000000..f773872 --- /dev/null +++ b/app/Http/Middleware/JSONRequest.php @@ -0,0 +1,21 @@ +headers->set('Accept', 'application/json'); + return $next($request); + } +} diff --git a/app/Http/Requests/AssistantRequest.php b/app/Http/Requests/AssistantRequest.php new file mode 100644 index 0000000..3df70c0 --- /dev/null +++ b/app/Http/Requests/AssistantRequest.php @@ -0,0 +1,34 @@ +route('assistant'); + + if ($assistant && $assistant->user_id === $this->user('api')->id) { + return true; + } + + return false; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ToolRequest.php b/app/Http/Requests/ToolRequest.php new file mode 100644 index 0000000..9b738b2 --- /dev/null +++ b/app/Http/Requests/ToolRequest.php @@ -0,0 +1,33 @@ +route('tool'); + if ($tool) { + return $tool->user_id === $this->user()->id; + } + + return false; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/LLM/Qwen.php b/app/LLM/Qwen.php index b9c58e3..aa58663 100644 --- a/app/LLM/Qwen.php +++ b/app/LLM/Qwen.php @@ -6,7 +6,8 @@ class Qwen extends Base { - protected string $model = "qwen-max"; + protected string $model = 'qwen-max'; + /** * Create a new class instance. */ @@ -15,30 +16,30 @@ public function __construct() // } - public function getResponse() + public function getResponse(): void { $client = new Client([ 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache', - 'Authorization' => 'Bearer ' . config('services.dashscope.api_key'), - ] + 'Authorization' => 'Bearer '.config('services.dashscope.api_key'), + ], ]); - $url = config('services.dashscope.api_base') . '/compatible-mode/v1/chat/completions'; + $url = config('services.dashscope.api_base').'/compatible-mode/v1/chat/completions'; $messages = [ [ 'role' => 'user', - 'content' => "你好", + 'content' => '你好', ], ]; $jsonBody = json_encode([ 'model' => $this->model, 'messages' => $messages, - 'stream' => true + 'stream' => true, ]); $response = $client->request('POST', $url, [ @@ -47,15 +48,14 @@ public function getResponse() ]); $body = $response->getBody(); -// while (!$body->eof()) { -// echo $body->read(1024); -// } + // while (!$body->eof()) { + // echo $body->read(1024); + // } - while (!$body->eof()) { -// $body->read(1); + while (! $body->eof()) { + // $body->read(1); echo $body->read(1); } - } } diff --git a/app/Models/Assistant.php b/app/Models/Assistant.php index 1be4f78..53a82c8 100644 --- a/app/Models/Assistant.php +++ b/app/Models/Assistant.php @@ -12,6 +12,7 @@ class Assistant extends Model protected $fillable = [ 'name', 'description', + 'prompt', 'user_id', ]; diff --git a/app/Models/Tool.php b/app/Models/Tool.php index a69c2f8..e191ce7 100644 --- a/app/Models/Tool.php +++ b/app/Models/Tool.php @@ -5,19 +5,22 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Facades\Http; class Tool extends Model { protected $fillable = [ 'name', 'description', - 'url', + 'discovery_url', 'api_key', - 'callback_url', + 'data', 'user_id', ]; + protected $casts = [ + 'data' => 'array', + ]; + public function user(): BelongsTo { return $this->belongsTo(User::class); @@ -28,15 +31,4 @@ public function functions(): HasMany return $this->hasMany(ToolFunction::class); } - public function fetchFunctions(): void - { - $url = $this->url; - - $json = Http::get($url); - - $json = $json->json(); - - $this->callback_url = $json['callback_url']; - - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 63d6845..3104d74 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -40,7 +40,8 @@ private function setJWTGuard(): void $jwt = $request->bearerToken(); if (empty($jwt)) { - return response()->json(['error' => 'No token provided'], 401); + return null; +// return response()->json(['error' => 'No token provided'], 401); } $headers = new stdClass(); @@ -49,12 +50,18 @@ private function setJWTGuard(): void $decoded = JWT::decode($jwt, $keys, $headers); // $request->attributes->add(['token_type' => $headers->typ]); } catch (Exception $e) { - return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401); +// dd($e); + return null; + + +// return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401); } // must id_token if ($headers->typ !== 'id_token') { - return response()->json(['error' => 'The token not id_token'], 401); + return null; + +// return response()->json(['error' => 'The token not id_token'], 401); } // 检查是否有 字段 @@ -63,17 +70,22 @@ private function setJWTGuard(): void ]; foreach ($required_fields as $field) { - if (! isset($decoded->$field)) { - return response()->json(['error' => 'The token not contain the field '.$field], 401); + if (!isset($decoded->$field)) { + return null; + +// return response()->json(['error' => 'The token not contain the field '.$field], 401); } } if (config('oauth.force_aud')) { - if (! in_array($decoded->aud, config('oauth.trusted_aud'))) { - return response()->json(['error' => 'The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401); + if (!in_array($decoded->aud, config('oauth.trusted_aud'))) { + throw new Exception('The application rejected the token, token aud is ' . $decoded->aud . ', app aud is ' . config('oauth.client_id')); +// return response()->json(['error' => 'The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401); } - return response()->json(['error' => 'The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401); + // throw + throw new Exception('The token not match the application, ' . ' token aud is ' . $decoded->aud . ', app aud is ' . config('oauth.client_id')); +// return response()->json(['error' => 'The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401); } return User::where('external_id', $decoded->sub)->firstOrCreate([ diff --git a/app/Repositories/Tool/Tool.php b/app/Repositories/Tool/Tool.php index e988273..4c72a43 100644 --- a/app/Repositories/Tool/Tool.php +++ b/app/Repositories/Tool/Tool.php @@ -9,6 +9,7 @@ class Tool public string $name; public string $homepage_url; + public string $callback_url; public string $description; @@ -25,6 +26,7 @@ public function __construct(array $data) $this->data = $data; $this->parse(); } + /** * @throws Exception */ diff --git a/app/Repositories/Tool/ToolFunction.php b/app/Repositories/Tool/ToolFunction.php index 57c5c53..24d8b86 100644 --- a/app/Repositories/Tool/ToolFunction.php +++ b/app/Repositories/Tool/ToolFunction.php @@ -7,13 +7,18 @@ class ToolFunction { public string $name; + public string $description; + public array $parameters; + protected array $data; + public array $required; /** * Create a new class instance. + * * @throws Exception */ public function __construct(array $data) diff --git a/bootstrap/app.php b/bootstrap/app.php index 6b55c10..5113808 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ alias([ // 'jwt' => JWTMiddleware::class // ]); + // add for api + $middleware->api([ + JSONRequest::class + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/config/auth.php b/config/auth.php index c8936a3..286228d 100644 --- a/config/auth.php +++ b/config/auth.php @@ -42,7 +42,7 @@ ], 'api' => [ 'driver' => 'jwt', - // 'provider' => 'users', +// 'provider' => 'users', ], ], diff --git a/config/services.php b/config/services.php index 15347b7..ae09afa 100644 --- a/config/services.php +++ b/config/services.php @@ -38,6 +38,6 @@ 'dashscope' => [ 'api_key' => env('DASHSCOPE_API_KEY'), 'api_base' => env('DASHSCOPE_API_BASE', 'https://dashscope.aliyuncs.com'), - ] + ], ]; diff --git a/config/session.php b/config/session.php index f0b6541..b209e78 100644 --- a/config/session.php +++ b/config/session.php @@ -18,7 +18,7 @@ | */ - 'driver' => env('SESSION_DRIVER', 'database'), + 'driver' => env('SESSION_DRIVER', 'redis'), /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2024_07_23_093256_create_tools_table.php b/database/migrations/2024_07_23_093256_create_tools_table.php index 460bdf4..1a6398e 100644 --- a/database/migrations/2024_07_23_093256_create_tools_table.php +++ b/database/migrations/2024_07_23_093256_create_tools_table.php @@ -17,7 +17,7 @@ public function up(): void $table->string('name'); $table->string('description')->nullable(); - $table->string('discovery_url'); + $table->string('discovery_url')->index(); $table->string('api_key')->nullable(); diff --git a/database/migrations/2024_07_24_071633_add_prompt_to_assistants_table.php b/database/migrations/2024_07_24_071633_add_prompt_to_assistants_table.php new file mode 100644 index 0000000..4c18527 --- /dev/null +++ b/database/migrations/2024_07_24_071633_add_prompt_to_assistants_table.php @@ -0,0 +1,29 @@ +text('prompt')->after('description'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('assistants', function (Blueprint $table) { + $table->dropColumn('prompt'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index ae9a13b..cd58e8e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,8 +1,23 @@ user(); -})->middleware('auth:api,web'); +Route::middleware(['auth:api,web'])->group(function () { + Route::get('/user', function (Request $request) { + return $request->user(); + }); + + Route::apiResource('tools', ToolController::class); + Route::apiResource('assistants', AssistantController::class); + Route::apiResource('chats', ChatController::class); + + Route::get('assistants/{assistant}/tools', [AssistantToolController::class, 'index']); + Route::post('assistants/{assistant}/tools', [AssistantToolController::class, 'store']); + Route::delete('assistants/{assistant}/tools/{tool}', [AssistantToolController::class, 'destroy']); +});