ollama+springboot ai+vue+elementUI整合

1. 下载安装ollama

(1) 官网下载地址:https://github.com/ollama/ollama

这里以window版本为主,下载链接为:https://ollama.com/download/OllamaSetup.exe。

安装完毕后,桌面小图标有一个小图标,表示已安装成功,安装完毕后,首先改变环境变量,打开系统环境变量,设置如下:

OLLAMA_HOST: 0.0.0.0
OLLAMA_MODELS: D:\ai-models (这个目录需要提前创建好,目录名任意)

在这里,OLLAMA_HOST参数是为了跨域访问,方便第三方应用通过http请求访问。OLLAMA_MODELS参数为了改变模型下载地址,默认目录为C:\Users\<用户名>\.ollama\models。

接着,我们拉一个大模型,这里以阿里qwen2.5为例,更多模型可以从Ollama网站查询。打开命令行执行下面的命令:

ollama run qwen2.5

如果没有模型,首先自动尝试拉镜像,如果需要手动拉取镜像,执行ollama pull qwen2.5命令。上述命令执行完毕后,我们就可以直接使用大模型。

2. curl命令请求数据

Api详细文档: https://github.com/ollama/ollama/blob/main/docs/api.md

gitbash对curl命令支持并不好,下面的命令用win11的bash窗口或者用linux窗口执行这些命令。

(1)

curl http://localhost:11434/api/chat -d '{
  "model": "qwen2.5",
  "messages": [
    {
      "role": "user",
      "content": "天空为什么是蓝色的?"
    }
  ]
}'

(2)

curl http://localhost:11434/api/chat -d '{
 "model": "qwen2.5",
 "stream":false,
  "messages": [
    {
      "role": "user",
      "content": "天空为什么是蓝色的?"
    }
  ]
}'

(3)

curl http://localhost:11434/api/generate -d '{
  "model": "qwen2.5",
  "prompt": "你是谁?"
}'

(4)

curl http://localhost:11434/api/generate -d '{
  "model": "qwen2.5",
  "prompt": "你是谁?",
  "stream": false
}'

3. Springboot集成

(1) 首先打开https://start.spring.io/网站,填写如下必要信息。这里要注意,不要使用springboot2.x。我们需要使用springboot3.x.

(2) 用Eclipse或者idea导入项目,首先配置application.yml,内容如下:

server:
  port: 9999
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: qwen2.5

(3) 接下来我们需要配置跨域设置,新建一个类CorsConfig,写入如下内容:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOriginPatterns("*")
                        .allowedMethods("*")
                        .allowedHeaders("*")
                        .allowCredentials(true)
                        .exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);
            }
        };
    }
}

(4) 编写controller类,定义OllamaClientController类,写入如下内容:

import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/ollama")
public class OllamaClientController {
	@Autowired
	private OllamaChatModel ollamaChatModel;
    // http://localhost:9999/ollama/chat/v1?msg=天空为什么是蓝色的?
    @GetMapping("/chat/v1")
    public String ollamaChat(@RequestParam String msg) {
        return ollamaChatModel.call(msg);
    }
    // http://localhost:9999/ollama/chat/v2?msg=天空为什么是蓝色的?
    @GetMapping("/chat/v2")
    public Flux<String> ollamaChat2(@RequestParam String msg) {
    	Flux<String> stream = ollamaChatModel.stream(msg);
        return stream;
    }
}

(5) 接着启动项目,打开浏览器输入: http://localhost:9999/ollama/chat/v2?msg=天空为什么是蓝色的?, 会看到如下图信息,这里中文虽然乱码,但是不影响后续前端开发。

4. 前端页面开发

(1)这里采用Vue+ElementUI开发,首先需要创建一个vue项目。Vue项目整合ElementUI参考Element - The world's most popular Vue UI framework。将实现如下图所示的效果图:

(2) 优先采用流式数据返回,因为它响应速度较快,并且由于restful返回的结果是md格式的数据,所以,首先集成对md的支持。

首先,项目需要增加如下依赖:

npm i vue-markdown-loader
npm i vue-loader
npm i vue-template-compiler
npm i github-markdown-css
npm i highlight.js
npm i markdown-loader
npm i html-loader
npm i marked

安装完成后,还需要做一些配置,首先配置vue.config文件,增加如下内容:

const { defineConfig } = require('@vue/cli-service')
const path = require("path");
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 8932, // 端口
    client: {
      overlay: false,
    },
  },
  configureWebpack: {
    module: {
      rules: [
        // 配置读取 *.md 文件的规则
        {
          test: /\.md$/,
          use: [
            { loader: "html-loader" },
            { loader: "markdown-loader", options: {} }
          ]
        }
      ]
    }
  }
})


