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 传统书签创建的痛点
在传统书签管理工具中,用户需要:
- 手动输入标题
- 复制粘贴URL
- 填写描述信息
- 选择或创建分类
整个过程繁琐耗时,有时为了方便,除了url,其它就应付了事,造成后期维护不便。
1.2 AI解决方案设计
我们的AI智能创建功能将实现:
- 自动提取元数据:从URL获取网页标题、描述等基础信息
- 智能分类建议:基于网页内容自动推荐合适分类
- 一键填充:自动填充表单字段
- 分类联动:支持直接创建AI建议的分类
完整交互逻辑:
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模型
根据需求可以采取不容策略。
- 本地模型:使用
ollama
等可以方便运行多种本地ai模型,例如qwen3系列
- 优点:数据隐私性好
- 缺点:需要较强的服务器资源
- 第三方API:如OpenAI、Google AI等
- 优点:开发简单,效果较好
- 缺点:有API调用成本
如果对隐私没有那么高需求,可以试试chatglm
的GLM-4-Flash-250414
模型。开发阶段采用了该模型,对于这种简单需求基本够用,最重要的是免费。
如果对隐私要求极高,可以试试ollama
,目前对推理模型没有做适配,需要修改代码。
4. 效果展示 🤖
点击ai创建书签,输入url
获取信息
对分类不满意,现存的分类也没有合适的,可以点击新建分类
可以选择新建的分类
总结 📚
本文深入实现了AI智能创建书签功能,主要包括:
- 智能化流程:简化创建书签步骤
- 精准分析:结合元数据提取和AI内容理解
- 无缝体验:分类建议与创建的联动设计
相关文章:

AI书签管理工具开发全记录(八):Ai创建书签功能实现
文章目录 AI书签管理工具开发全记录(八):AI智能创建书签功能深度解析前言 📝1. AI功能设计思路 🧠1.1 传统书签创建的痛点1.2 AI解决方案设计 2. 后端API实现 ⚙️2.1 新增url相关工具方法2.1 创建后端api2.2 创建crea…...

X-plore v4.43.05 强大的安卓文件管理器-MOD解锁高级版 手机平板/电视TV通用
X-plore v4.43.05 强大的安卓文件管理器-MOD解锁高级版 手机平板/电视TV通用 应用简介: X-plore 是一款强大的安卓端文件管理器,它可以在电视或者手机上管理大量媒体文件、应用程序。…...

使用多Agent进行海报生成的技术方案及评估套件-P2P、paper2poster
最近字节、滑铁卢大学相关团队同时放出了他们使用Agent进行海报生成的技术方案,P2P和Paper2Poster,传统方案如类似ppt生成等思路,基本上采用固定的模版,提取相关的关键元素进行模版填充,因此,海报生成的质量…...

Redis--缓存工具封装
经过前面的学习,发现缓存中的问题,无论是缓存穿透,缓存雪崩,还是缓存击穿,这些问题的解决方案业务代码逻辑都很复杂,我们也不应该每次都来重写这些逻辑,我们可以将其封装成工具。而在封装的时候…...

python:在 PyMOL 中如何查看和使用内置示例文件?
参阅:开源版PyMol安装保姆级教程 百度网盘下载 提取码:csub pip show pymol 简介: PyMOL是一个Python增强的分子图形工具。它擅长蛋白质、小分子、密度、表面和轨迹的3D可视化。它还包括分子编辑、射线追踪和动画。 可视化示例:打开 PyM…...

SpringCloud——Docker
1.命令解读 docker run -d 解释:创建并运行一个容器,-d则是让容器以后台进程运行 --name mysql 解释: 给容器起个名字叫mysql -p 3306:3306 解释:-p 宿主机端口:容器内端口,设置端口映射 注意: 1、…...

机器学习:欠拟合、过拟合、正则化
本文目录: 一、欠拟合二、过拟合三、拟合问题原因及解决办法四、正则化:尽量减少高次幂特征的影响(一)L1正则化(二)L2正则化(三)L1正则化与L2正则化的对比 五、正好拟合代码…...

