当前位置: 首页 > ai >正文

AI书签管理工具开发全记录(八):Ai创建书签功能实现

文章目录

  • AI书签管理工具开发全记录(八):AI智能创建书签功能深度解析
    • 前言 📝
    • 1. AI功能设计思路 🧠
      • 1.1 传统书签创建的痛点
      • 1.2 AI解决方案设计
    • 2. 后端API实现 ⚙️
      • 2.1 新增url相关工具方法
      • 2.1 创建后端api
      • 2.2 创建createAIBookmark
      • 2.3 初始化ai模型
      • 2.4 编写promot获取建议信息
      • 2.5 元数据处理
      • 2.6 将信息返回给前端
    • 3. 前端实现 💻
      • 3.1 增加api实现
      • 3.2 调用ai创建书签方法
      • 3.3 创建书签组件
    • 4. AI模型集成说明 🤖
      • 4.1 技术选型
    • 4. 效果展示 🤖
    • 总结 📚

AI书签管理工具开发全记录(八):AI智能创建书签功能深度解析

前言 📝

在前一篇文章中,我们完成了书签和分类管理的基础功能实现。本文将聚焦于项目中特色的功能之一,AI智能创建书签,详细解析如何利用AI技术实现智能化的书签创建流程,大幅提升用户操作效率。

1. AI功能设计思路 🧠

1.1 传统书签创建的痛点

在传统书签管理工具中,用户需要:

  1. 手动输入标题
  2. 复制粘贴URL
  3. 填写描述信息
  4. 选择或创建分类
    整个过程繁琐耗时,有时为了方便,除了url,其它就应付了事,造成后期维护不便。

1.2 AI解决方案设计

我们的AI智能创建功能将实现:

  1. ​自动提取元数据​​:从URL获取网页标题、描述等基础信息
  2. ​智能分类建议​​:基于网页内容自动推荐合适分类
  3. ​一键填充​​:自动填充表单字段
  4. ​分类联动​​:支持直接创建AI建议的分类

完整交互逻辑:
image.png

2. 后端API实现 ⚙️

为了后续和方便多个大模型进行对接,放弃了轻量级的http形式,使用eino框架,此处我们先对接openai模型,提供BaseUrl配置,任何和openai兼容的api都是可以使用的。

2.1 新增url相关工具方法

//internal/utils/url.go
package utilsimport ("bytes""fmt""io""net/http""net/url""regexp""strings""github.com/PuerkitoBio/goquery"
)func IsValidURL(urlStr string) bool {// 检查空字符串if urlStr == "" {return false}// 尝试解析URLu, err := url.ParseRequestURI(urlStr)if err != nil {return false}// 检查Schemeif u.Scheme != "http" && u.Scheme != "https" {return false}// 检查Hostif u.Host == "" {return false}// 简单验证域名格式domainRegex := `^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$`if matched, _ := regexp.MatchString(domainRegex, u.Host); !matched {return false}return true
}type WebpageInfo struct {URL   string `json:"url"`Title string `json:"title"`HTML  string `json:"html"`Text  string `json:"text"`
}func GetWebpageInfo(url string) (*WebpageInfo, error) {// Create a new HTTP clientclient := &http.Client{}// Create a new requestreq, err := http.NewRequest("GET", url, nil)if err != nil {return nil, err}// Set a custom User-Agent headerreq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")// Make the requestresp, err := client.Do(req)if err != nil {return nil, err}defer resp.Body.Close()// Check Content-Type is HTMLcontentType := resp.Header.Get("Content-Type")if !strings.Contains(contentType, "text/html") {return nil, fmt.Errorf("URL does not return HTML content")}// Read response bodybodyBytes, err := io.ReadAll(resp.Body)if err != nil {return nil, err}// Parse HTML documentdoc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))if err != nil {return nil, err}// Extract <title> tag contenttitle := doc.Find("title").Text()if title == "" {title = "Untitled"}// Clean up titletitle = strings.TrimSpace(title)title = strings.Join(strings.Fields(title), " ")// Get first 2000 characters of HTMLhtmlContent := string(bodyBytes)if len(htmlContent) > 2000 {htmlContent = htmlContent[:2000] + "..."}// Extract plain text (with HTML tags removed)textContent := doc.Text()// Clean up text contenttextContent = strings.TrimSpace(textContent)textContent = strings.Join(strings.Fields(textContent), " ")// Limit text content length if neededif len(textContent) > 2000 {textContent = textContent[:2000] + "..."}return &WebpageInfo{URL:   url,Title: title,HTML:  htmlContent,Text:  textContent,}, nil
}func TruncateURLForName(urlStr string) string {u, err := url.Parse(urlStr)if err != nil {return urlStr}// 使用域名作为名称host := u.Hostname()if strings.HasPrefix(host, "www.") {host = host[4:]}return host
}

2.1 创建后端api

//internal/api/api.gobookmark := api.Group("/bookmarks")
{...bookmark.POST("/ai", server.createAIBookmark)
}

