当前位置:   article > 正文

VUE3 + TypeScript 仿ChatGPT前端UI_vue3模仿gpt

vue3模仿gpt

基于html5 开发的仿ChatGPT的前端UI项目,采用了VUE3 + TypeScript等技术选型开发,实现了基本的消息会话、PDF会话、新增会话、删除会话、会话历史、Token统计等新增功能。

  • 消息会话、新建会话、会话历史、删除会话、Token统计、PDF文件上传等功能代码如下:
  1. <script setup lang='ts'>
  2. import { ref } from 'vue'
  3. import { useRoute } from 'vue-router'
  4. import { router } from '@/router'
  5. import { useScroll } from './hooks/useScroll'
  6. import VuePdfApp from "vue3-pdf-app"
  7. import "vue3-pdf-app/dist/icons/main.css"
  8. import { encode } from 'gpt-tokenizer'
  9. const { scrollRef, scrollToBottom } = useScroll()
  10. // Conversation and PDF preview panel toggle control
  11. let showTab = ref<string>("nav-tab-chat")
  12. let tabWidth = ref<string>("")
  13. // vue3-pdf-app UI configuration
  14. let pdfFile = ref<string>("")
  15. const config = ref<{}>({
  16. sidebar: true,
  17. toolbar: {
  18. toolbarViewerLeft: {
  19. findbar: true,
  20. previous: true,
  21. next: true,
  22. pageNumber: false,
  23. },
  24. toolbarViewerRight: {
  25. presentationMode: true,
  26. openFile: false,
  27. print: false,
  28. download: false,
  29. viewBookmark: false,
  30. },
  31. toolbarViewerMiddle: {
  32. zoomOut: true,
  33. zoomIn: true,
  34. scaleSelectContainer: true,
  35. }
  36. },
  37. })
  38. // Message input box
  39. const prompt = ref<string>('')
  40. // Loading state and button state
  41. const buttonDisabled = ref<boolean>(false)
  42. // Get uuid from URL params
  43. const route = useRoute()
  44. let { uuid } = route.params as { uuid: string }
  45. interface Conversation {
  46. title: string;
  47. uuid: string;
  48. isEdit: boolean;
  49. createDate: string;
  50. lastChatContent: string;
  51. active: boolean;
  52. }
  53. interface Message {
  54. send: {
  55. model: string;
  56. messages: {
  57. role: string;
  58. content: string;
  59. fileName: any;
  60. fileSize: number;
  61. }[];
  62. temperature: number;
  63. };
  64. loading: boolean;
  65. receive?: {
  66. model: string;
  67. choices: {
  68. message?: {
  69. content: string;
  70. };
  71. delta: {
  72. content: string;
  73. };
  74. }[];
  75. };
  76. }
  77. // Conversation list and message list
  78. var conversationList = ref<Conversation[]>([])
  79. var messageList = ref<Message[]>([]);
  80. let conversations = window.localStorage.getItem("chatStore")
  81. if(conversations){
  82. conversationList.value = JSON.parse(conversations)
  83. }
  84. // Check if new conversation
  85. if (!uuid || uuid === '0') {
  86. uuid = Date.now().toString()
  87. // Initialize empty conversation
  88. if(!conversations){
  89. conversationList.value.push({
  90. title: 'New Chat',
  91. uuid: uuid,
  92. isEdit: false,
  93. createDate: new Date().toLocaleString(),
  94. lastChatContent: 'Hello I am ChatGPT3.5...',
  95. active: true
  96. })
  97. }else{
  98. // If has history, get last conversation
  99. let lastConversation = conversationList.value[conversationList.value.length-1]
  100. uuid = lastConversation.uuid
  101. let messages = window.localStorage.getItem(uuid)
  102. if(messages) {
  103. messageList.value = JSON.parse(messages)
  104. }
  105. router.push({ name: 'Chat', params: { uuid } })
  106. }
  107. }else{
  108. // Load current conversation messages
  109. let messages = window.localStorage.getItem(uuid)
  110. if(messages) {
  111. messageList.value = JSON.parse(messages)
  112. }
  113. conversationList.value.forEach((item, index) => {
  114. if(item.uuid == uuid){
  115. item.active = true
  116. }else{
  117. item.active = false
  118. }
  119. })
  120. scrollToBottom()
  121. }
  122. // Set active conversation
  123. function handleAdd() {
  124. // Reset the message record of the new conversation
  125. messageList.value = []
  126. // Reset the active status of the conversation list
  127. conversationList.value.forEach((item, index) => {
  128. item.active = false
  129. })
  130. // Initialize an empty conversation
  131. uuid = Date.now().toString()
  132. conversationList.value.unshift({
  133. title: "New Chat",
  134. uuid: uuid,
  135. isEdit: false,
  136. createDate: new Date().toLocaleString(),
  137. lastChatContent: 'Hello I am ChatGPT3.5...',
  138. active: true
  139. })
  140. // Save the conversation to local storage
  141. window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
  142. }
  143. // Menu toggle
  144. function handleMenu(){
  145. let rootbody = document.getElementById("rootbody")
  146. if (rootbody) {
  147. if(rootbody.classList.value==""){
  148. rootbody.classList.value="open-sidebar-menu"
  149. }else{
  150. rootbody.classList.value=""
  151. }
  152. }
  153. }
  154. // Switch conversation
  155. function handleSwitch(selectedUuid: string) {
  156. uuid = selectedUuid
  157. // Reset message record of the new conversation
  158. let messages = window.localStorage.getItem(selectedUuid)
  159. if(messages){
  160. messageList.value = JSON.parse(messages)
  161. }else{
  162. messageList.value = []
  163. }
  164. // Reset active status of the conversation list
  165. conversationList.value.forEach((item, index) => {
  166. if(item.uuid == selectedUuid){
  167. item.active = true
  168. }else{
  169. item.active = false
  170. }
  171. })
  172. router.push({ name: 'Chat', params: { uuid } })
  173. }
  174. // File upload related
  175. var fileName = ref()
  176. var fileSize = ref<number>(0)
  177. var formattedFileSize = ref<string>('0B')
  178. var fileUploadCard = ref<boolean>(false)
  179. var fileContent = ref()
  180. // Handle file upload
  181. function handleUpload(e: Event) {
  182. const target = e.target as HTMLInputElement;
  183. if(target.files && target.files[0].size >= 5 * 1024 * 1024){
  184. alert('Maximum file size limit is 5MB')
  185. return
  186. }else if (!target.files || target.files.length === 0) {
  187. alert('Please select a file')
  188. return
  189. }
  190. // Set file upload style
  191. fileName.value = target.files[0].name
  192. fileSize.value = target.files[0].size
  193. formatFileSize()
  194. // Preview PDF
  195. showTab.value = 'nav-tab-doc'
  196. tabWidth.value = 'width: 60%'
  197. pdfFile.value = URL.createObjectURL(target.files[0])
  198. // Upload file and extract content
  199. const formData = new FormData()
  200. formData.append('doc', target.files[0])
  201. fetch(import.meta.env.VITE_API_UPLOAD, {
  202. method: 'POST',
  203. body: formData,
  204. })
  205. .then(response => response.text())
  206. .catch(error => console.error('Error:', error))
  207. .then(function (docContent) {
  208. if (typeof docContent !== 'string') {
  209. alert("Failed to extract file content")
  210. return
  211. }
  212. const tokens = encode(docContent)
  213. if(tokens.length > 4096){
  214. alert("Exceeded maximum token limit of 4096")
  215. fileName.value = ''
  216. fileSize.value = 0
  217. formattedFileSize.value = '0B'
  218. }else{
  219. // Set the extracted content
  220. fileContent.value = docContent
  221. // Show file upload card
  222. fileUploadCard.value = true
  223. }
  224. })
  225. }
  226. function handleBackChat(){
  227. showTab.value = 'nav-tab-chat'
  228. tabWidth.value = ''
  229. }
  230. function handleBackDoc(){
  231. showTab.value = 'nav-tab-doc'
  232. tabWidth.value = 'width: 40%'
  233. }
  234. // Format file size in Bytes, KB, MB, GB
  235. function formatFileSize() {
  236. if (fileSize.value < 1024) {
  237. formattedFileSize.value = fileSize.value + 'B';
  238. } else if (fileSize.value < (1024*1024)) {
  239. var temp = fileSize.value / 1024
  240. formattedFileSize.value = temp.toFixed(2) + 'KB'
  241. } else if (fileSize.value < (1024*1024*1024)) {
  242. var temp = fileSize.value / (1024*1024)
  243. formattedFileSize.value = temp.toFixed(2) + 'MB'
  244. } else {
  245. var temp = fileSize.value / (1024*1024*1024);
  246. formattedFileSize.value = temp.toFixed(2) + 'GB'
  247. }
  248. }
  249. // Submit message
  250. function handleSubmit() {
  251. onConversation()
  252. }
  253. // Stream request to ChatGPT3.5
  254. async function onConversation() {
  255. let message = prompt.value
  256. if (!message || message.trim() === '')
  257. return
  258. // Clear input box and disable button
  259. prompt.value = ''
  260. buttonDisabled.value = true
  261. fileUploadCard.value = false
  262. // Send message (for local display, not directly sent to GPT)
  263. messageList.value.push({
  264. send: {
  265. model: "gpt-3.5-turbo-1106",
  266. messages: [
  267. {
  268. role: "user",
  269. content: message,
  270. fileName: fileName.value,
  271. fileSize: fileSize.value,
  272. },
  273. ],
  274. temperature: 0.7,
  275. },
  276. loading: true,
  277. });
  278. scrollToBottom()
  279. // Stream request to ChatGPT3.5
  280. try {
  281. if(fileContent.value){
  282. message += ', Uploaded file content: ' + fileContent.value
  283. }
  284. let data = {
  285. "model": "gpt-3.5-turbo-1106",
  286. "messages": [{"role": "user", "content": message }],
  287. "temperature": 0.7,
  288. "stream": true
  289. }
  290. let headers = {
  291. 'Content-Type': 'application/json',
  292. 'Authorization': 'Bearer ' + import.meta.env.VITE_API_KEY,
  293. }
  294. // Send request
  295. let response = await fetch(import.meta.env.VITE_APP_URL, {
  296. method: 'POST',
  297. headers: headers,
  298. body: JSON.stringify(data)
  299. })
  300. // Reset file upload related states immediately after sending to ChatGPT
  301. fileName.value = ''
  302. fileSize.value = 0
  303. formattedFileSize.value = '0B'
  304. if (!response.ok) {
  305. throw new Error('Network response was not ok')
  306. }
  307. // Read the data returned from the stream
  308. const reader = response.body?.getReader();
  309. const textDecoder = new TextDecoder()
  310. let result = true
  311. while (reader && result) {
  312. // Get a chunk
  313. const { done, value } = await reader.read()
  314. if (done) {
  315. console.log('Stream ended')
  316. result = false
  317. // Restore button state
  318. buttonDisabled.value = false
  319. fileContent.value = ''
  320. // Save current messages
  321. window.localStorage.setItem(uuid, JSON.stringify(messageList.value))
  322. window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
  323. break
  324. }
  325. // Convert chunk string to array
  326. let chunkText = textDecoder.decode(value)
  327. chunkText = chunkText.replace(/data:/g, '')
  328. let results = chunkText.split('\n\n').filter(Boolean)
  329. // Iterate through the array and process multiple chunks
  330. for (let i = 0; i < results.length; i++) {
  331. var chunk = results[i]
  332. if (chunk.indexOf('DONE') == -1) {
  333. var chunkData = JSON.parse(chunk)
  334. if (chunkData.choices[0].delta.content) {
  335. if (!messageList.value[messageList.value.length - 1].receive) {
  336. // If it is the first result, set the state directly
  337. messageList.value[messageList.value.length - 1].receive = chunkData
  338. messageList.value[messageList.value.length - 1].loading = false
  339. } else {
  340. const lastMessage = messageList.value[messageList.value.length - 1]?.receive;
  341. if (lastMessage && lastMessage.choices[0].delta.content) {
  342. lastMessage.choices[0].delta.content += chunkData.choices[0].delta.content;
  343. }
  344. }
  345. scrollToBottom()
  346. }
  347. }
  348. }
  349. }
  350. } catch (e) {
  351. console.log(e)
  352. }
  353. }
  354. function handleDele(selectedUuid: string){
  355. // Reset the active state of the conversation list
  356. conversationList.value.forEach((item, index) => {
  357. if(item.uuid == selectedUuid){
  358. conversationList.value.splice(index,1)
  359. // Save the conversation to local storage
  360. window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
  361. return false
  362. }
  363. })
  364. // Reset the message records of the new conversation
  365. if(uuid == selectedUuid){
  366. let messages = window.localStorage.getItem(selectedUuid)
  367. if(messages){
  368. window.localStorage.removeItem(selectedUuid)
  369. messageList.value = []
  370. }
  371. }
  372. }
  373. </script>
  374. <template>
  375. <div id="layout" class="theme-cyan">
  376. <!-- Sidebar -->
  377. <div class="navigation navbar justify-content-center py-xl-4 py-md-3 py-0 px-3">
  378. <a href="#" title="ChatGPT-UI" class="brand">
  379. <svg class="logo" viewBox="0 0 128 128" width="24" height="24" data-v-c0161dce=""><path fill="#42b883" d="M78.8,10L64,35.4L49.2,10H0l64,110l64-110C128,10,78.8,10,78.8,10z" data-v-c0161dce=""></path><path fill="#35495e" d="M78.8,10L64,35.4L49.2,10H25.6L64,76l38.4-66H78.8z" data-v-c0161dce=""></path></svg>
  380. </a>
  381. <div class="nav flex-md-column nav-pills flex-grow-1" role="tablist" aria-orientation="vertical">
  382. <a class="mb-xl-3 mb-md-2 nav-link active" data-toggle="pill" href="#" role="tab">
  383. <i class="zmdi zmdi-comment-alt"></i> <!-- Chat -->
  384. </a>
  385. <a class="mb-xl-3 mb-md-2 nav-link d-none d-sm-block flex-grow-1" data-toggle="pill" href="#" role="tab">
  386. <i class="zmdi zmdi-layers"></i> <!-- Layers -->
  387. </a>
  388. <a class="mt-xl-3 mt-md-2 nav-link light-dark-toggle" href="#">
  389. <i class="zmdi zmdi-brightness-2"></i> <!-- Light/Dark Mode -->
  390. <input class="light-dark-btn" type="checkbox">
  391. </a>
  392. <a class="mt-xl-3 mt-md-2 nav-link d-none d-sm-block" href="#" role="tab">
  393. <i class="zmdi zmdi-settings"></i> <!-- Settings -->
  394. </a>
  395. </div>
  396. <button type="submit" class="btn sidebar-toggle-btn shadow-sm" @click="handleMenu">
  397. <i class="zmdi zmdi-menu"></i> <!-- Menu -->
  398. </button>
  399. </div>
  400. <!-- Sidebar -->
  401. <div class="sidebar border-end py-xl-4 py-3 px-xl-4 px-3" :style="tabWidth">
  402. <div class="tab-content">
  403. <!-- Chat Records -->
  404. <div class="tab-pane fade active show" id="nav-tab-chat" role="tabpanel" v-if="showTab === 'nav-tab-chat'">
  405. <div class="d-flex justify-content-between align-items-center mb-4">
  406. <h3 class="mb-0 text-primary">ChatGPT-UI</h3>
  407. <div>
  408. <button class="btn btn-dark" type="button" @click="handleAdd">New Chat</button></div>
  409. </div>
  410. <ul class="chat-list">
  411. <li class="header d-flex justify-content-between ps-3 pe-3 mb-1">
  412. <span>RECENT CHATS</span>
  413. </li>
  414. <li v-for="(item, index) in conversationList" :class="[item.active ? 'active' : '']" @click="handleSwitch(item.uuid)">
  415. <div class="hover_action">
  416. <button type="button" class="btn btn-link text-info"><i class="zmdi zmdi-eye"></i></button>
  417. <button type="button" class="btn btn-link text-danger" @click="handleDele(item.uuid)"><i class="zmdi zmdi-delete"></i></button>
  418. </div>
  419. <a href="#" class="card">
  420. <div class="card-body">
  421. <div class="media">
  422. <div class="avatar me-3">
  423. <span class="status rounded-circle"></span>
  424. <img class="avatar rounded-circle" :style="[item.active ? 'filter:grayscale(0)' : 'filter:grayscale(1)']" src="../assets/chatgpt.jpg" alt="avatar"></div>
  425. <div class="media-body overflow-hidden">
  426. <div class="d-flex align-items-center mb-1">
  427. <h6 class="text-truncate mb-0 me-auto">{{ item.title }}</h6>
  428. <p class="small text-muted text-nowrap ms-4 mb-0">{{ item.createDate }}</p></div>
  429. <div class="text-truncate">{{ item.lastChatContent }}</div></div>
  430. </div>
  431. </div>
  432. </a>
  433. </li>
  434. </ul>
  435. </div>
  436. <!-- end Chat Records -->
  437. <!-- PDF Preview -->
  438. <div class="tab-pane fade active show" id="nav-tab-doc" role="tabpanel" v-if="showTab === 'nav-tab-doc'">
  439. <div class="d-flex justify-content-between align-items-center mb-4">
  440. <h3 class="mb-0 text-primary">ChatGPT-PDF</h3>
  441. <div>
  442. <button class="btn btn-dark" type="button" @click="handleBackChat">Back Chat</button></div>
  443. </div>
  444. <ul class="chat-list">
  445. <li class="header d-flex justify-content-between ps-3 pe-3 mb-1">
  446. <span>PREVIEW</span>
  447. </li>
  448. <li>
  449. <vue-pdf-app style="height: 100vh;" :config="config" :pdf="pdfFile"></vue-pdf-app>
  450. </li>
  451. </ul>
  452. </div>
  453. <!-- end PDF Preview -->
  454. </div>
  455. </div>
  456. <div class="main px-xl-5 px-lg-4 px-3">
  457. <div class="chat-body">
  458. <!-- Chat Box Header -->
  459. <div class="chat-header border-bottom py-xl-4 py-md-3 py-2">
  460. <div class="container-xxl">
  461. <div class="row align-items-center">
  462. <div class="col-6 col-xl-4">
  463. <div class="media">
  464. <div class="me-3 show-user-detail">
  465. <span class="status rounded-circle"></span>
  466. <img class="avatar rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
  467. <div class="media-body overflow-hidden">
  468. <div class="d-flex align-items-center mb-1">
  469. <h6 class="text-truncate mb-0 me-auto">ChatGPT 3.5</h6></div>
  470. <div class="text-truncate">Powered By OpenAI</div></div>
  471. </div>
  472. </div>
  473. </div>
  474. </div>
  475. </div>
  476. <!-- end Chat Box Header -->
  477. <div class="chat-content" id="scrollRef" ref="scrollRef">
  478. <div class="container-xxl">
  479. <ul class="list-unstyled py-4" v-for="(item, index) of messageList">
  480. <!-- Right Message -->
  481. <li class="d-flex message right">
  482. <div class="message-body">
  483. <span class="date-time text-muted"></span>
  484. <div class="message-row d-flex align-items-center justify-content-end">
  485. <div class="message-content border p-3">
  486. {{ item.send.messages[0].content }}
  487. <div class="attachment" v-show="item.send.messages[0].fileName" @click="handleBackDoc">
  488. <div class="media mt-2">
  489. <div class="avatar me-2">
  490. <div class="avatar rounded no-image orange">
  491. <i class="zmdi zmdi-collection-pdf"></i>
  492. </div>
  493. </div>
  494. <div class="media-body overflow-hidden">
  495. <h6 class="text-truncate mb-0">{{ item.send.messages[0].fileName }}</h6>
  496. <span class="file-size">{{ item.send.messages[0].fileSize }}</span>
  497. </div>
  498. </div>
  499. </div>
  500. </div>
  501. </div>
  502. </div>
  503. </li>
  504. <!-- end Right Message -->
  505. <!-- Left Message -->
  506. <li class="d-flex message" v-if="item.receive">
  507. <div class="mr-lg-3 me-2">
  508. <img class="avatar sm rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
  509. <div class="message-body">
  510. <span class="date-time text-muted">{{ item.receive.model }}</span>
  511. <div class="message-row d-flex align-items-center">
  512. <div class="message-content p-3">
  513. <v-md-preview :text="item.receive.choices[0].message?item.receive.choices[0].message.content:item.receive.choices[0].delta.content"></v-md-preview>
  514. </div>
  515. </div>
  516. </div>
  517. </li>
  518. <!-- end Left Message -->
  519. <!-- Loading Message -->
  520. <li class="d-flex message" v-if="item.loading">
  521. <div class="mr-lg-3 me-2">
  522. <img class="avatar sm rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
  523. <div class="message-body">
  524. <div class="message-row d-flex align-items-center">
  525. <div class="message-content p-3">
  526. <div class="wave">
  527. <span class="dot"></span>
  528. <span class="dot"></span>
  529. <span class="dot"></span>
  530. </div>
  531. </div>
  532. </div>
  533. </div>
  534. </li>
  535. <!-- end Loading Message -->
  536. </ul>
  537. </div>
  538. </div>
  539. <!-- Message Input Box -->
  540. <div class="chat-footer border-top py-xl-4 py-lg-2 py-2">
  541. <div class="container-xxl">
  542. <div class="row">
  543. <div class="col-12">
  544. <form @submit.prevent="handleSubmit">
  545. <div class="input-group align-items-center">
  546. <input type="text" v-model="prompt" class="form-control border-0 pl-0" placeholder="Type your message...">
  547. <div class="attachment" v-show="fileUploadCard" @click="handleBackDoc">
  548. <div class="media mt-2">
  549. <div class="avatar me-2">
  550. <div class="avatar rounded no-image orange">
  551. <i class="zmdi zmdi-collection-pdf"></i>
  552. </div>
  553. </div>
  554. <div class="media-body overflow-hidden">
  555. <h6 class="text-truncate mb-0">{{ fileName }}</h6>
  556. <span class="file-size">{{ fileSize }}</span>
  557. </div>
  558. </div>
  559. </div>
  560. <div class="input-group-append">
  561. <span class="input-group-text border-0">
  562. <input type="file" accept="application/pdf" id="fileInput" ref="file" @change="handleUpload" style="display:none">
  563. <button class="btn btn-sm btn-link text-muted" data-toggle="tooltip" @click="($refs.file as HTMLInputElement).click()" title="" type="button" data-original-title="Attachment">
  564. <i class="zmdi zmdi-attachment font-22"></i>
  565. </button>
  566. </span>
  567. </div>
  568. <div class="input-group-append">
  569. <span class="input-group-text border-0 pr-0">
  570. <button type="submit" class="btn btn-primary" :disabled="buttonDisabled" @click="handleSubmit">
  571. <i class="zmdi zmdi-mail-send"></i>
  572. </button>
  573. </span>
  574. </div>
  575. </div>
  576. </form>
  577. </div>
  578. </div>
  579. </div>
  580. </div>
  581. <!-- end Message Input Box -->
  582. </div>
  583. </div>
  584. <!-- Empty Page -->
  585. <div class="main px-xl-5 px-lg-4 px-3" style="display:none">
  586. <div class="chat-body">
  587. <div class="chat d-flex justify-content-center align-items-center h-100 text-center py-xl-4 py-md-3 py-2">
  588. <div class="container-xxl">
  589. <div class="avatar lg avatar-bg me-auto ms-auto mb-5">
  590. <img class="avatar lg rounded-circle border" src="../assets/user.png" alt="">
  591. <span class="a-bg-1"></span>
  592. <span class="a-bg-2"></span>
  593. </div>
  594. <h5 class="font-weight-bold">Hey, Robert!</h5>
  595. <p>Please select a chat to start messaging.</p>
  596. </div>
  597. </div>
  598. </div>
  599. </div>
  600. <!-- end Empty Page -->
  601. </div>
  602. </template>
  • PDF会话、PDF预览、文件上传和附件信息等功能,功能代码如下:

  1. // vue3-pdf-app 组件配置,预览PDF界面配置
  2. let pdfFile = ref<string>("")
  3. const config = ref<{}>({
  4. sidebar: true,
  5. toolbar: { //工具条配置
  6. toolbarViewerLeft: {
  7. findbar: true, //查找功能
  8. previous: true,//上一页
  9. next: true,//下一页
  10. pageNumber: false, //页号
  11. },
  12. toolbarViewerRight: { // 工具条右侧
  13. presentationMode: true,
  14. openFile: false,
  15. print: false,
  16. download: false,
  17. viewBookmark: false,
  18. },
  19. toolbarViewerMiddle: { //工具条中间位置
  20. zoomOut: true,
  21. zoomIn: true,
  22. scaleSelectContainer: true,
  23. }
  24. },
  25. })
  1. <ul class="chat-list">
  2. <li class="header d-flex justify-content-between ps-3 pe-3 mb-1">
  3. <span>PREVIEW</span>
  4. </li>
  5. <li>
  6. // PDF预览组件
  7. <vue-pdf-app style="height: 100vh;" :config="config" :pdf="pdfFile"></vue-pdf-app>
  8. </li>
  9. </ul>

环境要求

开发版本: Node 18.15.0 + Vue 3

项目配置

ChatGPT UI的默认配置存储在“.env”文件中。您将需要覆盖一些值以使ChatGPT UI在本地运行。

  1. VITE_APP_URL = 填写OpenAI的API地址或第三方封装的API,格式示例:https://api.openai.com/v1/chat/completions
  2. VITE_API_KEY= 填写OpenAI的ApiKey, 格式示例: sk-FihjnhGKO14eYLmPpV1234BlbkFJUq1lS0RNenkDsjgGLopx
  3. VITE_API_UPLOAD = 填写解析pdf文件的API地址,格式示例: http://domain.com/upload/pdf

项目初始化

npm install

运行开发环境

npm run dev

访问项目

http://localhost:1003

构建生产环境

npm run build

下载地址

https://gitee.com/supertinys_ryan/chatgpt-ui

https://github.com/uniconnector/chatgpt-ui

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/906876
推荐阅读
相关标签
  

闽ICP备14008679号