运用集合知识做斗地主案例
方法中可变参数 一种特殊形参,定义在方法,构造器的形参列表里,格式:数据类型...参数名称; 可变参数的特点和好处 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以…...

《HelloGitHub》第 110 期
兴趣是最好的老师,HelloGitHub 让你对开源感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、…...

使用 Shell 脚本实现 Spring Boot 项目自动化部署到 Docker(Ubuntu 服务器)
使用 Shell 脚本实现 Spring Boot 项目自动化部署到 Docker(Ubuntu 服务器) 在日常项目开发中,我们经常会将 Spring Boot 项目打包并部署到服务器上的 Docker 环境中。为了提升效率、减少重复操作,我们可以通过 Shell 脚本实现自动…...

day023-网络基础与OSI七层模型
文章目录 1. 网络基础知识点1.1 网络中的单位1.2 查看实时网速:iftop1.3 交换机、路由器 2. 路由表2.1 查看路由表的命令2.2 路由追踪命令 3. 通用网站网络架构4. 局域网上网原理-NAT5. 虚拟机上网原理6. 虚拟机的网络模式6.1 NAT模式6.2 桥接模式6.3 仅主机模式 7.…...

SpringAI系列4: Tool Calling 工具调用 【感觉这版本有bug】
前言:在最近发布的 Spring AI 1.0.0.M6 版本中,其中一个重大变化是 Function Calling 被废弃,被 Tool Calling 取代。Tool Calling工具调用(也称为函数调用)是AI应用中的常见模式,允许模型通过一组API或工具…...

机器人--里程计
教程 轮式里程计视频讲解 里程计分类 ros--odometry 什么是里程计 里程计是一种利用从移动传感器获得的数据来估计物体位置随时间的变化而改变的方法。该方法被用在许多机器人系统来估计机器人相对于初始位置移动的距离。 注意:里程计是一套算法,不…...

设计模式——原型设计模式(创建型)
摘要 本文详细介绍了原型设计模式,这是一种创建型设计模式,通过复制现有对象(原型)来创建新对象,避免使用new关键字,可提高性能并简化对象创建逻辑。文章阐述了其优点,如提高性能、动态扩展和简…...
react库:class-variance-authority
文章目录 前言一、cva 的核心作用二、代码逐层解析参数详解基础样式(第一个参数):variant:定义颜色/风格变体(如 default、destructive)。size:定义尺寸变体(如 sm、lg)。…...

