<form class="p-4 flex space-x-4 justify-center items-center" action="/chat" method="post">
@csrf
<input id="message" placeholder="输入你的问题..." type="text" name="message" autocomplete="off" class="border rounded-md p-2 flex-1" required />
<button class="flex items-center justify-center px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-md text-sm md:text-base" type="submit">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
</button>
<button class="flex items-center justify-center px-4 py-2 bg-gray-400 hover:bg-gray-500 text-white rounded-md text-sm md:text-base" onclick="window.location.href='/reset'">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</button>
</form>
接下来,我们将完成后端处理会话与重置会话的核心功能。
实现会话核心功能
我们将新建一个控制器
ChatController
处理会话相关的业务逻辑:
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use GeekrOpenAI\Laravel\Facades\OpenAI;
class ChatController extends Controller
* Handle the incoming prompt.
public function chat(Request $request): RedirectResponse
// 系统消息
$messages = $request->session()->get('messages', [
['role' => 'system', 'content' => 'You are GeekChat - A ChatGPT clone. Answer as concisely as possible.']
// 用户消息
$messages[] = ['role' => 'user', 'content' => $request->input('message')];
$response = OpenAI::chat()->create([
'model' => 'gpt-3.5-turbo',
'messages' => $messages
// 响应消息
$messages[] = ['role' => 'assistant', 'content' => $response->choices[0]->message->content];
$request->session()->put('messages', $messages);
return redirect('/');
正如我上面提到的,此处的提示有点不同 —— 它是来自用户的消息和从 OpenAI 获得的响应的混合物。新的聊天 API 还允许我们定义“系统”消息,这是某种通用指令,用于告诉聊天模型其一般用途应该是什么。
这里我们通过
$messages
数组聚合提示,作为默认值,我们将在其中放置我们的“系统”消息,然后将用户问题消息放进来,就可以使用这个数组并执行 API 请求:
$response = OpenAI::chat()->create([
'model' => 'gpt-3.5-turbo',
'messages' => $messages
拿到 OpenAI 聊天响应消息后,我们还要将其添加到我们的 $messages 数组中:
$messages[] = ['role' => 'assistant', 'content' => $response->choices[0]->message->content];
请注意,这里我们添加来自 API 响应消息时,使用了 “assistant” 角色将其添加到 $messages 数组中,以表明这是来自 API 而不是用户的消息。
这样一来,我们就能够提出涉及先前消息和回复上下文的问题了。
由于这些“消息”需要随时间增长,我们需要将它们存储在某个地方,对于这个简单的克隆版本,我们将把消息存储在会话中。现在我们的 $messages 数组包含了我们需要的所有消息,我们可以将其存储回会话中并重定向回去:
$request->session()->put('messages', $messages);
return redirect('/');
就是这样!下一次用户发送消息时,我们将重用会话中的消息并将新消息附加到其中,就像 ChatGPT 一样。
最后不要忘了在路由文件 routes/web.php 中定义路由与控制器方法的映射关系:
Route::post('/chat', ChatController::class . '@chat');
相比之下,重置会话就简单多了,只需要清空会话中的消息数据,然后重定向到首页即可,还是在 ChatController 中定义重置会话方法:
* Reset the session.
public function reset(Request $request): RedirectResponse
$request->session()->forget('messages');
return redirect('/');
对应的路由映射关系如下:
Route::get('/reset', ChatController::class . '@reset');
完成前端展示
现在,我们在会话中有了所有的消息(包含来自 OpenAI 响应和用户的消息),我们需要做的就是将它们传递给视图并向用户显示它们。这一切都是在首页路由中完成的。
为了不显示内部的“系统”消息,我们可以在将消息传递给视图之前从消息数组中删除它:
Route::get('/', function () {
$messages = collect(session('messages', []))->reject(fn ($message) => $message['role'] === 'system');
return view('welcome', [
'messages' => $messages
在视图中,我现在只是循环遍历消息,根据其来自用户还是来自 GeekChat 给它们不同的背景颜色,然后使用 Markdown 解析器解析消息内容并渲染:
@foreach($messages as $message)
<div class="flex rounded-lg p-4 @if ($message['role'] === 'assistant') bg-green-200 flex-reverse @else bg-blue-200 @endif ">
<div class="ml-4">
<div class="text-lg">
@if ($message['role'] === 'assistant')
<a href="#" class="font-medium text-gray-900">GeekChat</a>
@else
<a href="#" class="font-medium text-gray-900">你</a>
@endif
<div class="mt-1">
<p class="text-gray-600">
{!! \Illuminate\Mail\Markdown::parse($message['content']) !!}
@endforeach
这就是所有需要做的事情,得益于新的 OpenAI ChatGPT API,你可以轻松构建自己的 ChatGPT 克隆版。
基于 Docker 开发部署
如果你对 Laravel 部署,还可以使用 Laravel 自带的 Sail 扩展包通过 Docker 在本地部署启动应用,开始之前需要先安装 Sail 扩展包:
composer require laravel/sail --dev
由于这个项目比较简单,没有数据库、缓存、消息队列,所以我们直接通过 Sail 启动应用就好了(确保此时 Docker Desktop 已经启动并运行):
./vendor/bin/sail up -d
如果启动过程中出现类似下面这样的错误:
#0 274.4 tee: /etc/apt/keyrings/ppa_ondrej_php.gpg: No such file or directory
多半是网络原因导致的,可以参照这篇教程 设置 Ubuntu 软件源的国内镜像,或者参考这个项目的 Github 代码仓库 ,我把很多不需要数据库、前端依赖都删除掉了,同时移除了本地对 PHP/Composer/Sail 的依赖,保留最小可用资源,直接通过 docker-compose up -d 启动即可。
启动成功后,就可以通过 http://localhost在浏览器中访问 ChatGPT 网页版了:
use GuzzleHttp\Client as GuzzleClient;
use OpenAI\Client;
use OpenAI\Transporters\HttpTransporter;
use OpenAI\ValueObjects\ApiKey;
use OpenAI\ValueObjects\Transporter\BaseUri;
use OpenAI\ValueObjects\Transporter\Headers;
class OpenAI
* Creates a new Open AI Client with the given API token.
public static function client(string $apiKey, $baseUri, string $organization = null): Client
$apiKey = ApiKey::from($apiKey);
$baseUri = BaseUri::from($baseUri);
$headers = Headers::withAuthorization($apiKey);
if ($organization !== null) {
$headers = $headers->withOrganization($organization);
$client = new GuzzleClient();
$transporter = new HttpTransporter($client, $baseUri, $headers);
return new Client($transporter);
这个代理的源码我也提交到 Github 仓库里了,其实就是做一层转发而已:GO-OPENAI-PROXY ,觉得有帮助就给个 star 吧。
Loading...
Published in AI and ChatGPT
Previous Post
基于最新的 ChatGPT API 实现命令行版 ChatGPT
Next Post
基于 Laravel + Vue + Whisper 实现语音版 ChatGPT
好了,看github项目readme:https://github.com/geekr-dev/geekchat ,设置下OpenAI密钥,直接通过docker-compsoe up -d 启动应用就可以了
use GuzzleHttp\Client as GuzzleClient;
use OpenAI\Client;
use OpenAI\Transporters\HttpTransporter;
use OpenAI\ValueObjects\ApiKey;
use OpenAI\ValueObjects\Transporter\BaseUri;
use OpenAI\ValueObjects\Transporter\Headers;
$openai = static function(string $apiKey, string $organization = null) {
$apiKey = ApiKey::from($apiKey);
<code>$baseUri = BaseUri::from('api.openai.com/v1');
$headers = Headers::withAuthorization($apiKey);
if ($organization !== null) {
$headers = $headers->withOrganization($organization);
$headers = $headers->withAccept('text/event-stream');
$client = new GuzzleClient([
// 这里填代理地址
'proxy' => 'http://127.0.0.1:10809',
'stream' => true,
$transporter = new HttpTransporter($client, $baseUri, $headers);
return new Client($transporter);
</code>
$client = $openai('xxxxxxxxxx');
$result = $client->chat()->create([
'model' => 'gpt-3.5-turbo',
'messages' => $messages = [
['role' => 'system', 'content' => '你是一个文案高手'],
['role' => 'user', 'content' => '生成一段200字的话语'],
'n' => 3,
ChatGPT 终极指南 Prompt 工程指南 通过 ChatGPT 进行股票投资 基于 ChatGPT 构建 SaaS 产品 通过 ChatGPT 玩转加密货币 通过 ChatGPT 玩转 SEO 通过 ChatGPT 玩转无代码 通过 ChatGPT 玩转粉丝运营 通过 ChatGPT 玩转电子商务 通过 ChatGPT 玩转销售漏斗 基于 ChatGPT 构建数字产品 通过 ChatGPT 玩转Notion 通过 ChatGPT 编写广告文案
ChatGPT 终极指南 - 28,006 viewsGo 入门到精通教程 - 61,370 viewsGo 快速入门篇(一):第一个 Go 程序 - 15,545 viewsGo 快速入门篇(二):Go 项目工程管理示例(基于 Go Modules) - 14,963 views开篇:为什么学习 Go 语言 - 13,190 views基于 OpenAI API + Laravel 快速构建网页版 ChatGPT - 11,952 viewsGo 数据类型篇(一):变量、作用域、常量和枚举 - 11,806 viewsGo 快速入门篇(三):单元测试、问题定位及代码调试 - 11,139 views面向 ChatGPT 编程实现全栈开发的 18 种方法 - 10,070 viewsGo 数据类型篇(八):切片使用入门与数据共享问题处理 - 9,215 views
Go 面向对象编程篇(三):通过组合实现类的继承和方法重写 (5.00)
Go 快速入门篇(三):单元测试、问题定位及代码调试 (5.00)
Go 错误处理篇(三):panic 和 recover (5.00)
Go 数据结构和算法篇(二):栈 (5.00)
基于 OpenAI API + Laravel 快速构建网页版 ChatGPT (5.00)
Go 数据结构和算法篇(六):选择排序 (5.00)
Go 并发编程篇(一):从多进程、多线程到协程 (5.00)
Go 数据结构和算法篇(十二):字符串匹配之 KMP 算法 (5.00)
Go 数据结构和算法篇(十八):平衡二叉树 (5.00)
Go 数据结构和算法篇(十七):二叉排序(查找)树 (5.00)