JDK:17

pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>deepseek4jDemo2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>deepseek4jDemo2</name>
    <description>deepseek4jDemo2</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
            <version>1.0.0.M2</version>
        </dependency>

        <dependency>
            <groupId>io.github.pig-mesh.ai</groupId>
            <artifactId>deepseek-spring-boot-starter</artifactId>
            <version>1.4.5</version>
        </dependency>
        <!-- 链接 milvus SDK-->
        <dependency>
            <groupId>io.milvus</groupId>
            <artifactId>milvus-sdk-java</artifactId>
            <version>2.5.5</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.12.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <repositories>
        <!-- spring ai -->
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <profiles>
        <profile>
            <id>dev</id> <!-- 补充profile的id -->
            <properties>
                <username>zhangsan</username>
                <profile.active>dev</profile.active>
            </properties>
            <repositories>

            </repositories>
        </profile>
    </profiles>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
OllamaController
package org.example.deepseek4jdemo2.controller;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.protobuf.Field;
import io.milvus.client.MilvusClient;
import io.milvus.grpc.JSONArray;
import io.milvus.grpc.MutationResult;
import io.milvus.grpc.QueryResults;
import io.milvus.param.R;
import io.milvus.param.dml.DeleteParam;
import io.milvus.param.dml.InsertParam;
import io.milvus.param.dml.QueryParam;
import io.milvus.v2.service.vector.request.DeleteReq;
import io.milvus.v2.service.vector.response.DeleteResp;
import jakarta.annotation.Resource;
import org.example.deepseek4jdemo2.config.MilvusConnectPool;
import org.reactivestreams.Publisher;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
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;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@RestController
@RequestMapping("/ollama")
public class OllamaController {
    /**
     * 用来记录上一次的对话, 实现历史对话
     */
    List<Message> messageList = new CopyOnWriteArrayList<>();

    @Resource
    private OllamaChatModel ollamaChatModel;