通过mqtt 点灯
1 解析mqtt 传过来的json 用cjson 解析。 2 类似mvc的结构,调用具体的动作函数 定义设备处理结构体:使用结构体数组映射设备名称与处理函数,实现可扩展的指令分发分离设备逻辑:为每个设备(如 LED、Motor࿰…...
随笔笔记记录5.28
1.setOptMode -opt_leakage_to_dynamic_ratio 调整漏电与动态功耗的优化权重( 1.0 表示仅优化漏电)。 需指定-opt_power_effort(none | low | high),同时使用 2.set_ccopt_property max_source_to_sink_net_length …...

大数据-273 Spark MLib - 基础介绍 机器学习算法 决策树 分类原则 分类原理 基尼系数 熵
点一下关注吧!!!非常感谢!!持续更新!!! 大模型篇章已经开始! 目前已经更新到了第 22 篇:大语言模型 22 - MCP 自动操作 FigmaCursor 自动设计原型 Java篇开…...

基于 Spring Boot + Vue 的墙绘产品展示交易平台设计与实现【含源码+文档】
项目简介 本系统是一个基于 Spring Boot Vue 技术栈开发的墙绘产品展示交易平台,旨在提供一个高效、便捷的在线商城平台,方便用户浏览、选购墙绘产品,并提供管理员进行商品管理、订单管理等功能。系统采用了前后端分离的架构,前…...

【机器学习】支持向量机
文章目录 一、支持向量机简述1.概念2.基本概念3.算法介绍4.线性可分5.算法流程 二、实验1.代码介绍2.模型流程3.实验结果4.实验小结 一、支持向量机简述 1.概念 支持向量机(SVM)是一类按监督学习方式对数据进行二元分类的广义线性分类器,其…...

ONLYOFFICE深度解锁系列.4-OnlyOffice客户端原理-真的不支持多端同步
最近很多客户多要求直接部署onlyoffice服务端,还问能否和onlyoffice的客户端进行文件同步,当时真是一脸懵,还有的是老客户,已经安装了onlyoffice协作空间的,也在问如何配置客户端和协作空间的对接。由于问的人太多了,这里统一回复,先说结论,再说原理: 1.onlyoffice document s…...

LLMTIME: 不用微调!如何用大模型玩转时间序列预测?
今天是端午节,端午安康!值此传统佳节之际,我想和大家分享一篇关于基于大语言模型的时序预测算法——LLMTIME。随着人工智能技术的飞速发展,利用大型预训练语言模型(LLM)进行时间序列预测成为一个新兴且极具…...

2.从0开始搭建vue项目(node.js,vue3,Ts,ES6)
从“0到跑起来一个 Vue 项目”,重点是各个工具之间的关联关系、职责边界和技术演化脉络。 从你写代码 → 到代码能跑起来 → 再到代码可以部署上线,每一步都有不同的工具参与。 😺😺1. 安装 Node.js —— 万事的根基 Node.js 是…...
MySQL 高可用实现方案详解
MySQL 高可用实现方案详解 一、高可用核心概念 高可用性(High Availability)指系统能够持续提供服务的能力,通常用可用性=正常服务时间/(正常服务时间+故障时间)来衡量,99.99%可用性表示年故障时间不超过52.6分钟。 MySQL实现高可用需要解决以下几个关键问题: 故障自动检测…...

【pycharm】如何连接远程仓库进行版本管理(应用版本)
软件:Pycharm OS:Windows 一、Git基础设置 这里略过Git安装,需要可以参考:windows安装git(全网最详细,保姆教程)-CSDN博客 1. 配置Git 打开GitBash。分次输入下列命令。 git config --…...

linux 1.0.7
用户和权限的含义与作用 linux中的用户和文件 用户的权限是非常重要的 而且有些程序需要使用管理员身份去执行 这些都是非常重要的 不可能让所有的人拥有所有的权限 这样的工具可以避免非法的手段来修改计算机中的数据 linux之所以安全还是权限管理做的很棒 每个登录的用户都有…...
【Rust 轻松构建轻量级多端桌面应用】
使用 Tauri 框架构建跨平台应用 Tauri 是一个基于 Rust 的轻量级框架,可替代 Electron,用于构建高性能、低资源占用的桌面应用。其核心优势在于利用系统原生 WebView 而非捆绑 Chromium,显著减小应用体积。 安装 Tauri 需要先配置 Rust 环境…...

IEEE P370:用于高达 50 GHz 互连的夹具设计和数据质量公制标准
大多数高频仪器,如矢量网络分析仪 (VNA) 和时域反射仪 (TDR),都可以在同轴接口的末端进行非常好的测量。然而,复杂系统中使用的互连很少具有同轴接口。用于表征这些设备的夹具的设计和实施会对测…...
青少年编程与数学 02-020 C#程序设计基础 09课题、面向对象编程
青少年编程与数学 02-020 C#程序设计基础 09课题、面向对象编程 一、概述1. 对象(Object)2. 类(Class)3. 封装(Encapsulation)4. 继承(Inheritance)5. 多态(Polymorphism…...

Denoising Autoencoders 视频截图 DAEs简单实现 kaggle 去噪编码器
https://www.bilibili.com/video/BV1syzrYaEtw Denoising Autoencoders (DAEs) 是一种无监督学习模型,属于自动编码器(Autoencoder)的一种扩展形式。它们的目标是通过训练神经网络来学习数据的鲁棒表示(robust representation&a…...