2.2 创建createAIBookmark

//internal/api/api.go// CreateAIBookmark godoc
// @Summary 使用AI创建书签
// @Description 根据URL使用AI自动生成书签信息
// @Tags bookmarks
// @Accept json
// @Produce json
// @Param request body models.AIBookmarkRequest true "URL信息"
// @Success 200 {object} models.AIBookmarkResponse
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/bookmarks/ai [post]
func (s *Server) createAIBookmark(c *gin.Context) {// 返回网页信息和AI建议c.JSON(200, nil)
}

2.3 初始化ai模型

//internal/api/api.go// 初始化AI模型
ctx := context.Background()
config := common.AppConfigModel
maxTokens := config.AI.MaxTokens
temperature := float32(config.AI.Temperature)
model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{BaseURL:     config.AI.BaseURL,APIKey:      config.AI.PIKey,Timeout:     time.Duration(config.AI.Timeout) * time.Second,Model:       config.AI.Model,MaxTokens:   &maxTokens,Temperature: &temperature,
})
if err != nil {c.JSON(500, gin.H{"error": "AI模型初始化失败"})return
}

2.4 编写promot获取建议信息

//internal/api/api.go// 准备消息
messages := []*schema.Message{{Role: "system",Content: `你是一个专业的书签助手,负责分析网页内容并生成合适的书签信息。
请遵循以下规则:
1. 分类选择:
- 分类名称应该简洁明了,尽量在10个字符以内,或者2-4个汉字
- 避免使用测试、测试1等明显不合适的分类
- 现有分类中没有合适的分类,优先创建新分类2. 名称生成:
- 使用网页标题作为基础,但要去除网站名称、分隔符等无关信息
- 保持简洁,通常不超过10个汉字
- 如果标题不够清晰,可以根据内容补充关键信息3. 描述生成:
- 总结网页的核心内容和价值
- 突出最重要的2-3个要点
- 使用简洁的语言,不超过50个汉字
- 避免使用"这是一个..."等冗余表达
- 使用markdown格式请以JSON格式返回结果,格式如下:
{
"category": "分类名称",
"name": "书签名称",
"description": "书签描述"
}`,},{Role: "user",Content: fmt.Sprintf(`请分析以下网页内容并生成书签信息:现有分类列表:%v网页信息:
标题:%s
内容:%s请确保:
1. 避免使用测试、测试1等明显不合适的分类
2. 如果已经有适合的分类,不要创建重复的分类,例如已经有ai,就不要创建人工智能等分类
3. 生成的名称要简洁明了
4. 描述要突出网页的核心价值`, categoryNames, webpageInfo.Title, webpageInfo.Text),},
}// 生成回复
response, err := model.Generate(ctx, messages)
if err != nil {c.JSON(500, gin.H{"error": "AI生成失败"})return
}

2.5 元数据处理

//internal/api/api.go// 使用正则表达式去除 ```json 和 ```
re := regexp.MustCompile("(?s)^\\s*```json\\s*(.*?)\\s*```\\s*$")
matches := re.FindStringSubmatch(response.Content)
if len(matches) < 2 {c.JSON(500, gin.H{"error": "无法提取JSON内容"})return
}
cleanedJSON := matches[1]var suggestion models.BookmarkSuggestion
err = json.Unmarshal([]byte(cleanedJSON), &suggestion)
if err != nil {c.JSON(500, gin.H{"error": "解析AI响应失败"})return
}

2.6 将信息返回给前端

//internal/api/api.go// 返回AI建议
aiResp := models.AIBookmarkResponse{Suggestion: suggestion,Webpage:    models.WebpageInfo{Title: webpageInfo.Title, URL: webpageInfo.URL},
}// 返回网页信息和AI建议
c.JSON(200, aiResp)

3. 前端实现 💻

3.1 增加api实现

3.2 调用ai创建书签方法

//web/src/api/bookmark/index.js// 使用AI创建书签
export function createAIBookmark(data) {return request({url: '/api/bookmarks/ai',method: 'post',data})
}

3.3 创建书签组件

创建AICreateDialog,编写ai创建书签组件