    @Autowired
    private MilvusConnectPool milvusConnectPool;
    /**
     * 测试Milvus
     * @param msg 消息
     * @return Object
     */
    @RequestMapping(value = "/ai/testMilvus")
    public ResponseEntity<String> testMilvus(@RequestParam(value = "msg")String msg){
//        return addEntity();
//        return delEntity();
        return selectEntity();
    }
    private  ResponseEntity<String> addEntity(){
        //申明
        MilvusClient milvusClient= null;
        try {
            //获取
            milvusClient = milvusConnectPool.getMilvusClient();
            //使用相关逻辑...
            //插入一条记录
            // 定义集合名称
            String collectionName = "test1";
            // 准备插入的数据
            String id = "4"; // 标量字段
//            List<Float> vector = new ArrayList<>();
//            vector.add(0.1f);
//            vector.add(0.2f);
//            vector.add(0.3f); // 假设这是一个 3 维向量
            Gson gson = new Gson();
            JsonObject jsonObject = gson.fromJson("{\"id\": "+id+", \"embedding\": [0.267653703392273,0.27469801090735446,0.7523448179995487,0.6182679052248916,0.5209705659673978,0.43325358160863026,0.734382551032327,0.14742111756050447,0.802413264681755,0.2462367824605045,0.8299054084772908,0.31264182713641087,0.04917484937162442,0.16127496484577897,0.2455484992892647,0.24276937996044468,0.8916419617467273,0.6665976572600019,0.9296905551538344,0.18167655642252423,0.4386186230899989,0.5849729529799343,0.2694948471406158,0.26614017277642743,0.8341706792424171,0.969470311659355,0.9121468980472549,0.2098657060788296,0.9883191740257584,0.06447802122144597,0.7118003795533774,0.736822974913709,0.9820312196361698,0.48581245336410794,0.1170811069106843,0.5134760820090933,0.34197137179044157,0.2299590027664138,0.8001682318319869,0.3621451457669662,0.6107828674109055,0.7362099273922231,0.18389225714694213,0.5170530188880735,0.06062360331149086,0.4416321491991757,0.13404612096779078,0.8124414219675331,0.13364241253820075,0.10089229984111814,0.37836102919784187,0.8199055981481149,0.4930100213035673,0.4249653532649711,0.46112697056926555,0.0598526101464365,0.2709764521575848,0.09098176531284685,0.6875809291691868,0.3400363848833554,0.9823577536488808,0.41755220600264176,0.9208623739588515,0.37271486138965537,0.5842775261352489,0.5320319248318137,0.9033098195782951,0.9313662654645027,0.9574831706937885,0.5366807664113107,0.33229305289457445,0.8023644765297424,0.4223566001331389,0.13775175913848448,0.413981932518503,0.5182451969350947,0.42693156008700117,0.8966189680658261,0.11518616496893519,0.9644146031198013,0.7385933802201408,0.602367975638975,0.7475218290878269,0.955402233631401,0.4117843073949887,0.04136438267797016,0.9065018351772771,0.5244012347993954,0.5604760504007029,0.39928012315669514,0.949872148410162,0.9905761967161661,0.695375291966563,0.24034338541509226,0.6975226686693514,0.9792128157460795,0.7821347117509823,0.5697587296888842,0.23299486125378555,0.41736112155934135,0.5689365569712348,0.4915866286703243,0.4972308367573499,0.7813830127027821,0.8844740787436474,0.44279424624744834,0.8528308018090132,0.5780228641541774,0.5523061476589788,0.3618334840206221,0.44096656196363937,0.0777927933524829,0.6717438441669625,0.16500861297209402,0.17021866624842397,0.029649856606401848,0.08754637577879376,0.8243210031521997,0.09135375267457002,0.7262079108275465,0.06143994684844811,0.2072436229208674,0.2146583852675905,0.03804672242289775,0.8336921828494217,0.38735637067305273,0.61624457478956,0.36513073908775495], \"name\": \"李四\"}", JsonObject.class);
            List<JsonObject> rows = Arrays.asList(jsonObject);
            // 构造插入参数
            InsertParam insertParam = InsertParam.newBuilder()
                    .withCollectionName(collectionName)
                    .withRows(rows)
                    .build();
            // 插入数据
            R<MutationResult> response = milvusClient.insert(insertParam);

            return ResponseEntity.ok("成功"+response);
        }catch (Exception e){
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("获取失败");
        }finally {
            //归还
            milvusConnectPool.releaseMilvusClient(milvusClient);
        }
    }
    private  ResponseEntity<String> delEntity(){
        MilvusClient milvusClient= null;
        try {
            //获取
            milvusClient = milvusConnectPool.getMilvusClient();
            String collectionName = "test1";
            String expr = "name == \"李四\"";

            R<MutationResult> delete = milvusClient.delete(DeleteParam.newBuilder()
                    .withCollectionName(collectionName)
                    .withExpr(expr).build());
            return ResponseEntity.ok("成功"+delete);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        } finally {
            //归还
            milvusConnectPool.releaseMilvusClient(milvusClient);
        }
    }
    private  ResponseEntity<String> selectEntity(){
        MilvusClient milvusClient= null;
        try {
            //获取
            milvusClient = milvusConnectPool.getMilvusClient();
            String collectionName = "test1";
            String expr = "name == \"张三\"";
            QueryParam queryParam = QueryParam.newBuilder().withCollectionName(collectionName).withExpr(expr).build();
            R<QueryResults> queryResultsR = milvusClient.query(queryParam);
            return ResponseEntity.ok("成功"+queryResultsR);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        } finally {
            //归还
            milvusConnectPool.releaseMilvusClient(milvusClient);
        }
    }
    /**
     * 非流式
     * @param msg 消息
     * @return Object
     */
    @RequestMapping(value = "/ai/ollama")
    public Object ollama(@RequestParam(value = "msg")String msg){
        return ollamaChatModel.call(msg);
    }

