作者 Richer

功能完成

1 -APP_NAME=ai-chat.weigoutong.com 1 +APP_NAME=ai-assistant
2 APP_NAME_ZH=AI助手 2 APP_NAME_ZH=AI助手
3 -APP_ENV=production  
4 -APP_KEY=base64:3I5SI8L6rBQRvOZranhb5Zt+OS7h3WqWWgrdkHTbnFE= 3 +APP_ENV=local
  4 +APP_KEY=base64:uiieHf/mBMwSnOuMwnzoclXT97jhhdxC1KoV8KzO+qM=
5 APP_DEBUG=true 5 APP_DEBUG=true
6 -APP_URL=http://ai-chat.weigoutong.com  
7 -APP_DOMAIN = ai-chat.weigoutong.com 6 +APP_URL=http://ai-assistant.dev.zhangxinkeji.com
  7 +APP_DOMAIN = ai-assistant.dev.zhangxinkeji.com
8 8
9 LOG_CHANNEL=daily 9 LOG_CHANNEL=daily
10 10
11 DB_CONNECTION=mysql 11 DB_CONNECTION=mysql
12 DB_PORT=3306 12 DB_PORT=3306
13 -DB_HOST=localhost  
14 -DB_DATABASE=ai-chat 13 +DB_HOST=rm-uf63r3yhy6806wdpjqo.mysql.rds.aliyuncs.com
  14 +DB_DATABASE=xzy.ai-assistant
15 DB_USERNAME=root 15 DB_USERNAME=root
16 -DB_PASSWORD=4deec965296ff346 16 +DB_PASSWORD=zhangxinkeji@2003
17 17
18 # 远程数据库 美国服务器使用该配置 18 # 远程数据库 美国服务器使用该配置
19 #DB_CONNECTION_REMOTE=mysql 19 #DB_CONNECTION_REMOTE=mysql
20 #DB_PORT_REMOTE=3306 20 #DB_PORT_REMOTE=3306
21 #DB_HOST_REMOTE=rm-uf63r3yhy6806wdpjqo.mysql.rds.aliyuncs.com 21 #DB_HOST_REMOTE=rm-uf63r3yhy6806wdpjqo.mysql.rds.aliyuncs.com
22 -#DB_DATABASE_REMOTE=xzy.chatgpt 22 +#DB_DATABASE_REMOTE=ai-assistant
23 #DB_USERNAME_REMOTE=root 23 #DB_USERNAME_REMOTE=root
24 #DB_PASSWORD_REMOTE=zhangxinkeji@2003 24 #DB_PASSWORD_REMOTE=zhangxinkeji@2003
25 25
@@ -27,9 +27,9 @@ DB_PASSWORD=4deec965296ff346 @@ -27,9 +27,9 @@ DB_PASSWORD=4deec965296ff346
27 DB_CONNECTION_REMOTE=mysql 27 DB_CONNECTION_REMOTE=mysql
28 DB_PORT_REMOTE=3306 28 DB_PORT_REMOTE=3306
29 DB_HOST_REMOTE=47.251.45.96 29 DB_HOST_REMOTE=47.251.45.96
30 -DB_DATABASE_REMOTE=ai-chat  
31 -DB_USERNAME_REMOTE=ai-chat  
32 -DB_PASSWORD_REMOTE=5E7mKZ3jKMTrD6wK 30 +DB_DATABASE_REMOTE=xzy.ai-assistant
  31 +DB_USERNAME_REMOTE=xzy.ai-assistant
  32 +DB_PASSWORD_REMOTE=yEcGyC78WBNTCYCm
33 33
34 BROADCAST_DRIVER=log 34 BROADCAST_DRIVER=log
35 CACHE_DRIVER=redis 35 CACHE_DRIVER=redis
@@ -64,7 +64,7 @@ PUSHER_APP_CLUSTER=mt1 @@ -64,7 +64,7 @@ PUSHER_APP_CLUSTER=mt1
64 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 64 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
65 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 65 MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
66 66
67 -JWT_SECRET=3QnPNbxLuAN1DToiUSi9IKVtjN8yh0XZE0n3U77owdGF4GqhJfakCMqFyWDc0c75 67 +JWT_SECRET=kIno1YeaMFvyNAADjNfQ4pRhF1Ikyq55YKkyuGM19amYYMMpy0wNlBXMilfqLLEp
68 68
69 # alipay 配置 69 # alipay 配置
70 ALI_APP_ID= 70 ALI_APP_ID=
@@ -79,6 +79,7 @@ WECHAT_MINI_APPID=wxd7941b64f8b9291b @@ -79,6 +79,7 @@ WECHAT_MINI_APPID=wxd7941b64f8b9291b
79 WECHAT_MINI_SECRET=b4d742d47f9f08d3242ca8a426326a7a 79 WECHAT_MINI_SECRET=b4d742d47f9f08d3242ca8a426326a7a
80 80
81 #openai 81 #openai
82 -OPENAI_API_KEY=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 82 +OPENAI_API_KEY=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
  83 +#OPENAI_API_KEY=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