之后,我们需要在main.js中配置参数,增加如下内容:

import 'element-ui/lib/theme-chalk/index.css';
// markdown样式
import "github-markdown-css";
// 代码高亮
import "highlight.js/styles/github.css"; //默认样式

接着启动项目,如果项目启动失败,删除package-lock.json文件和node_modules目录,执行npm install,然后启动项目。

接着增加QuestionItem.vue组件,内容如下:

<template>
    <div class="question-warp">
        <div>
            <el-avatar size="small" src="images/user-icon.jpg"></el-avatar>
        </div>
        <div class="question-content">
            {{ value }}
        </div>
    </div>
</template>
<script>
export default {
    name: 'QuestionItem',
    props: {
        value: String,
    },
    data() {
        return {

        }
    },
    methods: {

    }
}
</script>
<style scoped>

.question-warp {
    display: flex;
}

.question-content {
    margin: 5px;
    line-height: 25px;
    text-align: justify;
    width: 100%;
    background-color: rgb(249, 246, 243);
    border-radius: 10px;
    padding: 10px;
}
</style>

接着增加一个AnswerItem.vue组件,内容如下:

<template>
    <div class="answer-warp">
        <div class="answer-content">
            <div  v-html="value" class="markdown-body"></div>
        </div>
        <div>
            <el-avatar size="small" src="images/ai-icon.jpg"></el-avatar>
        </div>
    </div>
</template>
<script>
export default {
    name: 'AnswerItem',
    props: {
        value: String,
    },
    data() {
        return {

        }
    },
    methods: {

    }
}
</script>
<style scoped>
.answer-warp {
    display: flex;
}
.markdown-body{
    background-color: rgb(223, 241, 249);
}
.answer-content {
    margin: 5px;
    line-height: 25px;
    width: 100%;
    text-align: justify;
    background-color: rgb(223, 241, 249);
    border-radius: 10px;
    padding: 10px;
}
</style>

以上两个组件分别是问题和答案的组件,所以接下来增加CustomerService.vue组件,内容如下:

<template>
    <div>
        <div class="title">
            <span style="color: red;">AI</span>智能客服为您服务
        </div>
        <div class="content">
            <template v-for="item in data">
                <question-item v-if="item.question !== ''" :value="item.question" />
                <answer-item v-if="item.answer !== ''" :value="item.answer" />
            </template>
        </div>
        <div class="textarea-container">
            <el-input type="textarea" resize='none' placeholder="请输入内容" v-model="questionInputValue" :rows="6"
                class="custom-textarea"></el-input>
            <el-button :disabled="submitButtonDisabled" type="primary" class="submit-button" @click="handleSubmit">
                提交
            </el-button>
        </div>
    </div>
</template>
<script>
import QuestionItem from "@/components/QuestionItem.vue";
import AnswerItem from "@/components/AnswerItem.vue";
import { marked } from 'marked'
export default {
    name: 'CustomerService',
    components: {
        'question-item': QuestionItem,
        'answer-item': AnswerItem
    },
    data() {
        return {
            question: '',
            submitButtonDisabled: false,
            questionInputValue: '',
            data: []
        }
    },
    methods: {
        async handleSubmit() {
            // 处理提交逻辑
            console.log('提交的内容:', this.questionInputValue);
            if (this.questionInputValue.trim() === '') {
                this.$message({
                    type: "error",
                    message: "你没有输入内容哦"
                })
            } else {
                this.question = this.questionInputValue
                this.submitButtonDisabled = true
                this.data.push({
                    question: this.question,
                    answer: '正在思考中...'
                })
                this.questionInputValue = ''
                try {// 发送请求
                    let response = await fetch("http://localhost:9999/api/ollama/chat/v2?msg=" + this.question,

                        {
                            method: "get",
                            responseType: "stream",
                        });// ok字段判断是否成功获取到数据流
                    if (!response.ok) {
                        throw new Error("Network response was not ok");
                    }
                    // 用来获取一个可读的流的读取器(Reader)以流的方式处理响应体数据
                    const reader = response.body.getReader();
                    // 将流中的字节数据解码为文本字符串
                    const textDecoder = new TextDecoder();
                    let result = true;
                    let answer = ''
                    while (result) {
                        // done表示流是否已经完成读取value包含读取到的数据块
                        const { done, value } = await reader.read();
                        if (done) {
                            result = false;
                            this.submitButtonDisabled = false
                            break;
                        }
                        answer += textDecoder.decode(value);
                        this.$set(this.data, this.data.length - 1, {
                            question: this.question,
                            answer: marked(answer)
                        });
                    }
                } catch (err) {
                    console.log("发生错误:", err)
                }
            }
        }
    }
}
</script>
<style scoped>
.title {
    text-align: center;
    font-size: larger;
    font-weight: bold;
}
.content {
    height: 460px;
    overflow-y: auto;
}
.question {
    border: 2px solid salmon;
    border-radius: 10px;
}
.textarea-container {
    position: relative;
    display: flex;
    flex-direction: column;
}
.custom-textarea {
    /* 为按钮留出空间 */
    box-sizing: border-box;
    /* 确保内边距不会增加元素的总宽度 */
}
.submit-button {
    position: absolute;
    bottom: 10px;
    /* 根据需要调整 */
    right: 10px;
    /* 根据需要调整 */
    z-index: 1;
    /* 确保按钮在文本域之上 */
}
</style>

