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

article/2025/6/19 6:00:23

文章目录

  • 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.hkcw.cn/article/wUVnOWwvgH.shtml

相关文章

【计算机网络】第3章:传输层—概述、多路复用与解复用、UDP

目录 一、概述和传输层服务 二、多路复用与解复用 三、无连接传输&#xff1a;UDP 四、总结 &#xff08;一&#xff09;多路复用与解复用 &#xff08;二&#xff09;UDP 一、概述和传输层服务 二、多路复用与解复用 三、无连接传输&#xff1a;UDP 四、总结 &#xff08…

leetcode hot100刷题日记——30.两数之和

解答&#xff1a; 方法一&#xff1a;迭代 迭代大致过程就是&#xff1a; 算两条链表的当前位的和&#xff0c;加上上一位留下来的进位&#xff0c;就是新链表的当前位的数字。计算当前的进位。 这样&#xff0c;我们迭代需要的东西是&#xff1a;链表1&#xff0c;链表2&…

飞腾D2000与FPGA结合的主板

UD VPX-404是基于高速模拟/数字采集回放、FPGA信号实时处理、CPU主控、高速SSD实时存储架构开发的一款高度集成的信号处理组合模块&#xff0c;采用6U VPX架构&#xff0c;模块装上外壳即为独立整机&#xff0c;方便用户二次开发。 UD VPX-404模块的国产率可达到100%&#xff0…

Baklib知识中台驱动服务升级

知识中台架构升级路径 在数字化转型背景下&#xff0c;Baklib通过重构知识中台的技术底座与服务体系&#xff0c;形成了分层解耦的模块化架构。该架构以四库体系为核心支撑&#xff0c;通过分布式存储引擎与语义分析算法的深度耦合&#xff0c;实现了多源异构数据的标准化接入…

NHANES指标推荐:ALI

文章题目&#xff1a;A cross-sectional study examining the relationship between the advanced lung cancer inflammation index and prostate cancer 中文标题&#xff1a;一项检查晚期肺癌炎症指数与前列腺癌之间关系的横断面研究 发表杂志&#xff1a;Journal of Health…

Python训练打卡Day38

Dataset和Dataloader类 知识点回顾&#xff1a; Dataset类的__getitem__和__len__方法&#xff08;本质是python的特殊方法&#xff09;Dataloader类minist手写数据集的了解 在遇到大规模数据集时&#xff0c;显存常常无法一次性存储所有数据&#xff0c;所以需要使用分批训练的…

leetcode付费题 353. 贪吃蛇游戏解题思路

贪吃蛇游戏试玩:https://patorjk.com/games/snake/ 问题描述 设计一个贪吃蛇游戏,要求实现以下功能: 初始化游戏:给定网格宽度、高度和食物位置序列移动操作:根据指令(上、下、左、右)移动蛇头规则: 蛇头碰到边界或自身身体时游戏结束(返回-1)吃到食物时蛇身长度增加…

NLP学习路线图(十三):正则表达式

在自然语言处理&#xff08;NLP&#xff09;的浩瀚宇宙中&#xff0c;原始文本数据如同未经雕琢的璞玉。而文本预处理&#xff0c;尤其是其中至关重要的正则表达式技术&#xff0c;正是将这块璞玉转化为精美玉器的核心工具集。本文将深入探讨正则表达式在NLP文本预处理中的原理…

【算法】动态规划

一、动态规划的基本思想 动态规划算法与分治法类似&#xff0c;其基本思想也是将待求解的较大规模问题分解为若干个较小的子问题&#xff0c;先求解子问题&#xff0c;再从这些子问题的解得到原问题的解。 但动态规划法有自己的特点。分治法的子问题相互独立&#xff0c;适合动…

设计模式——原型设计模式(创建型)

摘要 本文详细介绍了原型设计模式&#xff0c;这是一种创建型设计模式&#xff0c;通过复制现有对象&#xff08;原型&#xff09;来创建新对象&#xff0c;避免使用new关键字&#xff0c;可提高性能并简化对象创建逻辑。文章阐述了其优点&#xff0c;如提高性能、动态扩展和简…