    /**
     * 流式
     * @param msg 消息
     * @return Flux<ServerSentEvent<String>>
     */
    @RequestMapping(value = "/ai/streamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> ollama1(@RequestParam(value = "msg") String msg) {
        UserMessage userMessage = new UserMessage(msg);
        if(messageList.size() == 2){
            messageList.remove(0);
        }
        messageList.add(userMessage);

        Prompt prompt = new Prompt(messageList);
        System.out.println("准备开始进行推送...");
        // 使用 Flux 流来进行异步处理,优化流的错误处理和结束操作
        return ollamaChatModel.stream(prompt) // 获取 Flux<ChatResponse>,异步流
                .doOnNext(chatResponse -> {
                    System.out.println("收到的数据: " + chatResponse);
                })
                .map(chatResponse -> {
                    String outputText = chatResponse.getResult().getOutput().getContent();
                    if (outputText == null || outputText.isEmpty()) {
//                        return ServerSentEvent.builder("没有返回有效数据").build();
                        return ServerSentEvent.builder("").event("complete").build();
                    }
                    System.out.println("推送 : " + outputText);
                    return ServerSentEvent.builder(outputText).build();
                })
                .onErrorResume(e -> {
                    // 错误处理,返回推送失败的信息
                    return Flux.just(ServerSentEvent.builder("推送发生错误,请重试。").build());
                });
    }
    /**
     *
     *         return ollamaChatModel.stream(prompt)
     *                 .concatMap(chatResponse -> {
     *                     String outputText = chatResponse.getResult().getOutput().getContent();
     *                     // 移除空值检查,允许传递空字符串
     *                     return Mono.just(ServerSentEvent.builder(outputText != null ? outputText : "").build());
     *                 })
     *                 .onErrorResume(e -> {
     *                     return (Publisher<? extends ServerSentEvent<String>>) Flux.just(
     *                             ServerSentEvent.builder("[ERROR] 请求失败").event("error").build(),
     *                             ServerSentEvent.builder().event("complete").build() // 结束信号
     *                     );
     *                 })
     *                 .concatWithValues( // 合并结束信号
     *                         ServerSentEvent.<String>builder()
     *                         .event("complete")
     *                         .data("") // 必须包含至少一个有效字段
     *                         .build()  // 正常结束信号
     *                 );
     * //                .concatWithValues(ServerSentEvent.builder().event("complete").build()); // 正常结束信号
     * //                .concatWith(Flux.just(ServerSentEvent.builder().event("complete").build())); // 正常结束信号
     */

}

前端访问跨域解决

package org.example.deepseek4jdemo2.config;

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

/*
* 跨域问题解决
* */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 所有接口
//                .allowedOrigins("*")  // 允许所有源(生产环境建议指定具体域名)
//                .addAllowedOrigin("http://example.com"); // 精确指定源
                .allowedOriginPatterns("http://localhost:8889","http://localhost:5173")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")  // 允许的方法
                .allowedHeaders("*")  // 允许所有请求头
                .allowCredentials(true)  // 允许携带凭证(如 cookies)
                .maxAge(3600);  // 预检请求缓存时间(秒)
    }
}
Milvus连接池
MilvusConnectPool
package org.example.deepseek4jdemo2.config;

import io.milvus.client.MilvusClient;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class MilvusConnectPool extends GenericObjectPool<MilvusClient> {

    /**
     * Creates a new {@code GenericObjectPool} using defaults from
     * {@link GenericObjectPoolConfig}.
     *
     * @param factory The object factory to be used to create object instances
     *                used by this pool
     */
    public MilvusConnectPool(PooledObjectFactory<MilvusClient> factory) {
        super(factory);
    }

    /**
     * Creates a new {@code GenericObjectPool} using a specific
     * configuration.
     *
     * @param factory The object factory to be used to create object instances
     *                used by this pool
     * @param config  The configuration to use for this pool instance. The
     *                configuration is used by value. Subsequent changes to
     *                the configuration object will not be reflected in the
     *                pool.
     */
    public MilvusConnectPool(PooledObjectFactory<MilvusClient> factory, GenericObjectPoolConfig<MilvusClient> config) {
        super(factory, config);
    }

    /**
     * Creates a new {@code GenericObjectPool} that tracks and destroys
     * objects that are checked out, but never returned to the pool.
     *
     * @param factory         The object factory to be used to create object instances
     *                        used by this pool
     * @param config          The base pool configuration to use for this pool instance.
     *                        The configuration is used by value. Subsequent changes to
     *                        the configuration object will not be reflected in the
     *                        pool.
     * @param abandonedConfig Configuration for abandoned object identification
     *                        and removal.  The configuration is used by value.
     */
    public MilvusConnectPool(PooledObjectFactory<MilvusClient> factory, GenericObjectPoolConfig<MilvusClient> config, AbandonedConfig abandonedConfig) {
        super(factory, config, abandonedConfig);
    }

    /**
     * 获取MilvusClient实例
     */
    public MilvusClient getMilvusClient() throws Exception {
        return super.borrowObject();
    }
    
    /**
     * 归还MilvusClient实例
     */
    public void releaseMilvusClient(MilvusClient milvusClient) {
        if (milvusClient!= null) {
            super.returnObject(milvusClient);
        }
    }
}
package org.example.deepseek4jdemo2.config;

