├── upload_tmp └── 1 ├── audio_result └── 1 ├── .env.example ├── .gitignore ├── clean.php ├── system ├── inc.php ├── Url.php ├── function.php └── OpenAi.php ├── README.md ├── README.en.md ├── LICENSE ├── chat.php ├── index.php └── recorder.mp3.min.js /upload_tmp/1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /audio_result/1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_URL = https://api.openai.com/ 2 | API_KEY = sk-xxx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略.vscode文件夹 2 | .vscode/ 3 | .env 4 | .gitignore 5 | .envbackup -------------------------------------------------------------------------------- /clean.php: -------------------------------------------------------------------------------- 1 | setBaseURL($conf['API_URL']); 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English README](README.en.md) 2 | 3 | # 项目简介 4 | 5 | 这个项目是一个简单的 demo,它使用了 TTS-1、Whisper 和 GPT-3.5-turbo 来进行对话交互。 6 | 7 | ## 技术栈 8 | 9 | - 前端使用 `bootstarp`、`vue2` 和 `recorder` 进行构建。 10 | - 前台支持选择浏览器语音转文字(低延迟)或者调用 OpenAI 的 Whisper(高精度)。 11 | - 前台可以上传 `mp3` 格式文件到 PHP,通过调用 TTS 得到 `opus` 音频以保证效率。 12 | - 访问 `clean.php` 可以清理缓存文件。 13 | 14 | ## 修改与更新 15 | 16 | - 去除了 Composer 依赖,改为内置修改版的 [OpenAI 库](https://github.com/orhanerday/open-ai),同时加入了与 TTS 相关的函数。 17 | - 现在使用自定义简单函数获取 `.env` 变量。 18 | 19 | # 部署教程 20 | 21 | 为了最佳效果,推荐使用 PHP 8.1。 22 | 23 | 1. `.env.example` 修改为 `.env`,并配置其中的 OpenAI Key 和 URL(如果需要使用代理,则修改 URL)。 24 | 25 | 2.必须使用https否则无法录音 -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # Project Introduction 2 | 3 | This project is a simple demo that utilizes TTS-1, Whisper, and GPT-3.5-turbo for conversational interactions. 4 | 5 | ## Technology Stack 6 | 7 | - The front end is built using `bootstarp`, `vue2`, and `recorder`. 8 | - The front end supports selecting browser-based speech-to-text (low latency) or calling OpenAI's Whisper (high accuracy). 9 | - Users can upload `mp3` format files to PHP and obtain `opus` audio through TTS to ensure efficiency. 10 | - Accessing `clean.php` allows for the clearing of cache files. 11 | 12 | ## Modifications and Updates 13 | 14 | - Removed Composer dependencies, and instead integrated a modified version of the [OpenAI library](https://github.com/orhanerday/open-ai), incorporating functions related to TTS. 15 | - Now utilizes a custom simple function to retrieve `.env` variables. 16 | 17 | # Deployment Guide 18 | 19 | For optimal performance, it is recommended to use PHP 8.1. 20 | 21 | 1. Rename `.env.example` to `.env` and configure the OpenAI Key and URL inside it (modify the URL if a proxy is required). 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 xy3xy3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /chat.php: -------------------------------------------------------------------------------- 1 | $ai_msg]); 21 | } elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['audio'])) { 22 | $audioFile = $_FILES['audio']; 23 | $tempFilePath = $audioFile['tmp_name']; 24 | $tmpfile = __DIR__ . "/upload_tmp/" . md5($tempFilePath) . ".mp3"; 25 | copy($tempFilePath, $tmpfile); 26 | // 语音转文字 27 | $text = audioTotext($tmpfile); 28 | if (empty($text)) { 29 | json("语音转文字失败"); 30 | } 31 | // 获取chat对话 32 | $ai_msg = chat($text); 33 | if (empty($ai_msg)) { 34 | json("ai回答失败"); 35 | } 36 | $audioData = speech($ai_msg, $_POST['voice']); 37 | if (empty($audioData)) { 38 | json("ai回答失败"); 39 | } 40 | //保存.wav到save 41 | $dir = '/audio_result/' . md5($ai_msg) . ".opus"; 42 | $saveDirectory = __DIR__ . $dir; 43 | file_put_contents($saveDirectory, $audioData); 44 | json($dir, 0, ['ai_msg' => $ai_msg]); 45 | } else { 46 | json("没收到文件且没有文本"); 47 | } 48 | -------------------------------------------------------------------------------- /system/Url.php: -------------------------------------------------------------------------------- 1 | $code, 'msg' => $msg]; 5 | //合并extra和data 6 | $data = array_merge($data, $extra); 7 | echo json_encode($data); 8 | exit(); 9 | } 10 | function audioTotext($c_file) 11 | { 12 | global $open_ai; 13 | $c_file = curl_file_create($c_file); 14 | $result = $open_ai->transcribe([ 15 | "model" => "whisper-1", 16 | "file" => $c_file, 17 | ]); 18 | $d = json_decode($result, true); 19 | // if (!isset($d["text"])) { 20 | // print_r($result); 21 | // } 22 | return isset($d["text"]) ? $d["text"] : null; 23 | } 24 | function speech($text, $voice) 25 | { 26 | global $open_ai; 27 | if (empty($voice)) $voice = "echo"; 28 | $result = $open_ai->speech([ 29 | "model" => "tts-1", 30 | "input" => $text, 31 | "voice" => $voice, 32 | "response_format" => "opus" 33 | ]); 34 | return $result; 35 | } 36 | function chat($text) 37 | { 38 | global $open_ai; 39 | $memory_entry = [ 40 | "role" => "user", 41 | "content" => $text 42 | ]; 43 | // 加入记忆 44 | if (isset($_SESSION['memory'])) { 45 | $tmp = $_SESSION['memory']; 46 | } else { 47 | $tmp = []; 48 | } 49 | $tmp[] = $memory_entry; 50 | $arr = [ 51 | 'model' => 'gpt-3.5-turbo-1106', 52 | 'messages' => $tmp, 53 | 'temperature' => 0.7, 54 | 'max_tokens' => 1000, 55 | ]; 56 | $complete = $open_ai->chat($arr); 57 | $d = json_decode($complete, true); 58 | if (isset($d['choices'][0]['message']['content'])) { 59 | $ai_msg = $d['choices'][0]['message']['content']; 60 | memory($text, $ai_msg); 61 | return $ai_msg; 62 | } else { 63 | // print_r($complete); 64 | return null; 65 | } 66 | } 67 | function memory($u, $a) 68 | { 69 | if (empty($u) || empty($a)) return; 70 | // 初始化或检查是否已经存在 $_SESSION['memory'] 71 | if (isset($_SESSION['memory'])) { 72 | // 只取最后的6对(如果有),然后加入新的 73 | $_SESSION['memory'][] = [ 74 | "role" => "user", 75 | "content" => $u 76 | ]; 77 | $_SESSION['memory'][] = [ 78 | "role" => "assistant", 79 | "content" => $a 80 | ]; 81 | // 截取最后的6对 82 | $_SESSION['memory'] = array_slice($_SESSION['memory'], -6); 83 | } else { 84 | // 如果 $_SESSION['memory'] 不存在,创建并添加第一对对话 85 | $_SESSION['memory'] = [ 86 | [ 87 | "role" => "user", 88 | "content" => $u 89 | ], 90 | [ 91 | "role" => "assistant", 92 | "content" => $a 93 | ] 94 | ]; 95 | } 96 | } 97 | function deleteDirectory($dir) 98 | { 99 | if (!file_exists($dir)) { 100 | return true; 101 | } 102 | 103 | if (!is_dir($dir)) { 104 | return unlink($dir); 105 | } 106 | 107 | foreach (scandir($dir) as $item) { 108 | if ($item == '.' || $item == '..') { 109 | continue; 110 | } 111 | 112 | if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) { 113 | return false; 114 | } 115 | } 116 | } 117 | function loadEnvVariables($filePath) 118 | { 119 | $variables = array(); 120 | 121 | if (file_exists($filePath)) { 122 | $file = fopen($filePath, 'r'); 123 | 124 | while (($line = fgets($file)) !== false) { 125 | $line = trim($line); 126 | 127 | // 跳过以#开头的注释行和空行 128 | if (empty($line) || $line[0] === '#') { 129 | continue; 130 | } 131 | 132 | // 解析变量名和值 133 | $parts = explode('=', $line, 2); 134 | $name = trim($parts[0]); 135 | $value = isset($parts[1]) ? trim($parts[1]) : ''; 136 | 137 | // 移除变量值中的引号 138 | if (preg_match('/^"(.+)"$/', $value, $matches) === 1) { 139 | $value = $matches[1]; 140 | } 141 | 142 | $variables[$name] = $value; 143 | } 144 | 145 | fclose($file); 146 | } 147 | 148 | return $variables; 149 | } 150 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |B&&(_[m][b/3]=b%3+1);for(b=1;b<4;b++){var H=w[b-1],C=w[b];Math.max(H,C)<4e4&&H<1.7*C&&C<1.7*H&&(1==b&&_[m][0]<=_[m][b]&&(_[m][0]=0),_[m][b]=0)}_[m][0]<=c.nsPsy.lastAttacks[m]&&(_[m][0]=0),3!=c.nsPsy.lastAttacks[m]&&_[m][0]+_[m][1]+_[m][2]+_[m][3]==0||((k=0)!=_[m][1]&&0!=_[m][0]&&(_[m][1]=0),0!=_[m][2]&&0!=_[m][1]&&(_[m][2]=0),0!=_[m][3]&&0!=_[m][2]&&(_[m][3]=0)),m<2?l[m]=k:0==k&&(l[0]=l[1]=0),i[m]=c.tot_ener[m]}}(e,t,a,s,n,r,_,w,R,A),function(e,t){var a=e.internal_flags;e.short_blocks!=ye.short_block_coupled||0!=t[0]&&0!=t[1]||(t[0]=t[1]=0);for(var s=0;s=t.lowpass2&&(a=Math.min(a,r)),t.lowpass1