之后我们在父组件调用该组件,即可,父组件示例代码如下:

…
    <el-drawer :visible.sync="customerService" :with-header="false" direction="rtl" size="45%">
      <div style="padding-left: 10px;padding-right:10px;">
        <customer-service />
      </div>
</el-drawer>
…
import CustomerService from "@/components/CustomerService.vue";
export default {
  name: "xxxx",
  components: {
    "customer-service": CustomerService
  },
 …
}
参考文档

1.ollama官网: Ollama

2. 报错 - 使用marked报错 marked__WEBPACK_IMPORTED_MODULE_4___default(...) is not a function_marked is not a function-CSDN博客

3.ollama readme : https://github.com/ollama/ollama?tab=readme-ov-file

4.vue中展示、读取.md 文件的方法(批量引入、自定义代码块高亮样式)_vue.js_脚本之家

5.在vue中解析md文档并显示-腾讯云开发者社区-腾讯云  

6.axios设置 responseType为 “stream“流式获取后端数据_axios stream-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/914529.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python数据写入excel文件

主要思路&#xff1a;数据 转DataFrame后写入excel文件 一、数据格式为字典形式1 k e &#xff0c; v [‘1’, ‘e’, 0.83, 437, 0.6, 0.8, 0.9, ‘好’] 1、这种方法使用了 from_dict 方法&#xff0c;指定了 orient‘index’ 表示使用字典的键作为行索引&#xff0c;然…

借助 Pause 容器调试 Pod

借助 Pause 容器调试 Pod 在 K8S 中&#xff0c;Pod 是最核心、最基础的资源对象&#xff0c;也是 Kubernetes 中调度最小单元。在介绍 Pause 容器之前需要先说明下 Pod 与容器的关系来理解为什么需要 Pause 容器来帮助调试 1. Pod 与 容器的关系 Pod 是一个抽象的逻辑概念&…

为何数据库推荐将IPv4地址存储为32位整数而非字符串?

目录 一、IPv4地址在数据库中的存储方式&#xff1f; 二、IPv4地址的存储方式比较 &#xff08;一&#xff09;字符串存储 vs 整数存储 &#xff08;二&#xff09;IPv4地址"192.168.1.8"说明 三、数据库推荐32位整数存储方式原理 四、存储方式对系统性能的影响…

独家|京东上线自营秒送,拿出二十年底牌和美团竞争

京东自营秒送开启招商&#xff0c;即时零售也要全托管&#xff1f; 作者|王迟 编辑|杨舟 据「市象」独家获悉&#xff0c;京东将在近期上线自营秒送业务&#xff0c;目前已经开始邀约制招商。「市象」获得的招商资料显示&#xff0c;和5月刚升级上线的京东秒送以POP模式不同&…

观成科技:Vagent注入的内存马加密通信特征分析

概述 vagent是一个使用Java语言开发的内存马注入工具。攻击者在利用vagent注入内存马之后可以利用别的代理工具或是webshell工具连接内存马进行通信。vagent对部分工具的内存马做了一些简单的魔改以达到绕过部分检测设备的目的。 vagent注入的内存马通信特征分析 vagent工具…

新增支持Elasticsearch数据源,支持自定义在线地图风格,DataEase开源BI工具v2.10.2 LTS发布

2024年11月11日&#xff0c;人人可用的开源BI工具DataEase正式发布v2.10.2 LTS版本。 这一版本的功能变动包括&#xff1a;数据源方面&#xff0c;新增了对Elasticsearch数据源的支持&#xff1b;图表方面&#xff0c;对地图类和表格类图表进行了功能增强和优化&#xff0c;增…

Ubuntu24.04安装搜狗输入法详细教程

本章教程,介绍如何在Ubuntu24.04版本操作系统上安装搜狗输入法。 一、下载安装包 搜狗输入法linux版本下载地址:https://shurufa.sogou.com/linux 二、安装步骤 1、更新源 sudo apt update2、安装fcitx输入法框架 sudo apt install fc