import io.milvus.client.MilvusClient;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Slf4j
@Configuration
public class MilvusConnectPoolConfig {

    private static MilvusConnectPool pool;

    @Value("${spring.datasource.milvus-connect-pool.milvus.username}")
    private String username;

    @Value("${spring.datasource.milvus-connect-pool.milvus.password}")
    private String password;
   
    @Value("${spring.datasource.milvus-connect-pool.milvus.host}")
    private String host;

    @Value("${spring.datasource.milvus-connect-pool.milvus.port}")
    private Integer port;

    /** 最大空闲数 */
    @Value("${spring.datasource.milvus-connect-pool.max-idle}")
    private Integer maxIdle;

    /** 最小空闲数 */
    @Value("${spring.datasource.milvus-connect-pool.min-idle}")
    private Integer minIdle;

    /** 最大总数 */
    @Value("${spring.datasource.milvus-connect-pool.max-total}")
    private Integer maxTotal;

    @Bean("milvusConnectPool")
    public MilvusConnectPool milvusConnectPool(){
        // 配置连接池的参数
        GenericObjectPoolConfig<MilvusClient> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(maxTotal); // 设置连接池的最大连接数
        config.setMaxIdle(maxIdle); // 设置连接池的最大空闲连接数
        config.setMinIdle(minIdle); // 设置连接池的最小空闲连接数
        config.setMinEvictableIdleTime(Duration.ofMinutes(30));//逐出连接的最小空闲时间, 默认1800000毫秒(30分钟)
        config.setTimeBetweenEvictionRuns(Duration.ofMinutes(30));// 多久执行一次对象扫描,将无用的对象销毁,默认-1不扫描
        config.setTestOnBorrow(true);// 在获取对象的时候检查有效性, 默认false
        config.setTestOnReturn(false);// 在归还对象的时候检查有效性, 默认false
        config.setTestWhileIdle(false);// 在空闲时检查有效性, 默认false
        config.setMaxWait(Duration.ofSeconds(1));// 最大等待时间, 默认的值为-1,表示无限等待。
        config.setLifo(true);// 是否启用后进先出, 默认true
        config.setBlockWhenExhausted(true);// 连接耗尽时是否阻塞, false立即抛异常,true阻塞直到超时, 默认true
        config.setNumTestsPerEvictionRun(3);// 每次逐出检查时 逐出的最大数目 默认3
        //此处建议关闭jmx或是设置config.setJmxNameBase(), 因为默认注册的jmx会与项目可能已经存在的其他基于池类的实现bean冲突
        config.setJmxEnabled(false);

        // 创建连接工厂
        MilvusConnectPoolFactory factory = new MilvusConnectPoolFactory(username, password, host, port);

        // 初始化连接池
        pool = new MilvusConnectPool(factory, config);

        // 以最小空闲数量为初始连接数, 添加初始连接
        if(minIdle > 0){
            for (int i = 0; i < minIdle; i++) {
                try {
                    pool.addObject();
                }catch (Exception e){
                    log.error("添加初始连接失败");
                }

            }
        }
        return pool;
    }

    /**
     * 注销连接池
     */
    @PreDestroy
    public static void close() {
        if (pool != null) {
            pool.close();
        }
    }
}

工厂类