<!--web/src/views/bookmark/components/AICreateDialog.vue-->
<template><el-dialogv-model="dialogVisible"title="AI创建书签"width="600px"><el-formref="formRef":model="form":rules="rules"label-width="80px"><el-form-item label="URL" prop="url"><el-input v-model="form.url" placeholder="请输入网页URL"><template #append><el-button @click="handleFetchMetadata" :loading="fetchingMetadata">获取信息</el-button></template></el-input></el-form-item><template v-if="form.metadata"><el-divider>网页信息</el-divider><el-descriptions :column="1" border><el-descriptions-item label="标题">{{ form.metadata.webpage.title }}</el-descriptions-item><el-descriptions-item label="URL">{{ form.metadata.webpage.url }}</el-descriptions-item></el-descriptions><el-divider>AI建议</el-divider><el-form-item label="分类" prop="category_id"><CategorySelectv-model="form.category_id":category-options="categoryOptions":ai-suggestion="form.metadata.suggestion.category"@update:category-options="categoryOptions = $event"/></el-form-item><el-form-item label="标题" prop="title"><el-input v-model="form.title" placeholder="请输入书签标题" /></el-form-item><el-form-item label="描述" prop="description"><el-inputv-model="form.description"type="textarea"placeholder="请输入书签描述"/></el-form-item></template></el-form><template #footer><span class="dialog-footer"><el-button @click="handleCancel">取消</el-button><el-button type="primary" @click="handleSubmit" :disabled="!form.metadata">确定</el-button></span></template></el-dialog>
</template><script setup>
import { ref, defineProps, defineEmits, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { createAIBookmark } from '/@/api/bookmark'
import CategorySelect from './CategorySelect.vue'const props = defineProps({modelValue: {type: Boolean,required: true},categoryOptions: {type: Array,required: true}
})const emit = defineEmits(['update:modelValue', 'success', 'update:categoryOptions'])// 对话框可见性
const dialogVisible = ref(props.modelValue)// 监听modelValue变化
watch(() => props.modelValue, (val) => {dialogVisible.value = val
})// 监听dialogVisible变化
watch(() => dialogVisible.value, (val) => {emit('update:modelValue', val)
})// 表单相关
const formRef = ref(null)
const fetchingMetadata = ref(false)
const form = ref({url: '',title: '',description: '',category_id: undefined,metadata: null
})// 移除不再需要的变量和函数
const categoryDialogVisible = ref(false)
const categoryForm = ref({name: '',description: ''
})// URL验证函数
const validateUrl = (rule, value, callback) => {...
}// 表单验证规则
const rules = {...
}// 获取网页元数据
const handleFetchMetadata = async () => {...
}// 处理提交
const handleSubmit = async () => {//...
}// 处理取消
const handleCancel = () => {...
}
</script><style scoped>
...
</style> 

4. AI模型集成说明 🤖

4.1 技术选型

我们采用了eino框架,很方便对接多种ai模型
根据需求可以采取不容策略。

  1. ​本地模型​​:使用ollama等可以方便运行多种本地ai模型,例如qwen3系列
    • 优点:数据隐私性好
    • 缺点:需要较强的服务器资源
  2. ​第三方API​​:如OpenAI、Google AI等
    • 优点:开发简单,效果较好
    • 缺点:有API调用成本

如果对隐私没有那么高需求,可以试试chatglmGLM-4-Flash-250414模型。开发阶段采用了该模型,对于这种简单需求基本够用,最重要的是免费。
如果对隐私要求极高,可以试试ollama,目前对推理模型没有做适配,需要修改代码。

4. 效果展示 🤖

点击ai创建书签,输入url
image.png

获取信息
image.png

对分类不满意,现存的分类也没有合适的,可以点击新建分类
image.png

可以选择新建的分类
image.png

总结 📚

本文深入实现了AI智能创建书签功能,主要包括:

  1. ​智能化流程​​:简化创建书签步骤
  2. ​精准分析​​:结合元数据提取和AI内容理解
  3. ​无缝体验​​:分类建议与创建的联动设计

http://www.xdnf.cn/news/10281.html

相关文章:

  • MSMQ消息队列》》Rabbit MQ》》安装延迟插件、延迟消息
  • 3D-激光SLAM笔记
  • Rollup打包输出产物遇到的一个坑。(分享心得)
  • Redis缓存问题重点详解
  • 57、IdentityServer4概述
  • [创业之路-398]:企业战略管理案例分析-战略意图是使命、愿景可聚焦、可量化、可落地、可实现、具象化的3-5年左右的目标
  • 三步问题 --- 动态规划
  • 二叉搜索树——AVL
  • 小红书 发评论 分析 x-s x-t
  • 在win10/11下Node.js安装配置教程
  • 网络编程1_网络编程引入
  • Centos环境下安装/重装MySQL完整教程
  • [SC]SystemC在CPU/GPU验证中的应用(二)
  • 【数据结构】图的存储(邻接矩阵与邻接表)
  • Spring Data Redis 实战指南
  • Java对象克隆:从浅到深的奥秘
  • 秒杀系统—5.第二版升级优化的技术文档三
  • Brighter 的线程模型:为何专用线程驱动异步消息泵
  • Python(十四)
  • Vue-自定义指令
  • *JavaScript中的Symbol类型:唯一标识符的艺术
  • # STM32F103 PA0到PA4多路ADC采集配置和采集程序
  • SQL进阶之旅 Day 9:高级索引策略
  • sass高阶应用
  • 基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统
  • resubmit v1.2.0 新特性支持类级别防止重复提交
  • 深度学习总结(40)
  • 数据集笔记:SeekWorld
  • 【Java笔记】Spring IoC DI
  • YOLOv8 移动端升级:借助 GhostNetv2 主干网络,实现高效特征提取