主要使用SpringBoot + VUE3 +deepseek实现的简朴的AI对话页面
页面有点简朴
主要写了三种方式
使用前在API_KEY更换成自己的key,模子可以使用V3大概R1
1.单纯的多轮对话
- import okhttp3.*;
- import java.io.IOException;
- import java.util.Objects;
- import java.util.concurrent.TimeUnit;
- public class DeepSeekOkHttpClient implements DeepSeekClient {
- private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
- private static final String API_KEY = "sk-xxx"; // 请务必妥善保管API_KEY
- private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
- private final OkHttpClient client;
- // 设置超时常量
- private static final int CONNECT_TIMEOUT = 10; // 连接超时(秒)
- private static final int READ_TIMEOUT = 130; // 读取超时(秒)
- private static final int WRITE_TIMEOUT = 30; // 写入超时(秒)
- public DeepSeekOkHttpClient() {
- this.client = new OkHttpClient.Builder()
- .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) // 设置连接超时
- .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) // 设置读取超时
- .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) // 设置写入超时
- .build();
- }
- @Override
- public String getChatCompletion(String userMessage) {
- String requestBody = String.format("{"model":"deepseek-chat","messages":[{"role":"user","content":"%s"}]}", userMessage);
- RequestBody body = RequestBody.create(requestBody, JSON);
- Request request = new Request.Builder()
- .url(API_URL)
- .addHeader("Authorization", "Bearer " + API_KEY)
- .post(body)
- .build();
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
- return Objects.requireNonNull(response.body()).string();
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- }
- }
复制代码 2.可以指定脚色的对话
在这里指定
- messages.put(new JSONObject()
- .put("role", "system")
- .put("content", "用精简语言回答问题,但是要回答准确全面!"));
复制代码 完备代码
- import okhttp3.*;import org.json.JSONArray;import org.json.JSONObject;import java.io.IOException;import java.util.concurrent.TimeUnit;public class DeepSeekChatWithRole { private static final String API_URL = "https://api.deepseek.com/v1/chat/completions"; private static final String API_KEY = "sk-xxx"; // 请妥善保管API_KEY private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); // 设置超时常量 private static final int CONNECT_TIMEOUT = 10; // 毗连超时(秒) private static final int READ_TIMEOUT = 130; // 读取超时(秒) private static final int WRITE_TIMEOUT = 30; // 写入超时(秒) private final OkHttpClient client; public DeepSeekChatWithRole() { this.client = new OkHttpClient.Builder() .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) // 设置毗连超时 .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) // 设置读取超时 .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) // 设置写入超时 .build(); } public String getChatCompletion(String userMessage) throws IOException { JSONArray messages = new JSONArray(); messages.put(new JSONObject()
- .put("role", "system")
- .put("content", "用精简语言回答问题,但是要回答准确全面!"));
- messages.put(new JSONObject() .put("role", "user") .put("content", userMessage)); JSONObject requestBody = new JSONObject() .put("model", "deepseek-reasoner") .put("messages", messages) .put("temperature", 0.7); // 控制回答的随机性(0-1) RequestBody body = RequestBody.create(requestBody.toString(), JSON); Request request = new Request.Builder() .url(API_URL) .addHeader("Authorization", "Bearer " + API_KEY) .post(body) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("API 哀求失败: " + response.code() + " - " + response.message()); } return response.body().string(); } } public static void main(String[] args) { DeepSeekChatWithRole chatClient = new DeepSeekChatWithRole(); try { String response = chatClient.getChatCompletion("Java 的 HashMap 和 LinkedHashMap 有什么区别?"); System.out.println("AI 回答: " + response); } catch (IOException e) { e.printStackTrace(); } }}
复制代码 3.带上下文关联,指定脚色回复
这里主要思路是:用redis存对话的汗青,然后每次会话会获取天生一个随机的不重复的key,用这个key存这次会话的汗青,在当前会话,每次举行对话时,都会传入key 作为获取汗青的参数,以达到上下文关联的目的
- import okhttp3.*;
- import org.json.JSONArray;
- import org.json.JSONObject;
- import redis.clients.jedis.Jedis;
- import java.io.IOException;
- import java.security.SecureRandom;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.TimeUnit;
- public class ChatRedisContext {
- private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
- private static final String API_KEY = "sk-xxx";
- private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
- // 超时设置
- private static final int CONNECT_TIMEOUT = 10;
- private static final int READ_TIMEOUT = 130;
- private static final int WRITE_TIMEOUT = 30;
- private final OkHttpClient client;
- private final String redisHost = "127.0.0.1"; // Redis 服务器地址
- private final int redisPort = 6379; // Redis 服务器端口
- private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- private static final SecureRandom random = new SecureRandom();
- public ChatRedisContext() {
- this.client = new OkHttpClient.Builder()
- .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
- .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
- .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
- .build();
- }
- private static class Message {
- String role;
- String content;
- public Message(String role, String content) {
- this.role = role;
- this.content = content;
- }
- public JSONObject toJson() {
- return new JSONObject()
- .put("role", this.role)
- .put("content", this.content);
- }
- }
- private void saveConversationHistory(String conversationKey, List<Message> history) {
- JSONArray messagesJson = new JSONArray();
- for (Message msg : history) {
- messagesJson.put(msg.toJson());
- }
- try (Jedis jedis = new Jedis(redisHost, redisPort)) {
- // 设置键值并设置过期时间为 12 小时(43200 秒)
- jedis.setex(conversationKey, 43200, messagesJson.toString());
- }
- }
- private List<Message> getConversationHistory(String conversationKey) {
- try (Jedis jedis = new Jedis(redisHost, redisPort)) {
- String historyJson = jedis.get(conversationKey);
- List<Message> history = new ArrayList<>();
- if (historyJson != null) {
- JSONArray messagesJson = new JSONArray(historyJson);
- for (int i = 0; i < messagesJson.length(); i++) {
- JSONObject msgJson = messagesJson.getJSONObject(i);
- history.add(new Message(msgJson.getString("role"), msgJson.getString("content")));
- }
- }
- return history;
- }
- }
- public String chat(String conversationKey, String userMessage) throws IOException {
- List<Message> conversationHistory = getConversationHistory(conversationKey);
- if (conversationHistory.isEmpty()) {
- conversationHistory.add(new Message("system", "用精简语言回答问题,但是要回答准确全面!"));
- }
- conversationHistory.add(new Message("user", userMessage));
- JSONArray messages = new JSONArray();
- for (Message msg : conversationHistory) {
- messages.put(msg.toJson());
- }
- JSONObject requestBody = new JSONObject()
- .put("model", "deepseek-chat")
- .put("messages", messages)
- .put("temperature", 0.7);
- RequestBody body = RequestBody.create(requestBody.toString(), JSON);
- Request request = new Request.Builder()
- .url(API_URL)
- .addHeader("Authorization", "Bearer " + API_KEY)
- .post(body)
- .build();
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) {
- throw new IOException("API 请求失败: " + response.code() + " - " + response.message());
- }
- String responseBody = response.body().string();
- JSONObject jsonResponse = new JSONObject(responseBody);
- String aiResponse = jsonResponse.getJSONArray("choices")
- .getJSONObject(0)
- .getJSONObject("message")
- .getString("content");
- conversationHistory.add(new Message("assistant", aiResponse));
- saveConversationHistory(conversationKey, conversationHistory);
- return aiResponse;
- }
- }
- public static String generateUniqueKey() {
- long timestamp = System.currentTimeMillis();
- StringBuilder randomString = new StringBuilder();
- for (int i = 0; i < 8; i++) { // 生成8个随机字母
- int index = random.nextInt(ALPHABET.length());
- randomString.append(ALPHABET.charAt(index));
- }
- return timestamp + "_" + randomString.toString();
- }
- public void clearConversation(String conversationKey) {
- try (Jedis jedis = new Jedis(redisHost, redisPort)) {
- if (null != conversationKey && jedis.exists(conversationKey)){
- jedis.del(conversationKey);
- }
- }
- }
-
- }
复制代码 寻常对话我还多写了一个接口方法,其实也不需要的
- /**
- * <描述>
- *
- * @author lj
- * @date 2025/5/26 9:34
- */
- public interface DeepSeekClient {
- String getChatCompletion(String userMessage);
- }
复制代码 主要的实现类
pom.xml
- <dependency>
- <groupId>com.squareup.okhttp3</groupId>
- <artifactId>okhttp</artifactId>
- <version>4.9.2</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.83</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.json/json -->
- <dependency>
- <groupId>org.json</groupId>
- <artifactId>json</artifactId>
- <version>20240303</version>
- </dependency>
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- <version>4.3.1</version> <!-- 确保使用的是最新版本 -->
- </dependency>
复制代码 接下来是前端代码,主要使用的是第三种方式(基于redis实现上下文)
1.每次页面加载,会默认创建一个会话,即调用获取key的接口
2.每次新增一个会话都会调用获取key的接口,确保每个会话的key是唯一的,达到上下文的目的
3.删除会话,会调用后台删除key的接口,同步删除了redis中的key
完备代码
.vue
- <template>
- <div id="app">
- <div class="sidebar">
- <h2>会话列表</h2>
- <ul>
- <li v-for="(session, index) in sessions" :key="index">
- <span @click="loadSession(index)">{{ session.title }}</span>
- <button @click="deleteSession(index)">删除</button>
- </li>
- </ul>
- <button @click="startNewSession" :disabled="sessions.length >= 10">+ 新会话</button>
- </div>
- <div class="chat-container">
- <div class="chat-title">{{ currentSession.title }}</div>
- <div class="messages" ref="messagesContainer">
- <div v-for="(msg, index) in currentSession.messages" :key="index" class="message" :class="msg.role">
- <span class="role">{{ msg.role }}:</span>
- <span class="content">{{ msg.content }}</span>
- </div>
- <div v-if="currentSession.loading" class="message assistant">
- <span class="role">小助手:</span>
- <span class="content">加载中...</span>
- </div>
- <div v-if="currentSession.timeout && !currentSession.loading" class="message assistant">
- <span class="role">小助手:</span>
- <span class="content">请求超时,请稍后再试。</span>
- </div>
- </div>
- <div class="input-container">
- <input v-model="userMessage" @keyup.enter="sendMessage" type="text" placeholder="输入你的消息..." />
- <button @click="sendMessage"
- :disabled="!currentSession.conversationKey || currentSession.loading || currentSession.timeout">发送</button>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { chat, getKey, clearConversation } from '@/api/main';
- import { ref, nextTick, onMounted } from 'vue';
- export default {
- setup() {
- const userMessage = ref('');
- const sessions = ref([]); // 存储会话列表
- const currentSession = ref({ title: '请开始新的会话', messages: [], loading: false, timeout: false, conversationKey: '' }); // 当前会话
- const sendMessage = async () => {
- if (!userMessage.value.trim()) return;
- const params = {
- conversationKey: currentSession.value.conversationKey,
- message: userMessage.value,
- };
- currentSession.value.messages.push({ role: 'user', content: userMessage.value });
- userMessage.value = '';
- currentSession.value.loading = true;
- currentSession.value.timeout = false;
- const timeoutPromise = new Promise((_, reject) => {
- setTimeout(() => {
- currentSession.value.timeout = true;
- currentSession.value.loading = false;
- reject(new Error('请求超时'));
- }, 120000);
- });
- try {
- const response = await Promise.race([chat(params), timeoutPromise]);
- currentSession.value.messages.push({ role: 'assistant', content: response.data });
- } catch (error) {
- console.error("发送消息失败:", error.message);
- if (error.message !== '请求超时') {
- currentSession.value.messages.push({ role: 'assistant', content: '请求失败,请重试。' });
- }
- } finally {
- currentSession.value.loading = false;
- nextTick(() => {
- const messagesContainer = document.querySelector('.messages');
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
- });
- }
- };
- const startNewSession = async () => {
- if (sessions.value.length >= 10) {
- alert('最多只能创建10个会话!');
- return;
- }
- try {
- const key = await getKey();
- if (!key || !key.data) {
- console.error("获取的会话密钥无效");
- alert('无法创建会话,获取的密钥无效,请稍后再试。');
- return;
- }
- const newSession = {
- title: `会话 ${sessions.value.length + 1}`,
- messages: [],
- loading: false,
- timeout: false,
- conversationKey: key.data,
- };
- sessions.value.push(newSession);
- loadSession(sessions.value.length - 1);
- } catch (error) {
- console.error("获取会话密钥失败:", error.message);
- }
- };
- const loadSession = (index) => {
- currentSession.value = sessions.value[index];
- };
- const deleteSession = async (index) => {
- try {
- const params = {
- conversationKey: sessions.value[index].conversationKey
- }
- await clearConversation(params);
- sessions.value.splice(index, 1);
- if (sessions.value.length > 0) {
- loadSession(0);
- } else {
- currentSession.value = { title: '请开始新的会话', messages: [], loading: false, timeout: false, conversationKey: '' };
- }
- } catch (error) {
- console.error("删除会话失败:", error.message);
- }
- };
- onMounted(() => {
- startNewSession();
- });
- return {
- userMessage,
- sendMessage,
- sessions,
- currentSession,
- startNewSession,
- loadSession,
- deleteSession,
- };
- },
- };
- </script>
- <style>
- #app {
- margin-top: 50px;
- display: flex;
- justify-content: center; /* 水平居中 */
- height: 80%;
- width: 70%; /* 设置宽度 */
- }
- .sidebar {
- width: 250px;
- border-right: 1px solid #ccc;
- padding: 10px;
- }
- .chat-container {
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 10px;
- }
- .chat-title {
- font-size: 24px;
- font-weight: bold;
- margin-bottom: 20px;
- }
- .messages {
- flex: 1;
- overflow-y: auto; /* 允许垂直滚动 */
- margin-bottom: 10px;
- max-height: 500px; /* 设置最大高度 */
- }
- .message {
- margin: 5px 0;
- }
- .message.user {
- text-align: right;
- color: green;
- }
- .message.assistant {
- margin: 10px;
- text-align: left;
- color: #333;
- }
- .input-container {
- display: flex;
- }
- input {
- flex: 1;
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- height: 30px;
- }
- button {
- padding: 10px;
- margin-left: 5px;
- border: none;
- background-color: #42b983;
- color: white;
- border-radius: 4px;
- cursor: pointer;
- }
- button:disabled {
- background-color: #ccc;
- cursor: not-allowed;
- }
- button:hover:not(:disabled) {
- background-color: #369e77;
- }
- </style>
复制代码 .ts
- import { get, post } from "@/config/request";
- interface ContextParams {
- conversationKey: string;
- message: string;
- }
- export function chat(params: ContextParams) {
- return get("api/getChatRedisContext", params);
- }
- export function getKey() {
- return get("api/getKey");
- }
- export function clearConversation(params: ContextParams) {
- return get("api/clearConversation", params);
- }
复制代码 config
- /* eslint-disable @typescript-eslint/no-explicit-any */
- import axios, {
- type AxiosInstance,
- type AxiosRequestConfig,
- type AxiosResponse,
- } from "axios";
- const commonurl = import.meta.env.VITE_APP_BASE_API;
- const envType = import.meta.env.VITE_ENV_SET;
- // const router = useRouter()
- interface resType {
- code: number;
- msg: string;
- data: any;
- }
- const getBaseUrl = (): string => {
- if (envType === "dev") {
- return commonurl;
- }
- return `https://api.example.com`;
- };
- const instance = axios.create({
- baseURL: "",
- timeout: 120000,
- });
- // 添加请求拦截器
- instance.interceptors.request.use(
- (config: AxiosRequestConfig): any => {
- // const { userDetil } = storeToRefs(userInfoStore());
- // const Authorization = userDetil.value.Authorization;
- // if (!Authorization && config.url?.indexOf("sys/login") === -1) {
- // const herf = window.location.href;
- // const baseUrl = window.location.href.split("#")[0];
- // const url = `${baseUrl}#/login`;
- // window.location.replace(url);
- // showError("用户未登录请重新登录!");
- // return {};
- // }
- config.headers = {
- "Content-Type": "application/json",
- // Authorization: String(Authorization),
- ...config.headers,
- };
- return config;
- },
- (error: unknown): Promise<any> => {
- // 对请求错误做些什么
- return Promise.reject(error);
- }
- );
- instance.interceptors.response.use(
- (response: AxiosResponse<resType>): any => {
- const data = response.data;
- if (data.code === 501) {
- showError("账号密码错误!" + `系统提示:${data.msg}`);
- return Promise.reject();
- }
- if (data.code === 500) {
- showError(`系统提示:${data.msg}`);
- return Promise.reject();
- }
- return response;
- },
- (error: any) => {
- if (
- error.response &&
- (error.response.status === 501 ||
- error.response.status === 404 ||
- error.response.status === 401)
- ) {
- const herf = window.location.href;
- const baseUrl = window.location.href.split("#")[0];
- const url = `${baseUrl}#/login`;
- window.location.replace(url);
- showError(error.message);
- return Promise.reject();
- }
- return Promise.reject(error);
- }
- );
- export const get = <T>(url: string, params?: any): any => {
- return instance.get<T>(url, { params });
- };
- export const post = <T>(url: string, data?: any): any => {
- return instance.post<T>(url, data);
- };
复制代码 这里的封装配置可能差别,可以自行使用,主要就是调用上面的几个接口
功能比力简陋,还在连续完善中,希望大佬多多指教(手动抱拳)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|