java程序从服务器端到Lambda函数的迁移与优化

source&#xff1a;https://www.jfokus.se/jfokus24-preso/From-Serverful-to-Serverless-Java.pdf 从传统的服务器端Java应用&#xff0c;到如今的无服务器架构。这不仅仅是技术名词的改变&#xff0c;更是开发模式和运维理念的一次深刻变革。先快速回顾一下我们熟悉的“服务…

57、IdentityServer4概述

IdentityServer4是一个基于ASP.NET Core的开源身份认证和授权框架&#xff0c;实现了OpenID Connect和OAuth 2.0协议。它为现代应用程序提供集中式的身份验证和授权服务&#xff0c;支持单点登录&#xff08;SSO&#xff09;、令牌颁发与验证、会话管理等功能&#xff0c;广泛应…

2025.5.29 学习日记 docker概念以及基本指令

Docker&#xff1a; Docker 是一种开源的容器化平台&#xff0c;用于快速部署应用程序&#xff0c;实现开发、测试和生产环境的一致性。 一、Docker 核心概念 镜像&#xff08;Image&#xff09; 只读的模板文件&#xff0c;用于创建容器&#xff0c;类似虚拟机的镜像&#x…

AI与智能驾驶的关系和原理:技术融合与未来展望-优雅草卓伊凡一、AI大模型基础原理与智能驾驶

AI与智能驾驶的关系和原理&#xff1a;技术融合与未来展望-优雅草卓伊凡 一、AI大模型基础原理与智能驾驶 1.1 AI大模型的核心架构 本内容由优雅草木心为卓伊凡提供技术辅助讲解&#xff0c;毕竟木心目前正在比亚迪。 人工智能大模型是基于深度学习的复杂神经网络系统&#…

企业AI部署热潮下的安全隐忧:速度与安全的博弈

数据来源&#xff1a;企业网D1net 企业AI部署热潮下的安全隐忧&#xff1a;速度与安全的博弈 近年来&#xff0c;生成式人工智能&#xff08;GenAI&#xff09;的迅猛发展让企业趋之若鹜。然而&#xff0c;在这场技术竞赛中&#xff0c;不少企业却因盲目追求速度而忽视了安全…

分析XSSstrike源码

#用于学习web安全自动化工具# 我能收获什么&#xff1f; 1.XSS漏洞检测机制 学习如何构造和发送XSS payload如何识别响应中的回显&#xff0c;WAF&#xff0c;过滤规则等如何使用词典&#xff0c;编码策略&#xff0c;上下文探测等绕过过滤器 2.Python安全工具开发技巧 使…

通过mqtt 点灯

1 解析mqtt 传过来的json 用cjson 解析。 2 类似mvc的结构&#xff0c;调用具体的动作函数 定义设备处理结构体&#xff1a;使用结构体数组映射设备名称与处理函数&#xff0c;实现可扩展的指令分发分离设备逻辑&#xff1a;为每个设备&#xff08;如 LED、Motor&#xff0…

解锁技术世界的“秘密知识库”:The Book of Secret Knowledge 深度解析

在浩如烟海的技术文档中,你是否渴望一个集中式宝库,收录那些资深工程师口耳相传的“秘密武器”?GitHub 上爆火的 The Book of Secret Knowledge 正是这样一个令人惊叹的集合。今天我们来深入探索这个项目,挖掘它的核心价值。 🔍 项目核心:不是什么,而是什么 不是一本传…

M4Pro安装ELK(ElasticSearch+LogStash+Kibana)踩坑记录

ElasticSearch安装&#xff0c;启动端口9200&#xff1a; docker pull elasticsearch:8.13.0 新增配置文件elasticsearch.yml&#xff1a; cd /opt/homebrew/etc/ mkdir elasticsearch_config cd elasticsearch_config vi elasticsearch.yml cluster.name: "nfturbo…

VC++: identifer “M_PI“ is undefined

本周拿到一份算法文件&#xff08;cpp),尝试在本机跑一下&#xff0c;提示M_PI不识别&#xff1a; identifer "M_PI" is undefined 解决方案&#xff1a; #define _USE_MATH_DEFINES