83 #OPENAI_API_KEY=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 84 #OPENAI_API_KEY=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
84 OPENAI_ORGANIZATION=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 85 OPENAI_ORGANIZATION=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
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 */ 16 */
17 namespace App\Models\Traits; 17 namespace App\Models\Traits;
18 18
  19 +use App\Jobs\ChatRecordItemJob;
19 use App\Models\Chat\ChatRecordItem; 20 use App\Models\Chat\ChatRecordItem;
20 use App\Models\System\SystemSetting; 21 use App\Models\System\SystemSetting;
21 use App\Models\User\TimesRecord; 22 use App\Models\User\TimesRecord;
@@ -190,6 +191,84 @@ trait ChatTrait @@ -190,6 +191,84 @@ trait ChatTrait
190 * @param string $type 191 * @param string $type
191 * @return string 192 * @return string
192 */ 193 */
  194 + public static function sendRequest1($open_ai, $send_data, $type = 'stream')
  195 + {
  196 + $answer = '';
  197 + if($type === 'general') {
  198 + $response = $open_ai->chatCompletions()->create(
  199 + new \Tectalic\OpenAi\Models\ChatCompletions\CreateRequest($send_data)
  200 + )->toModel();
  201 +
  202 + $answer = $response->choices[0]->message->content;
  203 + } else {
  204 +
  205 + if ($type === 'chunked') {
  206 + // 设置响应头信息
  207 + header('Access-Control-Allow-Credentials: true');
  208 + // 设置响应头信息
  209 + header('Transfer-Encoding: chunked');
  210 + header('Cache-Control: no-cache');
  211 + header('Access-Control-Allow-Origin: *');
  212 + header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
  213 + header('Access-Control-Allow-Headers: Content-Type');
  214 + header('Connection: keep-alive');
  215 + header('X-Accel-Buffering: no');
  216 +
  217 +// header('Access-Control-Allow-Credentials: true');
  218 +// // 设置响应头信息
  219 +// header('Transfer-Encoding: chunked');
  220 +// header('Content-Type: text/plain');
  221 +// header('Cache-Control: no-cache');
  222 +// header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
  223 +// header('Access-Control-Allow-Headers: Content-Type');
  224 +// header('Connection: keep-alive');
  225 + } elseif ($type === 'stream') {
  226 + header('Content-type: text/event-stream');
  227 + header('Cache-Control: no-cache');
  228 + ob_end_flush();
  229 + }
  230 + $complete = $open_ai->chat($send_data, function ($curl_info, $response) use (&$answer) {
  231 + //闭包函数处理流
  232 + $data = [];
  233 + $lines = explode("\n", $response);
  234 + foreach ($lines as $line) {
  235 + if (!str_contains($line, ':')) {
  236 + continue;
  237 + }
  238 + [$name, $value] = explode(':', $line, 2);
  239 + if ($name == 'data') {
  240 + $data[] = trim($value);
  241 + }
  242 + }
  243 + foreach ($data as $message) {
  244 + if ('[DONE]' === $message) {
  245 + echo "0\r\n\r\n";
  246 + } else {
  247 + $message = json_decode($message, true);
  248 + $content = $message['choices'][0]['delta']['content'] ?? '';
  249 + $answer .= $content;
  250 +// record_log('openai', '内容: ' . $content );
  251 +// echo str_replace("\n", "\\n", $content )."\n\n";
  252 + dump($content);
  253 + echo urlencode($content) . "\r\n";
  254 + }
  255 + }
  256 + ob_flush();
  257 + flush();
  258 + return strlen($response);
  259 + });
  260 + }
  261 +
  262 + return $answer;
  263 + }
  264 +
  265 + /**
  266 + * 发送请求
  267 + * @param $open_ai
  268 + * @param $send_data
  269 + * @param string $type
  270 + * @return string
  271 + */
