搭建自己的GPT

搭建自己的GPT

文章说明

目前GPT的使用比较主流,现有开源大模型,可以拉取到本地进行部署,搭建属于自己的GPT对话工具;主要用于熟悉大模型的本地搭建;本文采用开源的Ollama进行服务提供,官网已提供控制台版本的提问方式,也有采用docker部署Web ui的功能;我选择本地自己搭建一个简单的对话ui,进行简单使用;该模型部署在本机消耗的资源较大,且模型的回答能力不如千问、文心一言等;
采用md-editor-v3,将返回的内容进行markdown格式展示,效果感觉还不错
同时采用indexDB存储对话记录,部署起来也非常便捷
Ollama大模型下载在官网下载即可

核心代码

App.vue代码

html 复制代码
<script setup>
import {nextTick, onBeforeMount, reactive, watch} from "vue";
import {dbOperation} from "@/DbOperation";
import "@/config.js"
import {openDb} from "@/config";
import {confirm, message} from "@/util";
import {MdPreview} from 'md-editor-v3';
import 'md-editor-v3/lib/preview.css';

const data = reactive({
  content: "",
  messageList: [],
});

let isReplying = false;
let container;

function getMaxHeight() {
  container = document.getElementsByClassName("container")[0];
  container.scrollTo({
    top: container.scrollHeight,
    behavior: "smooth"
  });
}

onBeforeMount(() => {
  openDb(() => {
    dbOperation.getAllData((res) => {
      data.messageList = res.data;
      nextTick(() => {
        getMaxHeight();
      });
    });
  });
});

watch(() => data.content, () => {
  if (data.content.length > 500) {
    data.content = String(data.content).slice(0, 500);
  }
});

const avatarRobot = require("@/image/1.jpg");
const avatarUser = require("@/image/2.jpg");

function question() {
  if (isReplying) {
    message("正在对话中", "warning");
    return;
  }
  isReplying = true;

  const messageItem = {
    question: data.content,
    reply: "",
    createTime: new Date(),
  };
  data.messageList.push(messageItem);
  setTimeout(() => {
    const height = container.scrollHeight;
    container.scrollTo({
      top: height,
      behavior: "smooth"
    });
  }, 0);

  fetch(new Request('http://localhost:11434/api/chat', {
    method: 'post',
    mode: "cors",
    body: JSON.stringify({
      "model": "llama3.1",
      "messages": [
        {
          "role": "user",
          "content": data.content
        }
      ],
    }),
  })).then(response => {
    data.content = "";

    const reader = response.body.getReader();
    read();

    function read() {
      reader.read().then(({done, value}) => {
        if (done) {
          isReplying = false;
          dbOperation.add(messageItem, () => {
            message("对话成功", "success");
          });
          return;
        }
        const readContent = new Uint8Array(value);
        const content = JSON.parse(Uint8ArrayToString(readContent)).message.content;
        data.messageList[data.messageList.length - 1].reply += content;

        read();
      }).catch(error => {
        message(error, "error");
      });
    }
  }).catch(error => {
    message(error, "error");
  });
}

function Uint8ArrayToString(fileData) {
  const decoder = new TextDecoder('utf-8');
  return decoder.decode(fileData);
}

function editQuestion(item) {
  data.content = item.question;
}

function copy(item) {
  navigator.clipboard.writeText(item.reply).then(() => {
    message("复制到剪切板", "success");
  });
}

function deleteItem(item) {
  confirm("确认要删除该消息吗?", () => {
    data.messageList = data.messageList.filter(message => message.id !== item.id);
    dbOperation.delete(item.id, () => {
      message("删除成功", "success");
    });
  });
}
</script>

<template>
  <div class="container">
    <div class="message-container">
      <div v-for="item in data.messageList" :key="item.id" class="message-item">
        <div class="create-time">
          {{ item.createTime }}
        </div>
        <div class="user">
          <img :src="avatarUser" alt=""/>
          <p>
            <MdPreview v-model="item.question"/>
          </p>
          <i class="iconfont icon-edit" @click="editQuestion(item)"></i>
        </div>
        <div class="robot">
          <img :src="avatarRobot" alt=""/>
          <p>
            <MdPreview v-model="item.reply"/>
          </p>
          <i class="iconfont icon-copy" @click="copy(item)"></i>
          <i class="iconfont icon-delete" @click="deleteItem(item)"></i>
        </div>
      </div>
    </div>

    <div class="input-container">
      <textarea v-model="data.content"/>
      <p class="tip">{{ data.content.length }}/500</p>
      <i class="iconfont icon-send" @click="question"></i>
    </div>
  </div>
</template>

<style lang="scss">
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

.container {
  min-width: 20rem;
  width: 100vw;
  height: 100vh;
  background-color: #eaedf6;
  overflow: auto;
  position: relative;
  user-select: none;

  .message-container {
    max-width: 70rem;
    width: 100%;
    height: fit-content;
    min-height: calc(100vh - 8rem);
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    padding: 0 1rem 8rem;
    overflow: auto;

    .message-item {
      margin: 1.5rem 0;

      .create-time {
        height: 1rem;
        line-height: 1rem;
        font-size: 0.6rem;
        color: #999;
        margin-bottom: -0.5rem;
        margin-left: 0.5rem;
      }

      .user, .robot {
        margin: 1rem 0;
        display: flex;
        position: relative;

        &:hover .icon-edit, &:hover .icon-copy, &:hover .icon-delete {
          display: block;
        }

        img {
          border-radius: 50%;
          width: 2.5rem;
          height: 2.5rem;
        }

        p {
          flex: 1;
          display: flex;
          align-items: center;
          margin-left: 0.5rem;

          .md-editor-preview-wrapper {
            padding: 0 1rem;
          }
        }

        .icon-edit {
          position: absolute;
          right: 0.5rem;
          bottom: 0;
          transform: translateY(-50%);
          display: none;
        }

        .icon-copy {
          position: absolute;
          right: 1.8rem;
          bottom: 0;
          transform: translateY(-50%);
          display: none;
        }

        .icon-delete {
          position: absolute;
          right: 0.5rem;
          bottom: 0;
          transform: translateY(-50%);
          display: none;
        }
      }
    }
  }

  .input-container {
    max-width: 70rem;
    width: 80%;
    height: 6rem;
    position: fixed;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    background-color: #fff;
    border-radius: 1rem;

    &:focus-within {
      box-shadow: 0 0 1rem 0.1rem #888888;
    }

    textarea {
      outline: none;
      border: none;
      padding: 0.6rem;
      font-size: 1.2rem;
      resize: none;
      width: 100%;
      height: 100%;
      position: absolute;
      left: 0;
      top: 0;
      border-radius: 1rem;
    }

    .tip {
      font-size: 0.6rem;
      position: absolute;
      right: 1rem;
      bottom: 0.5rem;
    }

    .icon-send {
      font-size: 1.5rem;
      position: absolute;
      right: 1rem;
      bottom: 2rem;
      cursor: pointer;
    }
  }
}
</style>

效果展示

安装启动Ollama

问答演示1

问答演示2

问答演示3

源码下载

搭建自己的GPT

相关推荐
阿伟来咯~15 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端20 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱23 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai32 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨33 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
aPurpleBerry3 小时前
JS常用数组方法 reduce filter find forEach
javascript
ZL不懂前端3 小时前
Content Security Policy (CSP)
前端·javascript·面试