搭建自己的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

相关推荐
alikami16 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda1 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
User_undefined1 小时前
uniapp Native.js原生arr插件服务发送广播到uniapp页面中
android·javascript·uni-app
麦兜*1 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue
陈大爷(有低保)1 小时前
uniapp小案例---趣味打字坤
前端·javascript·vue.js
veminhe1 小时前
uni-app使用组件button遇到的问题
uni-app·vue
博客zhu虎康2 小时前
ElementUI 的 form 表单校验
前端·javascript·elementui