package org.example.deepseek4jdemo2.config;

import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusServiceClient;
import io.milvus.grpc.CheckHealthResponse;
import io.milvus.param.ConnectParam;
import io.milvus.param.LogLevel;
import io.milvus.param.R;
import io.milvus.param.RetryParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;

@Slf4j
public class MilvusConnectPoolFactory implements PooledObjectFactory<MilvusClient> {

    private final String username;
    private final String password;
    private final String host;
    private final Integer port;

    public MilvusConnectPoolFactory(String username, String password, String host, Integer port) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port;
    }
    
    @Override
    public void activateObject(PooledObject<MilvusClient> p) throws Exception {
        log.info("每次获取MilvusClient实例时触发此方法");
    }

    @Override
    public void destroyObject(PooledObject<MilvusClient> p) throws Exception {
        log.info("注销MilvusClient实例时触发此方法, 可使用MilvusClient.close()关闭连接");
        p.getObject().close();
    }

    @Override
    public PooledObject<MilvusClient> makeObject() throws Exception {
        log.info("创建MilvusClient实例");
        try {
            //连接参数
            ConnectParam connectParam = ConnectParam.newBuilder()
                    .withHost(host)
                    .withPort(port)
                    .withAuthorization(username,password)
                    .build();
            //重试参数
            RetryParam retryParam = RetryParam.newBuilder()
                    .withMaxRetryTimes(3)
                    .build();
            MilvusClient milvusClient = new MilvusServiceClient(connectParam).withRetry(retryParam);
            milvusClient.setLogLevel(LogLevel.Error);
            return new DefaultPooledObject<>(milvusClient);
        } catch (Exception e) {
            throw new RuntimeException("无法创建Milvus数据库连接", e);
        }
    }

    @Override
    public void passivateObject(PooledObject<MilvusClient> p) throws Exception {
        log.info("归还MilvusClient实例时触发此方法");
    }

    @Override
    public boolean validateObject(PooledObject<MilvusClient> p) {
        log.info("判断MilvusClient实例状态, 借助MilvusClient.checkHealth()实现");
        R<CheckHealthResponse> health = p.getObject().checkHealth();
        if (health.getStatus() == R.Status.Success.getCode()) {
            return true;
        } else {
            log.error("连接状态异常, 异常信息: {}", health.getMessage());
            return false;
        }
    }
}

chat.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>AI 流式聊天</title>
    <!-- 引入Quill富文本编辑器 -->
