ollama+springboot ai+vue+elementUI整合

news2024/11/24 16:05:07
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博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】-学习笔记03

第十一章-管理Linux软件包和进程 1.源码下载安装软件 1.1概念 源码文件&#xff1a;程序编写者使用C或C等语言编写的原始代码文本文件 源码文件使用.tar.gz或.tar.bz2打包成压缩文件 1.2特点 源码包可移植性好&#xff0c;与待安装软件的工作环境依赖性不大 由于有编译过程…

从手动到自动:掌握Shell脚本转换为System服务的魔法!

背景介绍 从 Ubuntu 17.10 版本开始&#xff0c;系统默认不再包含 /etc/rc.local 文件了&#xff0c;这是因为systemd已经成为了主要的系统初始化工具。不过别担心&#xff0c;如果你希望在开机时自动运行一些特定的命令&#xff0c;可以通过创建一个简单的 Shell脚本&#xf…

力扣-Hot100-哈希【算法学习day.30】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…

【数字静态时序分析】复杂时钟树的时序约束SDC写法

以上图为例&#xff0c;SoC芯片上往往存在几种不同的时钟源&#xff0c;有pll时钟、环振时钟、外部的晶振时钟&#xff0c;在SoC不同的模块或者不同的运行阶段使用的时钟也往往不同&#xff0c;所以在使用的时候&#xff0c;相同的模块会出现选择不同的时钟源的情况。上图的情形…

前端Cypress自动化测试全网详解

Cypress 自动化测试详解&#xff1a;从安装到实战 Cypress 是一个强大的端到端&#xff08;End-to-End, E2E&#xff09;功能测试框架&#xff0c;基于 Node.js 构建&#xff0c;支持本地浏览器直接模拟测试&#xff0c;并具有测试录屏功能&#xff0c;极大地方便了测试失败时的…

Qt_day4_Qt_UI设计

目录 Qt_UI设计 1. Designer 设计师&#xff08;掌握&#xff09; 2. Layout 布局&#xff08;重点&#xff09; 2.1 基本使用 2.2 高级用法 2.3 代码布局&#xff08;了解&#xff09; 3. Designer与C的关系&#xff08;熟悉&#xff09; 4. 基本组件&#xff08;掌握…

杨中科 .Net Core 笔记 DI 依赖注入2

ServiceCollection services new ServiceCollection();//定义一个承放服务的集合 services.AddScoped<iGetRole, GetRole>();using (ServiceProvider serviceProvider services.BuildServiceProvider()) {var list serviceProvider.GetServices(typeof(iGetRole));//获…

机器学习—Additional Layer Types

到目前为止&#xff0c;我们使用的所有神经网络都是密集型的&#xff0c;一层中的每个神经元&#xff0c;上一层的所有激活&#xff0c;事实证明&#xff0c;仅仅使用密集层类型&#xff0c;可以建立一些非常强大的学习算法&#xff0c;并帮助你建立关于神经网络能做什么的进一…

力扣 LeetCode 206. 反转链表(Day2:链表)

解题思路&#xff1a; pre &#xff0c;cur双指针 需要通过tmp暂存cur的下一个位置&#xff0c;以方便cur的下一步移动 class Solution {public ListNode reverseList(ListNode head) {ListNode pre null;ListNode cur head;while (cur ! null) {ListNode tmp cur.next;c…

硬件---4电感---基本概念与特性

一电感是什么 1电感的概念 电感就是一根导线加一个磁性原料。生活中&#xff0c;所有由线圈组成的器件都是电感。 如下图&#xff0c;常见的电感封装&#xff0c;有裸露的也有贴片的。 二电感的基本特性 1流过电感的电流不能发生突变 注意和电容的区别&#xff0c;一个是…

【软件工程】深入理解一下SOA(面向服务的架构)

关于SOA的一些看法 概述SOA的核心特性包括&#xff1a;一、服务自治与独立性二、松耦合与标准化三、服务重用与粒度四、服务可发现与安全 五、其他核心原则SOA的应用领域非常广泛&#xff0c;包括&#xff1a;SOA的一些挑战包括&#xff1a; &#x1f680; SOA在云计算中的应用…

【论文复现】ChatGPT多模态命名实体识别

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ChatGPT ChatGPT辅助细化知识增强&#xff01;1. 研究背景2. 模型结构和代码3. 任务流程第一阶段&#xff1a;辅助精炼知识启发式生成第二阶段…

隆盛策略正规炒股恒生科技指数跌4.19%,中芯国际跌近8%

查查配分析11月12日,香港恒生指数收跌2.84%,恒生科技指数跌4.19%。中兴通讯跌超9%,中芯国际跌近8%,蔚来跌超6%,美团、京东集团、理想汽车均跌超5%。 11月12日,港股跌幅扩大,恒生科技指数跌超4%,恒生指数跌超3%。 隆盛策略以其专业的服务和较低的管理费用在市场中受到不少关注。…

MFC图形函数学习07——画扇形函数

绘制扇形函数是MFC中绘图的基本函数&#xff0c;它绘制的仍是由椭圆弧与椭圆中心连线构成的椭圆扇形&#xff0c;特例是由圆弧与圆心连线构成的圆扇形。 一、绘制扇形函数 原型&#xff1a;BOOL Pie(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4); …

qt QHttpMultiPart详解

1. 概述 QHttpMultiPart是Qt框架中用于处理HTTP多部分请求的类。它类似于RFC 2046中描述的MIME multipart消息&#xff0c;允许在单个HTTP请求中包含多个数据部分&#xff0c;如文件、文本等。这种多部分请求在上传文件或发送带有附件的邮件等场景中非常有用。QHttpMultiPart类…

SpringBoot使用TraceId日志链路追踪

项目场景&#xff1a; 有时候一个业务调用链场景&#xff0c;很长&#xff0c;调了各种各样的方法&#xff0c;看日志的时候&#xff0c;各个接口的日志穿插&#xff0c;确实让人头大。为了解决这个痛点&#xff0c;就使用了TraceId&#xff0c;根据TraceId关键字进入服务器查询…

SSE (Server-Sent Events) 服务器实时推送详解

Server-Sent Events 一、什么是 SSE ?二、SSE 的工作原理三、SSE 的基本配置1.HTTP 请求和响应头设置2.SSE 字段介绍3.SSE 事件数据流示例 四、SseEmitter 的基本配置1.SseEmitter 介绍及用法2.使用 SseEmitter 示例11)编写核心 SSE Client2)编写 Controller3)前端接收与处理 …

【C++】踏上C++的学习之旅(六):深入“类和对象“世界,掌握编程的黄金法则(一)

文章目录 前言1. "面向过程"和"面向对象"的碰撞1.1 面向过程1.2 面向对象 2. "类"的引入3. "类"的定义3.1 &#x1f349;语法展示&#xff1a;3.2 "类"的两种定义方式3.3 "类"的命名规则 4. 类的访问限定符以及封…

机器学习:梯度提升树(GBDT)——基于决策树的树形模型

梯度提升树&#xff08;Gradient Boosting Decision Trees&#xff0c;GBDT&#xff09;是一种强大的机器学习方法&#xff0c;广泛用于回归和分类任务。它通过构建一系列决策树来优化模型的预测能力&#xff0c;基于梯度提升框架&#xff0c;使得每一棵树都试图纠正前一棵树的…

Spark SQL大数据分析快速上手-伪分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 伪分布模式也是在一台主机上运行&…