我们将会使用go语言的gin库来搭建一个属于自己的网页版GPT
一、准备工作
我们需要使用到ollama,如何下载和使用[ollama](Ollama完整教程:本地LLM管理、WebUI对话、Python/Java客户端API应用 - 老牛啊 - 博客园)请看这个文档
有过gin环境的直接运行就可以,如果没有就根据文档内容去下载相关配置库
二、使用步骤
git clone https://github.com/yty666zsy/gin_web_ai.git
cd gin_web_ai
ollama run "大模型的名称"
这里需要注意的是要在chat.html文件中修改模型的名称,要不然找不到模型,在这个位置
然后运行代码,如下图所示
"然后开启一个新的终端"
go run main.go
这里需要注意的是端口号可以适当的进行修改,防止某些端口被占用的情况
然后本地访问127.0.0.1:8088就能打开网址进行愉快的聊天啦
同时后台同步打印信息以便日志管理
下面把代码贴出来
chat.html
<!DOCTYPE html>
<html>
<head><title>Ollama 聊天界面</title><style>#chat-container {width: 800px;margin: 0 auto;padding: 20px;}#messages {height: 400px;border: 1px solid #ccc;overflow-y: auto;margin-bottom: 20px;padding: 10px;}.message {margin: 10px 0;padding: 10px;border-radius: 5px;}.user {background-color: #e3f2fd;text-align: right;}.assistant {background-color: #f5f5f5;}</style>
</head>
<body><div id="chat-container"><div id="messages"></div><div><select id="model"><option value="llama3-cn">Llama 3 中文</option></select><input type="text" id="message" style="width: 80%;" placeholder="输入消息..."><button onclick="sendMessage()">发送</button></div></div><script>const messagesDiv = document.getElementById('messages');const messageInput = document.getElementById('message');const modelSelect = document.getElementById('model');let chatHistory = [];function addMessage(role, content) {const messageDiv = document.createElement('div');messageDiv.className = `message ${role}`;messageDiv.textContent = content;messagesDiv.appendChild(messageDiv);messagesDiv.scrollTop = messagesDiv.scrollHeight;}async function sendMessage() {const content = messageInput.value.trim();if (!content) return;const requestData = {model: modelSelect.value,messages: chatHistory};console.log('发送请求:', requestData);addMessage('user', content);chatHistory.push({role: 'user', content: content});messageInput.value = '';try {const response = await fetch('/chat', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({model: modelSelect.value,messages: chatHistory})});const data = await response.json();chatHistory.push(data.message);addMessage('assistant', data.message.content);} catch (error) {console.error('Error:', error);addMessage('assistant', '发生错误,请重试。');}}messageInput.addEventListener('keypress', function(e) {if (e.key === 'Enter') {sendMessage();}});</script>
</body>
</html>
main.go
package mainimport ("bytes""encoding/json""fmt"//"io/ioutil""bufio""net""net/http""os""github.com/gin-gonic/gin"
)type ChatRequest struct {Model string `json:"model"`Messages []Message `json:"messages"`
}type Message struct {Role string `json:"role"`Content string `json:"content"`
}type ChatResponse struct {Message Message `json:"message"`
}// 添加新的结构体用于处理流式响应
type StreamResponse struct {Model string `json:"model"`CreatedAt string `json:"created_at"`Message Message `json:"message"`Done bool `json:"done"`DoneReason string `json:"done_reason,omitempty"`
}// 在main函数前添加一个新的函数
func findAvailablePort(startPort int) int {for port := startPort; port < startPort+100; port++ {listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))if err == nil {listener.Close()return port}}return startPort // 如果没找到可用端口,返回初始端口
}func main() {r := gin.Default()// 加载模板r.LoadHTMLGlob("templates/*")// 设置静态文件路径r.Static("/static", "./static")// 首页路由r.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "chat.html", nil)})// 处理聊天请求的APIr.POST("/chat", func(c *gin.Context) {var req ChatRequestif err := c.BindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}prettyJSON, _ := json.MarshalIndent(req, "", " ")fmt.Printf("发送到Ollama的请求:\n%s\n", string(prettyJSON))jsonData, _ := json.Marshal(req)resp, err := http.Post("http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(jsonData))if err != nil {fmt.Printf("调用Ollama API错误: %v\n", err)c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}defer resp.Body.Close()// 使用scanner来读取流式响应scanner := bufio.NewScanner(resp.Body)var fullContent stringfor scanner.Scan() {line := scanner.Text()var streamResp StreamResponseif err := json.Unmarshal([]byte(line), &streamResp); err != nil {fmt.Printf("解析流式响应行错误: %v\n", err)continue}// 累积内容fullContent += streamResp.Message.Content// 如果是最后一条消息if streamResp.Done {response := ChatResponse{Message: Message{Role: "assistant",Content: fullContent,},}c.JSON(http.StatusOK, response)return}}if err := scanner.Err(); err != nil {fmt.Printf("读取流式响应错误: %v\n", err)c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}})// 从环境变量获取端口,如果未设置则使用默认值port := os.Getenv("PORT")if port == "" {port = "8088"}r.Run(":" + port)
}