vxe-table 3.10+ 进阶高级用法(一),根据业务需求自定义实现筛选功能

vxe-table 是vue中非常强大的表格的&#xff0c;公司项目中复杂的渲染都是用 vxe-table 的&#xff0c;对于用的排序。筛选之类的都能支持&#xff0c;而且也能任意扩展&#xff0c;非常强大。 默认筛选功能 筛选的普通用法就是给对应的列指定参数&#xff1a; filters&#…

一文搞懂 ARM 64 系列: PACISB

1 PAC AMR64提供了PAC(Pointer Authentication Code)机制。 所谓PAC&#xff0c;简单来说就是使用存储在芯片硬件上的「密钥」&#xff0c;一个「上下文」&#xff0c;与「指针地址」进行加密计算&#xff0c;得出一个「签名」&#xff0c;将这个「签名」写入指针的高bit上。 计…

Spark 共享变量:广播变量与累加器解析

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

基于Matlab 火焰识别技术

课题介绍 森林承担着为人类提供氧气以及回收二氧化碳等废弃气体的作用&#xff0c;森林保护显得尤其重要。但是每年由于火灾引起的事故不计其数&#xff0c;造成重大的损失。如果有一款监测软件&#xff0c;从硬件处获得的图像中监测是否有火焰&#xff0c;从而报警&#xff0…

Group By、Having用法总结(常见踩雷点总结—SQL)

Group By、Having用法总结 目录 Group By、Having用法总结一、 GROUP BY 用法二、 HAVING 用法三、 GROUP BY 和 HAVING 的常见踩雷点3.1 GROUP BY 选择的列必须出现在 SELECT 中&#xff08;&#x1f923;最重要的一点&#xff09;3.2 HAVING 与 WHERE 的区别3.3 GROUP BY 可以…

《JavaEE进阶》----20.<基于Spring图书管理系统①(登录+添加图书)>

PS&#xff1a;关于接口定义 接口定义&#xff0c;通常由服务器提供方来定义。 1.路径&#xff1a;自己定义 2.参数&#xff1a;根据需求考虑&#xff0c;我们这个接口功能完成需要哪些信息。 3.返回结果&#xff1a;考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…

并发基础:(淘宝笔试题)三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。 💡 无论你是刚刚踏…

华为ensp实验二--mux vlan的应用

一、实验内容 1.实验要求&#xff1a; 在交换机上创建三个vlan&#xff0c;vlan10、vlan20、vlan100&#xff0c;将vlan100设置为mux-vlan&#xff0c;将vlan10设置为group vlan&#xff0c;将vlan20设置为separate vlan&#xff1b;实现vlan10的设备在局域网内可以进行互通&…

Hadoop + Hive + Apache Ranger 源码编译记录

背景介绍 由于 CDH&#xff08;Clouderas Distribution Hadoop &#xff09;近几年已经开始收费并限制节点数量和版本升级&#xff0c;最近使用开源的 hadoop 搭了一套测试集群&#xff0c;其中的权限管理组件用到了Apache Ranger&#xff0c;所以记录一下编译打包过程。 组件…

物联网对商业领域的影响

互联网彻底改变了通信方式&#xff0c;并跨越了因地理障碍造成的人与人之间的鸿沟。然而&#xff0c;物联网&#xff08;IoT&#xff09;的引入通过使设备能够连接到互联网&#xff0c;改变了设备的功能。想象一下&#xff0c;你的闹钟连接到互联网&#xff0c;并且能够用你的声…

PYNQ 框架 - 中断(INTR)驱动

目录 1. 简介 2. 分析 2.1 Block Design 2.2 AXI Timer 2.2.1 IP 基本信息 2.2.2 IP 地址空间 2.2.3 级联模式 2.2.4 生成/捕获模式 2.3 AXI Interrupt 2.3.1 IP 基本信息 2.3.2 IP 地址空间 2.3.3 相关概念 2.3.4 参数配置 2.3.5 中断确认寄存器 3. PYNQ 代码 …

HTB:Photobomb[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机进行端口开放扫描 再次使用nmap对靶机开放端口进行脚本、服务扫描 使用ffuf进行简单的子域名扫描 使用浏览器直接访问该域名 选取一个照片进行下载&#xff0c;使用Yakit进行抓包 USER_FLAG&#xff1a;a9afd9220ae2b5731…

ts枚举 enum

枚举&#xff08; enum &#xff09;可以定义⼀组命名常量&#xff0c;它能增强代码的可读性&#xff0c;也让代码更好维护。调用函数时传参时没有任何提示&#xff0c;编码者很容易写错字符串内容。并且⽤于判断逻辑的是连续且相关的⼀组值&#xff0c;那此时就特别适合使用枚…