<!--    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">-->
    <link href="./css/quill.snow.css" rel="stylesheet">
    <style>
        .container {
            max-width: 800px;
            margin: 20px auto;
            padding: 20px;
        }

        #editor {
            height: 300px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }

        .button-group {
            text-align: right;
        }

        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        button:disabled {
            background: #6c757d;
            cursor: not-allowed;
        }

        .loading {
            display: none;
            color: #6c757d;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div class="container" id="app">
    <!-- 富文本编辑器容器 -->
    <div id="editor"></div>
<!--    <div>-->
<!--        <textarea id="area1" style="width: 798px;height: 298px;"></textarea>-->
<!--    </div>-->
    <!-- 操作按钮 -->
    <div class="button-group">
        <button @click="sendMessage" :disabled="isSending">发送请求</button>
        <div class="loading" :style="{display: isSending ? 'block' : 'none'}">
            正在接收AI回复...
        </div>
    </div>
</div>

<!-- 引入Vue.js -->
<!--<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>-->
<script src="./js/vue.js"></script>
<!-- 引入Quill编辑器 -->
<!--<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>-->
<script src="./js/quill.js"></script>

<script>
    // var elementArea = document.getElementById("area1");
    // quill.setText('你好,你是谁?');

    new Vue({
        el: '#app',
        data: {
            isSending: false,
            eventSource: null,
            responseContent: '',
            quill: null // 在 data 中声明
        },
        mounted(){
            // 初始化Quill编辑器
            this.quill = new Quill('#editor', {
                theme: 'snow',
                modules: {
                    toolbar: [
                        ['bold', 'italic', 'underline'],
                        [{ 'list': 'ordered'}, { 'list': 'bullet' }],
                        ['link', 'image']
                    ]
                }
            });
        },
        methods: {
            sendMessage() {
                // var elementQuill = document.getElementById("editor");
                if (this.isSending) return;

                this.isSending = true;
                this.responseContent = '';
                console.log(this.quill.getText());

                // 创建EventSource连接
                this.eventSource = new EventSource(
                    `http://192.168.1.104:8889/ollama/ai/streamChat?msg=${encodeURIComponent(this.quill.getText())}`
                );

                // 消息监听
                this.eventSource.addEventListener('message', event => {
                    console.log('消息监听',event)
                    if (event.data === '没有返回有效数据') {
                        this.closeConnection();
                        alert('未获取到有效响应');
                        return;
                    }
                    // 获取当前编辑器内容长度
                    const length = this.quill.getLength();
                    console.log("quill:"+length);
                    // 去除数据中的换行符(如果存在)
                    const cleanText = event.data.replace(/\n/g, '');
                    console.log("cleanText:"+cleanText);
                    // 将新内容插入到末尾(流式输出)
                    // this.quill.insertText(length, cleanText, 'user');
                    this.quill.insertText(length, event.data, { 'inline': true }, 'user');

                    this.responseContent = this.quill.getText().replace(/\n/g, '')
                    console.log('Quill content:', this.responseContent);
                    this.quill.setText(this.responseContent);
                    // 移动光标到新位置(可选)
                    this.quill.setSelection(length + cleanText.length, 0);
                    // 替换 Quill 的整个内容
                    console.log('Quill formats:', this.quill.getFormat());

                    // this.responseContent += event.data;
                    // console.log(this.responseContent);
                    //
                    // this.quill.updateContents([
                    //     { insert: event.data }
                    // ]);
                    // this.quill.setSelection(length + event.data.length, 0);

                    // var elementArea = document.getElementById("area1");
                    // elementArea.innerText = this.responseContent;
                });

                // this.eventSource.addEventListener('message', event => {
                //     if (event.data.startsWith('[ERROR]')) {
                //         this.closeConnection();
                //         alert(event.data.replace('[ERROR] ', ''));
                //         return;
                //     }
                //
                //     // 累积有效内容(过滤空值)
                //     if (event.data && event.data.trim() !== '') {
                //         this.responseContent += event.data;
                //         quill.updateContents([{ insert: this.responseContent }]);
                //     }
                // });
                // 添加流结束监听
                this.eventSource.addEventListener('complete', () => {
                    this.closeConnection();
                    // if (this.responseContent === '') {
                    //     alert('未获取到有效响应');
                    // }
                });
                // 错误处理
                this.eventSource.addEventListener('error', error => {
                    this.closeConnection();
                    console.error('连接错误:', error);
                    alert('连接发生异常');
                });
            },

            closeConnection() {
                if (this.eventSource) {
                    this.eventSource.close();
                    this.eventSource = null;
                }
                this.isSending = false;
            }
        },

        // 销毁前关闭连接
        beforeDestroy() {
            this.closeConnection();
        }
    });

    // 添加回车发送功能
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
            vueApp.sendMessage();
        }
    });
</script>
</body>
</html>

application.yml
 

server:
  port: 8889
spring:
  #Milvus 连接
  datasource:
    milvus-connect-pool:
      max-idle: 5
      min-idle: 2
      max-total: 10
      milvus:
        username: root
        password: 
        host: 192.168.1.44
        port: 19530
  application:
    name: Cleaner-AI
  ai:
    ollama:
      # ollama API Server 地址默认的他就是11434
      base-url: http://192.168.1.44:11434
      chat:
        enabled: true
          # 使用的模型名称
        model: deepseek-r1:14b
#            deepseek-v2:16b
        options:
          temperature: 0.7

## 推理模型链接信息
#deepseek:
# base-url: http://127.0.0.1:11434/v1
# model: deepseek-r1:14b
# api-key: ollama-local
#  # 向量模型链接信息
#embedding:
#  api-key: ${deepseek.api-key}
#  base-url: ${deepseek.base-url}
#  model: bge-m3:latest


Logo

更多推荐