[Golang] 纯文本查看 复制代码
package main
import (
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"regexp"
"strings"
"time"
)
// 去https://platform.xiaomimimo.com/#/console/api-keys 创建一个key 免费15天
const xiaomiKey = "sk-xxxx"
// 定义请求结构体
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_completion_tokens"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"top_p"`
Stream bool `json:"stream"`
Stop *string `json:"stop"`
FrequencyPenalty float64 `json:"frequency_penalty"`
PresencePenalty float64 `json:"presence_penalty"`
Thinking Thinking `json:"thinking"`
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type Thinking struct {
Type string `json:"type"`
}
// 预编译正则表达式以提高性能
var (
markdownRegexes = map[string]*regexp.Regexp{
"header": regexp.MustCompile(`^#+\s`),
"orderedList": regexp.MustCompile(`^\s*\d+\.\s`),
"bold": regexp.MustCompile(`\*\*(.*?)\*\*`),
"italic": regexp.MustCompile(`\*(.*?)\*`),
"inlineCode": regexp.MustCompile("`([^`]+)`"),
"codeBlock": regexp.MustCompile("```"),
"listItem": regexp.MustCompile(`^\s*[-*+]\s`),
"link": regexp.MustCompile(`\[.*?\]\(.*?\)`),
"quote": regexp.MustCompile(`^\s*>`),
"table": regexp.MustCompile(`^\s*\|.*\|`),
}
)
// 检测内容是否为Markdown格式
func isMarkdown(content string) bool {
content = strings.TrimSpace(content)
// 使用预编译的正则表达式进行快速检测
if markdownRegexes["header"].MatchString(content) ||
markdownRegexes["orderedList"].MatchString(content) ||
markdownRegexes["listItem"].MatchString(content) ||
markdownRegexes["link"].MatchString(content) ||
markdownRegexes["quote"].MatchString(content) ||
markdownRegexes["table"].MatchString(content) {
return true
}
// 检测简单的模式
if strings.Contains(content, "```") ||
strings.Contains(content, "`") ||
strings.Contains(content, "**") ||
strings.Contains(content, "*") ||
strings.Contains(content, "---") {
return true
}
return false
}
// 渲染Markdown内容为纯文本格式
func renderMarkdown(content string) string {
lines := strings.Split(content, "\n")
var result []string
inCodeBlock := false
for _, line := range lines {
// 处理代码块
if strings.HasPrefix(strings.TrimSpace(line), "```") {
inCodeBlock = !inCodeBlock
if inCodeBlock {
result = append(result, "═══ 代码块 ═══")
} else {
result = append(result, "═══ 代码块结束 ═══")
}
continue
}
if inCodeBlock {
// 代码块内内容直接添加,添加缩进
result = append(result, " "+line)
continue
}
// 处理标题
if strings.HasPrefix(line, "# ") {
result = append(result, "🔹 "+strings.TrimSpace(line[2:])+" 🔹")
} else if strings.HasPrefix(line, "## ") {
result = append(result, " ▸ "+strings.TrimSpace(line[3:])+" ◂")
} else if strings.HasPrefix(line, "### ") {
result = append(result, " • "+strings.TrimSpace(line[4:]))
} else if strings.HasPrefix(line, "#### ") {
result = append(result, " ◦ "+strings.TrimSpace(line[5:]))
} else if strings.HasPrefix(line, "- ") || strings.HasPrefix(line, "* ") {
// 处理无序列表
result = append(result, " • "+strings.TrimSpace(line[2:]))
} else if markdownRegexes["orderedList"].MatchString(line) {
// 处理有序列表
result = append(result, " "+strings.TrimSpace(line))
} else if strings.HasPrefix(line, "> ") {
// 处理引用
result = append(result, "│ "+strings.TrimSpace(line[2:]))
} else if strings.Contains(line, "**") {
// 处理粗体
result = append(result, strings.ReplaceAll(line, "**", "✨"))
} else if strings.Contains(line, "*") {
// 处理斜体
result = append(result, strings.ReplaceAll(line, "*", "~"))
} else if strings.Contains(line, "`") {
// 处理行内代码
result = append(result, strings.ReplaceAll(line, "`", "「"))
} else if strings.TrimSpace(line) == "---" {
// 处理分隔线
result = append(result, "─────────────────")
} else {
// 普通文本
result = append(result, line)
}
}
return strings.Join(result, "\n")
}
// 渲染Markdown内容为HTML格式
func renderMarkdownToHTML(content string) string {
// 转义HTML特殊字符
content = html.EscapeString(content)
lines := strings.Split(content, "\n")
var result []string
inCodeBlock := false
codeLang := ""
for _, line := range lines {
// 处理代码块
if strings.HasPrefix(strings.TrimSpace(line), "```") {
if !inCodeBlock {
inCodeBlock = true
// 提取语言标识
codeLang = strings.TrimSpace(line[3:])
if codeLang == "" {
codeLang = "text"
}
result = append(result, `<div class="code-block">`)
result = append(result, `<div class="code-header">`+codeLang+`</div>`)
result = append(result, `<pre><code>`)
} else {
inCodeBlock = false
result = append(result, `</code></pre>`)
result = append(result, `</div>`)
}
continue
}
if inCodeBlock {
// 代码块内内容直接添加
result = append(result, line)
continue
}
// 处理标题
if strings.HasPrefix(line, "# ") {
result = append(result, `<h1>`+strings.TrimSpace(line[2:])+`</h1>`)
} else if strings.HasPrefix(line, "## ") {
result = append(result, `<h2>`+strings.TrimSpace(line[3:])+`</h2>`)
} else if strings.HasPrefix(line, "### ") {
result = append(result, `<h3>`+strings.TrimSpace(line[4:])+`</h3>`)
} else if strings.HasPrefix(line, "#### ") {
result = append(result, `<h4>`+strings.TrimSpace(line[5:])+`</h4>`)
} else if strings.HasPrefix(line, "##### ") {
result = append(result, `<h5>`+strings.TrimSpace(line[6:])+`</h5>`)
} else if strings.HasPrefix(line, "###### ") {
result = append(result, `<h6>`+strings.TrimSpace(line[7:])+`</h6>`)
} else if strings.HasPrefix(line, "- ") || strings.HasPrefix(line, "* ") {
// 处理无序列表
result = append(result, `<li>`+strings.TrimSpace(line[2:])+`</li>`)
} else if markdownRegexes["orderedList"].MatchString(line) {
// 处理有序列表
content := strings.TrimSpace(line)
numStr := markdownRegexes["orderedList"].FindString(content)
text := strings.TrimSpace(content[len(numStr):])
result = append(result, `<li value="`+strings.TrimSpace(strings.TrimSuffix(numStr, "."))+`">`+text+`</li>`)
} else if strings.HasPrefix(line, "> ") {
// 处理引用
result = append(result, `<blockquote>`+strings.TrimSpace(line[2:])+`</blockquote>`)
} else if strings.Contains(line, "**") {
// 处理粗体
result = append(result, `<p>`+markdownRegexes["bold"].ReplaceAllString(line, `<strong>$1</strong>`)+`</p>`)
} else if strings.Contains(line, "*") {
// 处理斜体
result = append(result, `<p>`+markdownRegexes["italic"].ReplaceAllString(line, `<em>$1</em>`)+`</p>`)
} else if strings.Contains(line, "`") {
// 处理行内代码
result = append(result, `<p>`+markdownRegexes["inlineCode"].ReplaceAllString(line, `<code>$1</code>`)+`</p>`)
} else if strings.TrimSpace(line) == "---" {
// 处理分隔线
result = append(result, `<hr>`)
} else if strings.TrimSpace(line) == "" {
// 空行
result = append(result, `<br>`)
} else {
// 普通文本
result = append(result, `<p>`+line+`</p>`)
}
}
return strings.Join(result, "\n")
}
// 获取API密钥 可以自己改成config配置型
func getAPIKey() string {
return xiaomiKey
}
// 处理AI请求
func askAI(question string) (string, error) {
url := "https://api.xiaomimimo.com/v1/chat/completions"
apiKey := getAPIKey()
// 创建请求结构体
request := ChatRequest{
Model: "mimo-v2-flash",
MaxTokens: 1024,
Temperature: 0.8,
TopP: 0.95,
Stream: false,
Stop: nil,
FrequencyPenalty: 0,
PresencePenalty: 0,
Thinking: Thinking{
Type: "disabled",
},
Messages: []Message{
{
Role: "system",
Content: "你是代码高手,中文回复我的问题,帮我解决各种代码问题",
},
{
Role: "user",
Content: question,
},
},
}
// 序列化为JSON
jsonData, err := json.Marshal(request)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
// 创建HTTP请求
req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonData)))
if err != nil {
return "", fmt.Errorf("创建请求失败: %v", err)
}
// 设置请求头
req.Header.Add("api-key", apiKey)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "Go-Web-Client/1.0")
// 创建带超时的客户端
client := &http.Client{
Timeout: 30 * time.Second,
}
// 发送请求
res, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("请求失败: %v", err)
}
defer res.Body.Close()
// 检查响应状态
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP错误: %d %s", res.StatusCode, res.Status)
}
// 读取响应体
body, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
// 解析响应
var response struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
TotalTokens int `json:"total_tokens"`
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
} `json:"usage"`
Model string `json:"model"`
}
if err := json.Unmarshal(body, &response); err != nil || len(response.Choices) == 0 {
return "", fmt.Errorf("解析响应失败: %v", err)
}
return response.Choices[0].Message.Content, nil
}
// 生成主页面HTML
func generateMainPage() string {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>李狗蛋调用小米AI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
max-width: 900px;
width: 90%;
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
color: #2c3e50;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 1.1em;
}
.chat-container {
padding: 30px;
min-height: 500px;
display: flex;
flex-direction: column;
}
.input-area {
display: flex;
gap: 10px;
margin-top: 20px;
}
.input-field {
flex: 1;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 16px;
transition: border-color 0.3s;
}
.input-field:focus {
outline: none;
border-color: #667eea;
}
.ask-btn {
padding: 15px 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.ask-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.ask-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.loading::after {
content: '';
animation: dots 1.5s infinite;
}
@keyframes dots {
0%, 20% { content: ''; }
40% { content: '.'; }
60% { content: '..'; }
80%, 100% { content: '...'; }
}
.response-area {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
min-height: 300px;
display: none;
flex: 1;
margin-bottom: 20px;
overflow-y: auto;
}
.response-area.show {
display: flex;
flex-direction: column;
}
.response-area > * {
flex: 1;
}
.response-area p:first-child {
margin-top: 0;
}
.response-area p:last-child {
margin-bottom: 0;
}
.code-block {
margin: 20px 0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.code-header {
background: #34495e;
color: white;
padding: 10px 15px;
font-size: 0.9em;
font-weight: bold;
}
pre {
margin: 0;
background: #f8f9fa !important;
padding: 15px;
overflow-x: auto;
border: 1px solid #e9ecef;
}
code {
background: #f1f3f4;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
color: #e91e63;
}
pre code {
background: transparent !important;
padding: 0;
color: #333;
}
blockquote {
border-left: 4px solid #3498db;
padding-left: 20px;
margin: 20px 0;
color: #7f8c8d;
font-style: italic;
background: #f8f9fa;
padding: 15px 20px;
border-radius: 0 6px 6px 0;
}
h1, h2, h3, h4, h5, h6 {
color: #2c3e50;
margin: 20px 0 10px 0;
}
h1 { font-size: 2em; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
h2 { font-size: 1.5em; border-bottom: 2px solid #ecf0f1; padding-bottom: 8px; }
h3 { font-size: 1.2em; }
hr {
border: none;
height: 2px;
background: linear-gradient(to right, transparent, #bdc3c7, transparent);
margin: 30px 0;
}
p {
margin: 15px 0;
line-height: 1.6;
}
strong {
color: #2c3e50;
font-weight: bold;
}
em {
color: #8e44ad;
font-style: italic;
}
.meta-info {
background: #e8f4fd;
padding: 15px;
border-radius: 6px;
margin: 20px 0;
border-left: 4px solid #2196f3;
}
ul, ol {
padding-left: 30px;
margin: 15px 0;
}
li {
margin: 8px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🤖李狗蛋调用小米AI</h1>
<p>我是您的专业编程助手,随时为您解答代码问题</p>
</div>
<div class="chat-container">
<div id="loadingDiv" class="loading" style="display: none;">AI正在思考中</div>
<div id="responseDiv" class="response-area"></div>
<div class="input-area">
<input type="text" id="questionInput" class="input-field" placeholder="请输入您的编程问题..." />
<button id="askBtn" class="ask-btn">提问</button>
</div>
</div>
</div>
<script>
async function askQuestion() {
const input = document.getElementById('questionInput');
const btn = document.getElementById('askBtn');
const loading = document.getElementById('loadingDiv');
const response = document.getElementById('responseDiv');
const question = input.value.trim();
if (!question) {
alert('请输入问题');
return;
}
// 显示加载状态
btn.disabled = true;
loading.style.display = 'block';
response.classList.remove('show');
response.innerHTML = '';
try {
const res = await fetch('/ask', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: question })
});
if (!res.ok) {
throw new Error("HTTP错误: " + res.status);
}
const data = await res.json();
if (data.error) {
throw new Error(data.error);
}
// 显示响应
response.innerHTML = data.html;
response.classList.add('show');
// 滚动到响应区域顶部
response.scrollTop = 0;
} catch (error) {
response.innerHTML = '<div style="color: red; padding: 20px;">❌ 错误: ' + error.message + '</div>';
response.classList.add('show');
} finally {
btn.disabled = false;
loading.style.display = 'none';
}
}
// 回车键提交
document.getElementById('questionInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
askQuestion();
}
});
// 页面加载时聚焦输入框
window.onload = function() {
document.getElementById('questionInput').focus();
};
</script>
</body>
</html>`
}
func main() {
// 设置路由
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, generateMainPage())
})
http.HandleFunc("/ask", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
Question string `json:"question"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// 输出用户输入的内容到控制台
fmt.Printf("🔍 用户输入: %s\n", req.Question)
fmt.Printf("📅 时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Printf("📏 输入长度: %d 字符\n", len(req.Question))
fmt.Println("─────────────────────────────────")
// 调用AI
content, err := askAI(req.Question)
if err != nil {
fmt.Printf("❌ AI调用失败: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 输出AI响应信息到控制台
fmt.Printf("🤖 AI响应长度: %d 字符\n", len(content))
fmt.Printf("📝 响应预览: %.50s%s\n", content, func() string {
if len(content) > 50 {
return "..."
} else {
return ""
}
}())
fmt.Println("═══════════════════════════════════")
// 生成HTML响应
var htmlContent string
if isMarkdown(content) {
htmlContent = renderMarkdownToHTML(content)
} else {
htmlContent = `<div class="content"><p>` + html.EscapeString(content) + `</p></div>`
}
response := map[string]string{
"html": htmlContent,
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(response)
})
// 启动服务器
port := "8081"
fmt.Printf("🚀 服务器启动成功!\n")
fmt.Printf("🌐 请在浏览器中访问: http://localhost:%s\n", port)
fmt.Printf("💬 输入问题即可与AI助手对话\n")
fmt.Printf("⏹️ 按 Ctrl+C 停止服务器\n")
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Printf("服务器启动失败: %v\n", err)
}
}