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

相关推荐
破z晓1 小时前
javascript 导出excel表
开发语言·javascript·excel
小罗水3 小时前
第17章 后台管理端与演示支持
javascript·vue.js·ecmascript
极连AI3 小时前
极连AI平台解读、Codex5.6仅需0.01倍率,无需Token焦虑,极速响应
人工智能·gpt·chatgpt·aigc·ai编程·ai写作·gpu算力
猫猫不是喵喵.4 小时前
Vue3 hooks 函数
vue
万少4 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端
宁风NF4 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
猫猫不是喵喵.5 小时前
Vue3 中 computed 计算属性与 watch、watchEffect 监听
前端·javascript·vue.js
猫猫不是喵喵.6 小时前
Vue2 的 Vuex 状态管理与 Vue3 的 Pinia 状态管理
前端·javascript·vue.js
嘉哥AI工具笔记6 小时前
Codex 连续任务总被打断:先让任务可恢复,再判断要不要升 GPT Pro
gpt
看昭奚恤哭7 小时前
Flutter 布局核心思想
开发语言·javascript·flutter