193 public static function sendRequest($open_ai, $send_data, $type = 'stream') 272 public static function sendRequest($open_ai, $send_data, $type = 'stream')
194 { 273 {
195 $answer = ''; 274 $answer = '';
@@ -244,44 +323,79 @@ trait ChatTrait @@ -244,44 +323,79 @@ trait ChatTrait
244 } elseif ($type === 'stream') { 323 } elseif ($type === 'stream') {
245 header('Content-type: text/event-stream'); 324 header('Content-type: text/event-stream');
246 header('Cache-Control: no-cache'); 325 header('Cache-Control: no-cache');
247 - ob_end_flush();  
248 - $complete = $open_ai->chat($send_data, function ($curl_info, $data) use (&$answer) {  
249 -// record_log($this->log_channel, '开始请求' );  
250 -  
251 - $deltas = explode("\n", $data);  
252 - $content = '';  
253 - foreach ($deltas as $delta) {  
254 - if (strpos($delta, 'data: ') !== 0) { 326 + $complete = $open_ai->chat($send_data, function ($curl_info, $response) use (&$answer) {
  327 + //闭包函数处理流
  328 + $data = [];
  329 + $lines = explode("\n", $response);
  330 + foreach ($lines as $line) {
  331 + if (!str_contains($line, ':')) {
255 continue; 332 continue;
256 } 333 }
257 - $json = json_decode(substr($delta, 6));  
258 - if (isset($json->choices[0]->delta)) {  
259 - $content = $json->choices[0]->delta->content ?? "";  
260 - $answer .= $content;  
261 - } elseif (isset($json->error->message)) {  
262 - $content = $json->error->message;  
263 - } elseif(trim($delta) == "data: [DONE]") {  
264 - $content = " ";  
265 - } else {  
266 - $content = "对不起,我不知道怎么去回答。"; 334 + [$name, $value] = explode(':', $line, 2);
  335 + if ($name == 'data') {
  336 + $data[] = trim($value);
267 } 337 }
268 - echo str_replace("\n", "\\n", $content )."\n\n";  
269 - flush();  
270 } 338 }
  339 + foreach ($data as $message) {
  340 + if ('[DONE]' === $message) {
  341 + echo "0\r\n\r\n";
  342 + } else {
  343 + $message = json_decode($message, true);
  344 + $content = $message['choices'][0]['delta']['content'] ?? '';
  345 + $answer .= $content;
  346 +// record_log('openai', '内容: ' . $content );
  347 +// echo urlencode($content) . "\r\n";
  348 + echo str_replace("\n", "\\n", $content )."\n\n";
271 349
272 -// record_log($this->log_channel, '内容: ' . $content );  
273 -  
274 - if (connection_aborted()) {  
275 - return 0; 350 + }
276 } 351 }
  352 + echo PHP_EOL;
277 353
278 -// echo PHP_EOL;  
279 -// ob_flush();  
280 - return strlen($data); 354 + ob_flush();
  355 + flush();
  356 + return strlen($response);
281 }); 357 });
282 358
283 - echo "event: stop\n";  
284 - echo "data: stopped\n\n"; 359 +
  360 +// ob_end_flush();
  361 +// $complete = $open_ai->chat($send_data, function ($curl_info, $data) use (&$answer) {
  362 +// record_log($this->log_channel, '开始请求' );
  363 +//
  364 +// $deltas = explode("\n", $data);
  365 +// $content = '';
  366 +// foreach ($deltas as $delta) {
  367 +// if (strpos($delta, 'data: ') !== 0) {
  368 +// continue;
  369 +// }
  370 +// $json = json_decode(substr($delta, 6));
  371 +// if (isset($json->choices[0]->delta)) {
  372 +// $content = $json->choices[0]->delta->content ?? "";
  373 +// $answer .= $content;
  374 +// } elseif (isset($json->error->message)) {
  375 +// $content = $json->error->message;
  376 +// } elseif(trim($delta) == "data: [DONE]") {
  377 +// $content = " ";
  378 +// } else {
  379 +// $content = "对不起,我不知道怎么去回答。";
  380 +// }
  381 +//// echo "data: " .str_replace("\n", "\\n", $content )."\n\n";
  382 +// echo str_replace("\n", "\\n", $content )."\n\n";
  383 +// flush();
  384 +// }
  385 +//
  386 +// record_log($this->log_channel, '内容: ' . $content );
  387 +//
  388 +// if (connection_aborted()) {
  389 +// return 0;
  390 +// }
  391 +//
  392 +//// echo PHP_EOL;
  393 +//// ob_flush();
  394 +// return strlen($data);
  395 +// });
  396 +//
  397 +// echo "event: stop\n";
  398 +// echo "data: stopped\n\n";
285 } elseif($type === 'general') { 399 } elseif($type === 'general') {
286 $response = $open_ai->chatCompletions()->create( 400 $response = $open_ai->chatCompletions()->create(
287 new \Tectalic\OpenAi\Models\ChatCompletions\CreateRequest($send_data) 401 new \Tectalic\OpenAi\Models\ChatCompletions\CreateRequest($send_data)
@@ -37,7 +37,7 @@ class ChatRecordItemObserver @@ -37,7 +37,7 @@ class ChatRecordItemObserver
37 { 37 {
38 // 创建成功后,计算每个站点间的距离 38 // 创建成功后,计算每个站点间的距离
39 // $this->syncRecordItem($model); 39 // $this->syncRecordItem($model);
40 - ChatRecordItemJob::dispatch($model); 40 +// ChatRecordItemJob::dispatch($model);
41 } 41 }
42 42
43 /** 43 /**
@@ -38,7 +38,7 @@ class ChatRecordObserver @@ -38,7 +38,7 @@ class ChatRecordObserver
38 // 创建成功后,同步 38 // 创建成功后,同步
39 // $this->syncRecord($model); 39 // $this->syncRecord($model);
40 // 如果服务器是在本地则不进行同步 40 // 如果服务器是在本地则不进行同步
41 - ChatRecordJob::dispatch($model); 41 +// ChatRecordJob::dispatch($model);
42 } 42 }
43 43
44 /** 44 /**
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 */ 16 */
17 namespace App\Services; 17 namespace App\Services;
18 18
  19 +use App\Jobs\ChatRecordItemJob;
  20 +use App\Jobs\ChatRecordJob;
19 use App\Models\Category\Category; 21 use App\Models\Category\Category;
20 use App\Models\Category\CategoryLabel; 22 use App\Models\Category\CategoryLabel;
21 use App\Models\Chat\ChatRecordItem; 23 use App\Models\Chat\ChatRecordItem;
@@ -23,6 +25,7 @@ use App\Models\Chat\ChatRecord; @@ -23,6 +25,7 @@ use App\Models\Chat\ChatRecord;
23 use App\Models\System\SystemSetting; 25 use App\Models\System\SystemSetting;
24 use App\Models\Traits\ChatTrait; 26 use App\Models\Traits\ChatTrait;
25 use App\Models\Traits\SystemSettingTrait; 27 use App\Models\Traits\SystemSettingTrait;
  28 +use Carbon\Carbon;
26 use Illuminate\Contracts\Pagination\LengthAwarePaginator; 29 use Illuminate\Contracts\Pagination\LengthAwarePaginator;
27 use Illuminate\Http\Request; 30 use Illuminate\Http\Request;
28 use Illuminate\Http\Response; 31 use Illuminate\Http\Response;
@@ -45,7 +48,7 @@ class ChatRecordService extends BaseService @@ -45,7 +48,7 @@ class ChatRecordService extends BaseService
45 */ 48 */
46 public function __construct(ChatRecord $model) 49 public function __construct(ChatRecord $model)
47 { 50 {
48 - // 执行父类构造方法 51 + // 执行父类构造方法
49 parent::__construct($model); 52 parent::__construct($model);
50 } 53 }
51 54
@@ -175,6 +178,7 @@ class ChatRecordService extends BaseService @@ -175,6 +178,7 @@ class ChatRecordService extends BaseService
175 try { 178 try {
176 // 创建本次的聊天记录 179 // 创建本次的聊天记录
177 $model = $user->chatRecords()->create($record); 180 $model = $user->chatRecords()->create($record);
  181 +
178 // 创建用户的聊天记录 182 // 创建用户的聊天记录
179 $item = [ 183 $item = [
180 'user_id' => $user->id, 184 'user_id' => $user->id,
@@ -186,18 +190,19 @@ class ChatRecordService extends BaseService @@ -186,18 +190,19 @@ class ChatRecordService extends BaseService
186 'ai_model' => $this->ai_model 190 'ai_model' => $this->ai_model
187 ]; 191 ];
188 192
189 - $result = $model->items()->create($item);  
190 - if (!$result) { 193 + $result1 = $model->items()->create($item);
  194 + if (!$result1) {
191 record_log('chat', '聊天', 'end'); 195 record_log('chat', '聊天', 'end');
192 DB::rollBack(); 196 DB::rollBack();
193 } 197 }
194 198
  199 +
195 // TODO 根据标签组合完整的聊天内容 200 // TODO 根据标签组合完整的聊天内容
196 // 组合方式:分类起始语句+标签名称:标签明细,标签明细,标签名称:标签明细,提问内容 201 // 组合方式:分类起始语句+标签名称:标签明细,标签明细,标签名称:标签明细,提问内容
197 // 前置内容(每个对话场景的前置内容只有一个),标签内容(标签名称+用户选择的标签,每个标签以逗号分隔),用户输入内容 202 // 前置内容(每个对话场景的前置内容只有一个),标签内容(标签名称+用户选择的标签,每个标签以逗号分隔),用户输入内容
198 // 发送消息 203 // 发送消息
199 - $result = $this->send($user, $model, $body_all, $context, $stream);  
200 - if (!$result) { 204 + $result2 = $this->send($user, $model, $body_all, $context, $stream);
  205 + if (!$result2) {
201 record_log('chat', '聊天', 'end'); 206 record_log('chat', '聊天', 'end');
202 return false; 207 return false;
203 } 208 }
@@ -208,7 +213,14 @@ class ChatRecordService extends BaseService @@ -208,7 +213,14 @@ class ChatRecordService extends BaseService
208 DB::commit(); 213 DB::commit();
209 record_log('chat', '聊天', 'end'); 214 record_log('chat', '聊天', 'end');
210 215
211 - return $result; 216 + // 同步
  217 + ChatRecordJob::dispatch($model);
  218 +
  219 + ChatRecordItemJob::dispatch($result1);
  220 +
  221 + ChatRecordItemJob::dispatch($result2);
  222 +
  223 + return $result2;
212 } catch (\Exception $e) { 224 } catch (\Exception $e) {
213 $this->message = $e->getMessage(); 225 $this->message = $e->getMessage();
214 226
@@ -250,6 +262,8 @@ class ChatRecordService extends BaseService @@ -250,6 +262,8 @@ class ChatRecordService extends BaseService
250 return false; 262 return false;
251 } 263 }
252 264
  265 + ChatRecordItemJob::dispatch($result);
  266 +
253 // 发送消息 267 // 发送消息
254 $result = $this->send($user, $model, $body, $model->context, $stream); 268 $result = $this->send($user, $model, $body, $model->context, $stream);
255 DB::commit(); 269 DB::commit();
@@ -343,8 +357,35 @@ class ChatRecordService extends BaseService @@ -343,8 +357,35 @@ class ChatRecordService extends BaseService
343 } 357 }
344 358
345 $model = $query->latest()->first(); 359 $model = $query->latest()->first();
  360 + if (\request('test') == 1) {
  361 + dump($model);
  362 + }
  363 + if (!$model) {
  364 + return ['timeout' => true];
  365 + }
346 // 获取缓存中的数据是否存在,如果存在 366 // 获取缓存中的数据是否存在,如果存在
347 // 获取当前会话的缓存键 367 // 获取当前会话的缓存键
  368 + // add by Richer 于 2023年5月14日15:01:47 由于缓存是在另外服务器,所以只能通过上次聊天的时间怕判断
  369 + $item = $model->items->first();
  370 +// if (\request('test') == 1) {
  371 +// dump($item);
  372 +// }
  373 +
  374 + $item = $model->items->last();
  375 + if (!$item) {
  376 + return ['timeout' => true];
  377 + }
  378 +
  379 + if (Carbon::parse($item->created_at)->diffInMinutes(now()) >= 30){
  380 + return ['timeout' => true];
  381 + }
  382 +
  383 + return ['timeout' => false];
  384 +
  385 +// if (\request('test') == 1) {
  386 +// dump($item);
  387 +// }
  388 +
348 $cache_key = 'chat_context_' . $model->id; 389 $cache_key = 'chat_context_' . $model->id;
349 // 判断当前的聊天是否是基于上下文的聊天,如果是基于上下的文的聊天,需要携带上下文 390 // 判断当前的聊天是否是基于上下文的聊天,如果是基于上下的文的聊天,需要携带上下文
350 // 从缓存中获取当前会话的上下文 391 // 从缓存中获取当前会话的上下文