接续 Go-LLM-CPP 专案,继续扩充前端聊天室功能
一. 专案目录架构:
go-llm-cpp/
├── bin/ # 第三方依赖
│ ├── go-llama.cpp/ # 封裝 GGUF 模型推理(CGo)
│ └── llm-go/ # prompt 构建 + 回合管理(Go)
│
├── cmd/ # 可执行应用
│ └── main.go # CLI / HTTP server 入口点
│
├── config/
│ └── persona.yaml # 人格模板(系统 prompt + 选项)
│
├── llm/ # LLM 封装与上下文逻辑
│ ├── model.go # go-llama.cpp 模型初始化与推理
│ ├── session.go # prompt 组装、上下文构建、角色管理
│ └── memory.go # 对话记忆保存(快取 / SQLite)
│
├── api/ # Web API 接口逻辑
│ ├── server.go # FastAPI / Echo router 定义
│ └── handler.go # 对话处理器(chat/infer endpoint)
│
├── web-ui/ # Nuxt3 + Tailwind 前端聊天室(独立模组)
│ ├── pages/index.vue # 主对话页
│ ├── components/ # Chat bubble / RoleCard 元件
│ └── composables/ # API 对接与 socket 管理
│
├── swagger/ # OpenAPI v3 文件
│ └── swagger.yaml # chat 接口规格与参数说明
│
├── go.mod # Go 模组依赖
├── go.sum
└── README.md
要模组功能解说
模组 | 功能描述 |
---|---|
llm/model.go | 初始化 GGUF 模型、推理 token(封装 go-llama.cpp) |
llm/session.go | 管理上下文、构造完整 prompt、切换角色模板 |
config/persona.yaml | 配置角色语气、风格、禁止词与偏好用语 |
api/ | 提供 /chat、/roles、/history 等 Web API 接口 |
web-ui/ | Vue + Tailwind 结合 Nuxt 开发的聊天室介面(支援 streaming、socket typing 效果) |
本篇专案新增功能
• ✅ 地部署 LLM,无需外部 API
• ✅ GGUF 格式(Gemma / Llama2 / Mistral 等模型)
• ✅ 格对话模板切换(配置于 persona.yaml)
• ✅ Prompt 管理结构化(使用 llm-go 思路)
• ✅ CLI + Web API + Web UI 三端整合
• ✅ 支援 SQLite 快取历史与上下文
二. 前端模組
Vue Chat UI
<template>
<div class="chat-container">
<div class="chat-header">Go LLM Chatbot</div>
<div class="chat-messages" ref="messages">
<div v-for="(msg, index) in messages" :key="index" :class="['chat-message', msg.role]">
<strong>{{ msg.role === 'user' ? '你' : '機器人' }}:</strong> {{ msg.content }}
</div>
</div>
<div class="chat-input">
<input v-model="input" @keyup.enter="sendMessage" placeholder="請輸入訊息..." />
<button @click="sendMessage">發送</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'ChatApp',
data() {
return {
input: '',
messages: []
}
},
methods: {
async sendMessage() {
if (!this.input.trim()) return;
const userInput = this.input;
this.messages.push({ role: 'user', content: userInput });
this.input = '';
try {
const res = await axios.post('/api/chat', { message: userInput });
const reply = res.data.reply;
this.messages.push({ role: 'assistant', content: reply });
this.$nextTick(() => {
const msgBox = this.$refs.messages;
msgBox.scrollTop = msgBox.scrollHeight;
});
} catch (err) {
this.messages.push({ role: 'assistant', content: '出現錯誤,請稍後再試。' });
}
}
}
}
</script>
<style scoped>
.chat-container {
max-width: 600px;
margin: auto;
border: 1px solid #ccc;
border-radius: 8px;
display: flex;
flex-direction: column;
height: 80vh;
background: #fff;
}
.chat-header {
padding: 16px;
font-weight: bold;
background: #42b983;
color: white;
border-bottom: 1px solid #ccc;
}
.chat-messages {
flex: 1;
padding: 16px;
overflow-y: auto;
background: #f9f9f9;
}
.chat-message {
margin-bottom: 12px;
}
.chat-message.user {
text-align: right;
color: #333;
}
.chat-message.assistant {
text-align: left;
color: #2c3e50;
}
.chat-input {
display: flex;
border-top: 1px solid #ccc;
padding: 10px;
}
.chat-input input {
flex: 1;
padding: 8px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
.chat-input button {
margin-left: 10px;
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
nuxt.config.ts
export default defineNuxtConfig({
css: ['@/assets/chat.css'],
modules: ['@pinia/nuxt'],
runtimeConfig: {
public: {
apiBase: '/api'
}
}
})
三. API 接口 - Nuxt 3 与 Vue 3 和后端 go-llm-cpp 串接
于 Nuxt 3 与 Vue 3 所建构的完整聊天室前端架构,整合角色选择、历史保存与 API 对接逻辑,并配合后端 go-llm-cpp 提供的 Web API 使用方式
- 页面:pages/index.vue
<template>
<div class="min-h-screen bg-gray-100 p-6">
<div class="max-w-3xl mx-auto">
<div class="mb-4">
<label class="block font-bold">選擇角色</label>
<select v-model="persona" class="mt-1 block w-full border rounded p-2">
<option value="default">默認</option>
<option value="engineer">工程師</option>
<option value="teacher">老師</option>
</select>
</div>
<div class="bg-white rounded shadow p-4 h-[60vh] overflow-y-auto">
<div v-for="(msg, index) in history" :key="index" class="mb-2">
<div class="font-bold" :class="msg.role === 'user' ? 'text-blue-600' : 'text-green-600'">
{{ msg.role === 'user' ? '你:' : '助手:' }}
</div>
<div class="whitespace-pre-wrap">{{ msg.content }}</div>
</div>
</div>
<form @submit.prevent="send" class="mt-4 flex gap-2">
<input v-model="input" class="flex-1 border rounded p-2" placeholder="輸入訊息..." />
<button class="bg-blue-500 text-white px-4 py-2 rounded" :disabled="loading">
{{ loading ? '回應中...' : '發送' }}
</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useChatStore } from '@/stores/chat'
const chat = useChatStore()
const input = ref('')
const persona = ref('default')
const loading = ref(false)
watch(persona, () => {
chat.setPersona(persona.value)
})
const send = async () => {
if (!input.value) return
loading.value = true
await chat.sendMessage(input.value)
input.value = ''
loading.value = false
}
const history = computed(() => chat.history)
</script>
- 状态管理::stores/chat.ts
// 使用 Pinia 作为状态管理工具。
// stores/chat.ts
import { defineStore } from 'pinia'
interface Message {
role: 'user' | 'assistant'
content: string
}
export const useChatStore = defineStore('chat', {
state: () => ({
persona: 'default',
history: [] as Message[]
}),
actions: {
setPersona(p: string) {
this.persona = p
this.history = [] // 切換角色時清空對話
},
async sendMessage(content: string) {
this.history.push({ role: 'user', content })
const res = await $fetch<{ message: string }>('/api/chat', {
method: 'POST',
body: {
persona: this.persona,
history: this.history,
message: content
}
})
this.history.push({ role: 'assistant', content: res.message })
}
}
})
- API 對接邏輯:server/api/chat.ts
// 使用 Nuxt 3 的 server API 机制连接 go-llm-cpp 提供的后端服务。
// server/api/chat.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const res = await $fetch('http://localhost:11434/api/chat', {
method: 'POST',
body: {
persona: body.persona,
history: body.history,
message: body.message
}
})
return {
message: res.message || '(沒有回應)'
}
})
go-llm-cpp 的 Web API 请确保已支援 POST /api/chat 并接受 persona, history, message。
- 安裝依賴與配置
# 安装依赖
npm install
npm install @pinia/nuxt
# 启用 Pinia
# nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
四. 整合 Tailwind CSS 的 Nuxt 3 聊天室设计
- 安裝 Tailwind CSS
npx nuxi init chat-ui
cd chat-ui
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
- 設定 tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.{js,vue,ts}",
"./pages/**/*.{js,vue,ts}",
"./app.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}"
],
theme: {
extend: {}
},
plugins: []
}
- 在 assets/css/tailwind.css 新增
@tailwind base;
@tailwind components;
@tailwind utilities;
- 在 nuxt.config.ts 加入 Tailwind CSS
export default defineNuxtConfig({
css: ["@/assets/css/tailwind.css"],
modules: ["@pinia/nuxt"]
})
- pages/index.vue 加入 Tailwind 樣式(含角色切換與聊天視窗)
<template>
<div class="min-h-screen bg-gray-100 flex flex-col items-center p-6">
<div class="w-full max-w-3xl space-y-4">
<h1 class="text-2xl font-semibold">LLM 聊天機器人</h1>
<div>
<label class="block text-sm font-medium">選擇角色:</label>
<select v-model="persona" class="mt-1 block w-full border border-gray-300 rounded p-2">
<option v-for="opt in personaOptions" :key="opt" :value="opt">
{{ opt }}
</option>
</select>
</div>
<div class="bg-white rounded-lg shadow p-4 h-[55vh] overflow-y-auto">
<div v-for="(msg, i) in history" :key="i" class="mb-3">
<div :class="msg.role === 'user' ? 'text-blue-600' : 'text-green-600'">
<strong>{{ msg.role === 'user' ? '你' : '助手' }}:</strong>
</div>
<p class="whitespace-pre-wrap">{{ msg.content }}</p>
</div>
</div>
<form @submit.prevent="send" class="flex gap-2">
<input v-model="input" class="flex-1 border border-gray-300 rounded p-2" placeholder="輸入訊息..." />
<button class="bg-blue-600 text-white px-4 py-2 rounded" :disabled="loading">
{{ loading ? "回應中..." : "發送" }}
</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue"
import { useChatStore } from "@/stores/chat"
const input = ref("")
const persona = ref("default")
const loading = ref(false)
const chat = useChatStore()
const history = computed(() => chat.history)
const personaOptions = ["default", "engineer", "teacher"]
onMounted(() => {
chat.setPersona(persona.value)
})
watch(persona, () => {
chat.setPersona(persona.value)
})
const send = async () => {
if (!input.value.trim()) return
loading.value = true
await chat.sendMessage(input.value)
input.value = ""
loading.value = false
}
</script>
- SQLite 快取 / 记忆优化扩展
• 使用 @prisma/client 或 better-sqlite3 建立讯息表
• 将 history 写入 messages.db
• 搭配 embedding(例如 sentence-transformers)进行语义记忆检索
五. 打造本地 LLM 聊天机器人的未来展望
LLM 本地部署的門檻不斷降低,結合如 go-llama.cpp 與 llm-go 的專案已能讓開發者在無需倚賴雲端 API 的情況下,自行構建高效、安全、可擴展的聊天機器人系統。本專案透過模組化的架構設計,實現了 CLI、Web API 與 Vue 前端的完整整合,並支援角色切換、歷史對話保存與即時回應等功能。
参考链接:
本项目 README
🧾 GitCode 開源項目地址:
👉 GitCode - 全球开发者的开源社区,开源代码托管平台
我是一位独立开发者,加入使用者社群,一起讨论私有化 LLM 与 RAG 架构实践,欢迎 Star、Fork、Issue 交流。