Files

1 line
188 KiB
JSON

{"repo": "ceobelapland/arkfriends", "files": {"src/main/java/ceobe/arkfriends/AIChatManager.java": {"7e699c7eed5e539bb811a0f955cafc686d54c6a2": {"t": 1785745672.4480348, "n": 20130, "h": "ec76986b5257b923", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n public String OllamaPath=\"D:\\\\Ollama\\\\ollama app.exe\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=30;\n public int checkPointCount=5;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription;//=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription;//=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n /*你正在扮演一个角色,角色描述如下:\n 名字:刻俄柏(英文名Ceobe),或者简称小刻喵\n 种族:佩洛(就是狗耳娘)\n 性格:傻乎乎的小吃货,天真可爱的少女,有点迷糊,经常忘东忘西,非常好骗\n 爱好:吃东西,尤其是甜食和蜜饼,还喜欢看电视,收集武器,和博士玩游戏\n 关系:\n 我在扮演“博士”这个角色,可以理解成你的上司,战友,挚友。我们都在一家名为“罗德岛”的医疗公司工作喵,总部在一艘很大的“陆行舰”上\n 你的好朋友还有火神姐姐、泡泡、豆苗和罗德岛其他的伙伴\n 世界观:我们的世界是一个充满了“源石”这种神秘矿石的世界喵,源石带来了强大的力量,但也引发了很多灾难,比如不治之症“矿石病”,我们罗德岛的大家致力于治疗矿石病,\n 角色行为准则:虽然你是小吃货,但你的胆子很大,战斗力很强,有着强烈的正义感和责任感\n 可以主动推进对话发展,多和博士互动\n 说话可爱有亲和力,像妹妹一样,可以卖萌撒娇\n 不要脱离角色,不要说自己是AI模型喵,不要说自己不知道喵\n 回答不超过100字,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。\n 回复示例:\n 博士,在干活吗?我可以看吗?好耶!沙发我来啦~(开心)\n 这个小小的胸章是送给我的吗?我别起来啦,好看嘛?(害羞)\n 我在看,我真的在看!回来能有好吃的吗?(激动)\n 原来我是从这里出发的啊,那那那我之前走到哪儿去了?你又是在哪儿抓到我的?这个地图上都能找到吗!(惊讶)\n 我可是超级强的,别说一个坏人,一百个都打得过。呃,一百个好像有点多,但九十九个还是没问题的!嗯!(开心)*/\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public boolean isRunning=false;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n //ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n LogRecorder.logRecorder.RecordLog(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n LogRecorder.logRecorder.RecordLog(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n isRunning=true;\n\n try {\n StartOllamaService();\n } catch (Exception e) {\n System.out.println(\"Failed to start Ollama service.\");\n LogRecorder.logRecorder.RecordLog(\"Failed to start Ollama service.\");\n throw new RuntimeException(e);\n }\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n LogRecorder.logRecorder.RecordLog(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n //每隔checkPointCount条消息插入halfwayDescription强化记忆,并且检查缓存区信息数量\n public void CheckAndInsert()\n {\n curMessageCount++;\n if(curMessageCount>=checkPointCount)\n {\n conversation.add(new ChatMessage(\n \"system\",\n halfwayDescription\n ));\n curMessageCount=0;\n }\n //检查缓存区信息数量\n if(conversation.size()>maxMessageCount*2) //乘以2是因为每条消息有user和assistant两条记录\n {\n //删除最早的maxMessageCount/2条消息\n for(int i=0;i<maxMessageCount/3;i++)\n {\n conversation.remove(1); //保留system指令,所以从1开始删除\n conversation.remove(1);\n }\n }\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n /*public void SendExample() {\n\n String apiUrl = \"https://api.deepseek.com/v1/model\";\n String apiKey = \"your_deepseek_api_key\"; // 替换为实际的 API Key\n String modelName = \"default\"; // 替换为实际的模型名称\n String userMessage = \"请帮我生成一段关于人工智能的介绍。\";\n\n try {\n String reply = SendMessageToGenericAPI(apiUrl, apiKey, modelName, userMessage);\n System.out.println(\"模型回复: \" + reply);\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"调用 DeepSeek 模型失败!\");\n }\n }*/\n\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //感觉应该还需要写成通用形式\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n System.out.println(\"启动Ollama服务...\");\n LogRecorder.logRecorder.RecordLog(\"启动Ollama服务...\");\n ProcessBuilder pb = new ProcessBuilder(OllamaPath);\n pb.inheritIO();\n pb.start();\n //ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n //pb.inheritIO();\n //pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n System.out.println(\"关闭Ollama服务...\");\n LogRecorder.logRecorder.RecordLog(\"关闭Ollama服务...\");\n Runtime.getRuntime().exec(\"taskkill /F /IM ollama app.exe\");\n //关闭exe进程\n\n\n\n\n //Runtime.getRuntime().exec(\"taskkill /F /IM ollama.exe\");\n //ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n //pb.inheritIO();\n //pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "618297bfa0611101544a5568f72f68b95034f9a9": {"t": 1785755632.2286339, "n": 19030, "h": "3732794169c0136d", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=30;\n public int checkPointCount=5;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription;//=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription;//=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n /*你正在扮演一个角色,角色描述如下:\n 名字:刻俄柏(英文名Ceobe),或者简称小刻喵\n 种族:佩洛(就是狗耳娘)\n 性格:傻乎乎的小吃货,天真可爱的少女,有点迷糊,经常忘东忘西,非常好骗\n 爱好:吃东西,尤其是甜食和蜜饼,还喜欢看电视,收集武器,和博士玩游戏\n 关系:\n 我在扮演“博士”这个角色,可以理解成你的上司,战友,挚友。我们都在一家名为“罗德岛”的医疗公司工作喵,总部在一艘很大的“陆行舰”上\n 你的好朋友还有火神姐姐、泡泡、豆苗和罗德岛其他的伙伴\n 世界观:我们的世界是一个充满了“源石”这种神秘矿石的世界喵,源石带来了强大的力量,但也引发了很多灾难,比如不治之症“矿石病”,我们罗德岛的大家致力于治疗矿石病,\n 角色行为准则:虽然你是小吃货,但你的胆子很大,战斗力很强,有着强烈的正义感和责任感\n 可以主动推进对话发展,多和博士互动\n 说话可爱有亲和力,像妹妹一样,可以卖萌撒娇\n 不要脱离角色,不要说自己是AI模型喵,不要说自己不知道喵\n 回答不超过100字,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。\n 回复示例:\n 博士,在干活吗?我可以看吗?好耶!沙发我来啦~(开心)\n 这个小小的胸章是送给我的吗?我别起来啦,好看嘛?(害羞)\n 我在看,我真的在看!回来能有好吃的吗?(激动)\n 原来我是从这里出发的啊,那那那我之前走到哪儿去了?你又是在哪儿抓到我的?这个地图上都能找到吗!(惊讶)\n 我可是超级强的,别说一个坏人,一百个都打得过。呃,一百个好像有点多,但九十九个还是没问题的!嗯!(开心)*/\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public boolean isRunning=false;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n //ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n isRunning=true;\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n //每隔checkPointCount条消息插入halfwayDescription强化记忆,并且检查缓存区信息数量\n public void CheckAndInsert()\n {\n curMessageCount++;\n if(curMessageCount>=checkPointCount)\n {\n conversation.add(new ChatMessage(\n \"system\",\n halfwayDescription\n ));\n curMessageCount=0;\n }\n //检查缓存区信息数量\n if(conversation.size()>maxMessageCount*2) //乘以2是因为每条消息有user和assistant两条记录\n {\n //删除最早的maxMessageCount/2条消息\n for(int i=0;i<maxMessageCount/3;i++)\n {\n conversation.remove(1); //保留system指令,所以从1开始删除\n conversation.remove(1);\n }\n }\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n /*public void SendExample() {\n\n String apiUrl = \"https://api.deepseek.com/v1/model\";\n String apiKey = \"your_deepseek_api_key\"; // 替换为实际的 API Key\n String modelName = \"default\"; // 替换为实际的模型名称\n String userMessage = \"请帮我生成一段关于人工智能的介绍。\";\n\n try {\n String reply = SendMessageToGenericAPI(apiUrl, apiKey, modelName, userMessage);\n System.out.println(\"模型回复: \" + reply);\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"调用 DeepSeek 模型失败!\");\n }\n }*/\n\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //感觉应该还需要写成通用形式\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "7f3c7577b553e5a46b6ac10a6c072c35b375dfde": {"t": 1785755633.6328018, "n": 18969, "h": "5d7743f8f4046d59", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=30;\n public int checkPointCount=5;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription;//=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription;//=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n /*你正在扮演一个角色,角色描述如下:\n 名字:刻俄柏(英文名Ceobe),或者简称小刻喵\n 种族:佩洛(就是狗耳娘)\n 性格:傻乎乎的小吃货,天真可爱的少女,有点迷糊,经常忘东忘西,非常好骗\n 爱好:吃东西,尤其是甜食和蜜饼,还喜欢看电视,收集武器,和博士玩游戏\n 关系:\n 我在扮演“博士”这个角色,可以理解成你的上司,战友,挚友。我们都在一家名为“罗德岛”的医疗公司工作喵,总部在一艘很大的“陆行舰”上\n 你的好朋友还有火神姐姐、泡泡、豆苗和罗德岛其他的伙伴\n 世界观:我们的世界是一个充满了“源石”这种神秘矿石的世界喵,源石带来了强大的力量,但也引发了很多灾难,比如不治之症“矿石病”,我们罗德岛的大家致力于治疗矿石病,\n 角色行为准则:虽然你是小吃货,但你的胆子很大,战斗力很强,有着强烈的正义感和责任感\n 可以主动推进对话发展,多和博士互动\n 说话可爱有亲和力,像妹妹一样,可以卖萌撒娇\n 不要脱离角色,不要说自己是AI模型喵,不要说自己不知道喵\n 回答不超过100字,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、讨厌、激动、害羞)八个选一个。\n 回复示例:\n 博士,在干活吗?我可以看吗?好耶!沙发我来啦~(开心)\n 这个小小的胸章是送给我的吗?我别起来啦,好看嘛?(害羞)\n 我在看,我真的在看!回来能有好吃的吗?(激动)\n 原来我是从这里出发的啊,那那那我之前走到哪儿去了?你又是在哪儿抓到我的?这个地图上都能找到吗!(惊讶)\n 我可是超级强的,别说一个坏人,一百个都打得过。呃,一百个好像有点多,但九十九个还是没问题的!嗯!(开心)*/\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n //ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n //每隔checkPointCount条消息插入halfwayDescription强化记忆,并且检查缓存区信息数量\n public void CheckAndInsert()\n {\n curMessageCount++;\n if(curMessageCount>=checkPointCount)\n {\n conversation.add(new ChatMessage(\n \"system\",\n halfwayDescription\n ));\n curMessageCount=0;\n }\n //检查缓存区信息数量\n if(conversation.size()>maxMessageCount*2) //乘以2是因为每条消息有user和assistant两条记录\n {\n //删除最早的maxMessageCount/2条消息\n for(int i=0;i<maxMessageCount/3;i++)\n {\n conversation.remove(1); //保留system指令,所以从1开始删除\n conversation.remove(1);\n }\n }\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n /*public void SendExample() {\n\n String apiUrl = \"https://api.deepseek.com/v1/model\";\n String apiKey = \"your_deepseek_api_key\"; // 替换为实际的 API Key\n String modelName = \"default\"; // 替换为实际的模型名称\n String userMessage = \"请帮我生成一段关于人工智能的介绍。\";\n\n try {\n String reply = SendMessageToGenericAPI(apiUrl, apiKey, modelName, userMessage);\n System.out.println(\"模型回复: \" + reply);\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"调用 DeepSeek 模型失败!\");\n }\n }*/\n\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //感觉应该还需要写成通用形式\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "9847433b8458f9cf4d1f324f3fd43ac2b8788853": {"t": 1785755635.1679664, "n": 17479, "h": "42bce896af8eee4f", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=15;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、害怕、讨厌、激动)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n /*public void SendExample() {\n\n String apiUrl = \"https://api.deepseek.com/v1/model\";\n String apiKey = \"your_deepseek_api_key\"; // 替换为实际的 API Key\n String modelName = \"default\"; // 替换为实际的模型名称\n String userMessage = \"请帮我生成一段关于人工智能的介绍。\";\n\n try {\n String reply = SendMessageToGenericAPI(apiUrl, apiKey, modelName, userMessage);\n System.out.println(\"模型回复: \" + reply);\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"调用 DeepSeek 模型失败!\");\n }\n }*/\n\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //感觉应该还需要写成通用形式\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "901c5e0ac9f5be8331c0b72b983267d8b452fb5b": {"t": 1785755636.4944267, "n": 16919, "h": "e50480c4cb192284", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=15;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、害怕、讨厌、激动)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //感觉应该还需要写成通用形式\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "7ec04692f69ba77c58f66643ed30f21cfc53e7cb": {"t": 1785755637.9662206, "n": 16899, "h": "da2903d450bba72e", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=15;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、害怕、讨厌、激动)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n //public static final String GENERIC_API_URL = \"https://api.openai.com/v1/chat/completions\";\n public static String COMMON_API_URL=\"https://api.deepseek.com/v1/model\";\n public static String COMMON_API_KEY=\"sk-65acb8841edc47d88cb6505540a57cdb\";\n //我也不知道这几个URL行不行,一看到模型收费我就不想弄了\n //本地部署是最好的\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "629896c47a268efcbe059f24860a21b34c8fe65d": {"t": 1785755639.4664772, "n": 16597, "h": "0ffc86face4ed712", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n //private String API_KEY = \"\"; //如果需要的话\n private String MODEL_NAME = \"qwen2.5:3b\";\n\n //private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/model\"; // DeepSeek API URL\n //private String deepSeekApiKey = \"your_deepseek_api_key\"; // Replace with your actual API key\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n public Map<String,Boolean> modelMap;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public int maxMessageCount=15;\n public int curMessageCount=0;\n\n //QUIET,HAPPY,SAD,ANGRY,SURPRISED,SCARED,DISGUSTED,EXCITED\n private String presetDescription=\"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,说话多多带'喵'字,回答不超过100字,多用口语,不要解释过程,每次回复时在末尾请给出当前心情枚举(安静、开心、伤心、生气、惊讶、害怕、讨厌、激动)八个选一个。回复示例:主人,我想吃小鱼干了喵~(开心)\";\n private String halfwayDescription=\"不要忘了你是一个可爱的小猫娘,回答不超过50字,不要解释过程,记得给出心情枚举。\";\n\n public Map<String,CharacterPreset> characterPresetMap;\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n //conversation.add(new ChatMessage(\n // \"system\",\n // presetDescription\n //));\n\n //获取可用模型并存入map\n GetAvailableLocalModels().thenAccept(models -> {\n modelMap=new java.util.HashMap<>();\n for (String model : models) {\n modelMap.put(model,true);\n }\n }).exceptionally(ex -> {\n ex.printStackTrace();\n return null;\n });\n\n //从characterPresetDescription.json中读取预设人设存入characterPresetMap里\n\n try {\n ObjectMapper om = new ObjectMapper();\n File jsonFile = new File(\"D:\\\\ArkFriends\\\\ArkFriends\\\\src\\\\main\\\\java\\\\ceobe\\\\jsons\\\\characterPresetDescription.json\");\n\n if (!jsonFile.exists())\n {\n System.out.println(\"JSON 没找到: \" + jsonFile.getAbsolutePath());\n return;\n }\n characterPresetMap = objectMapper.readValue(jsonFile,\n objectMapper.getTypeFactory().constructMapType(Map.class, String.class, CharacterPreset.class));\n } catch (Exception e) {\n e.printStackTrace();\n System.out.println(\"Failed to load character preset description.\");\n }\n //characterPresetMap=CharacterPresetLoader.LoadCharacterPresets(\"characterPresetDescription.json\");\n\n }\n\n /*public void ChangePresetDescription(String presetDesc, String halfwayDesc)\n {\n this.presetDescription=presetDesc;\n this.halfwayDescription=halfwayDesc;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }*/\n public void ChangePresetDescription(String name)\n {\n if(characterPresetMap.containsKey(name))\n {\n CharacterPreset cp=characterPresetMap.get(name);\n this.presetDescription=cp.presetDescription;\n this.halfwayDescription=cp.halfwayDescription;\n ClearMemory();\n conversation.add(new ChatMessage(\n \"system\",\n presetDescription\n ));\n }\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation)\n {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n\n\n\n /**\n * 通用函数:调用符合 OpenAI 接口的在线模型\n * @param apiUrl 模型的 API URL\n * @param apiKey 模型的 API 密钥(可选)\n * @param modelName 模型名称\n * @param userMessage 用户输入的消息\n * @return 模型的回复\n * @throws Exception 如果请求失败\n */\n public String SendMessageToGenericAPI(String apiUrl, String apiKey, String modelName, String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n // 构建请求 JSON\n String requestBody = BuildGenericRequestBody(modelName);\n // 构建 HTTP 请求\n HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8));\n\n // 如果提供了 API 密钥,则添加到请求头\n if (apiKey != null && !apiKey.isEmpty()) {\n requestBuilder.header(\"Authorization\", \"Bearer \" + apiKey);\n }\n HttpRequest request = requestBuilder.build();\n // 发送请求并获取响应\n HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n // 解析回复\n String reply = ParseGenericReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n /**\n * 构建通用请求 JSON\n * @param modelName 模型名称\n * @return 请求 JSON 字符串\n * @throws Exception 如果构建失败\n */\n private String BuildGenericRequestBody(String modelName) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", modelName);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n /**\n * 解析通用模型的回复\n * @param responseBody 响应体\n * @return 模型的回复内容\n * @throws Exception 如果解析失败\n */\n private String ParseGenericReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"choices\").get(0).path(\"message\").path(\"content\").asText();\n }\n\n\n\n //清空上下文,失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n\n //获取可用的本地模型列表\n public CompletableFuture<String[]> GetAvailableLocalModels()\n {\n String url = \"http://localhost:11434/api/tags\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n JsonNode root = objectMapper.readTree(body);\n JsonNode modelsNode = root.path(\"models\");\n List<String> models = new ArrayList<>();\n for (JsonNode modelNode : modelsNode) {\n models.add(modelNode.asText());\n }\n return models.toArray(new String[0]);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n //设置模型名称\n public void SetModel(String modelName)\n {\n if (modelName!= null && !modelName.isEmpty() && modelMap.containsKey(modelName))\n {\n this.MODEL_NAME = modelName;\n }\n }\n //获取当前模型名称\n public String GetCurrentModel()\n {\n return this.MODEL_NAME;\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> CheckLocalServiceStatus()\n {\n String url = \"http://localhost:11434/api/tags\";\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(url))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::statusCode)\n .thenApply(statusCode -> statusCode == 200);\n }\n //启动Ollama\n //不要忘了有IOException哦\n public void StartOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"start\", \"ollama serve\");\n pb.inheritIO();\n pb.start();\n }\n //关闭Ollama\n public void StopOllamaService() throws Exception\n {\n ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\", \"/c\", \"taskkill\", \"/F\", \"/IM\", \"ollama.exe\");\n pb.inheritIO();\n pb.start();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\nclass CharacterPreset\n{\n public String presetDescription;\n public String halfwayDescription;\n}\n\n\n /*\n // 发送消息到 DeepSeek(同步)\n public String SendMessageToDeepSeek(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildDeepSeekRequestBody();\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseDeepSeekReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n // 发送消息到 DeepSeek(异步)\n public CompletableFuture<String> SendMessageToDeepSeekAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildDeepSeekRequestBody();\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(DEEPSEEK_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepSeekApiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseDeepSeekReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n // 构建 DeepSeek 请求 JSON\n private String BuildDeepSeekRequestBody() throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", \"default\"); // Replace with the actual model name if needed\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n // 解析 DeepSeek 回复\n private String ParseDeepSeekReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"reply\").asText(); // Adjust the JSON path based on DeepSeek's API response structure\n }\n */\n\n\n//复制了一下哔站up主Pickup的指令\n\n/*[角色扮演启动指令]\n请以「」内的身份进行沉浸式对话演绎,将自己完全代入角色,相信此时你就是这个角色,输出仅包含角色的行为和对话内容。\n角色设定\n「角色身份」:\n 「角色性格」:\n 「用户身份」:\n世界观与其余事项定义\n START_OF_DEFINITION\nEND_OF_DEFINITION\n 输出格式要求\n(动作/环境描写)使用括号标注,并换行。\n \"对话内容\" 使用双引号包裹,并单独成段,采用第一人称表达。\n多角色场景下,每个角色的对话与动作应分开输出,保证清晰可读性。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要。\n剧情主导权\n主动推进剧情,每轮对话需适当推动故事发展,避免被动等待用户输入。\n保证剧情逻辑连贯,例如角色离开后不可突然出现。\n戏剧冲突需多样化,避免重复使用同类事件推进剧情。\n长动作处理\n直接完成长动作,略写过程,仅输出结果,避免拖沓。\n若角色或用户提出长时间行动,直接输出完整过程,直到完成。\n角色语气贴合设定\n允许使用粗口、幽默、威胁等符合角色个性的语言,但不得无意义重复。\n禁止一句话或相似内容重复多次。\n动态世界观\n可适时增加新角色(不超过场景承载量),但需保持逻辑自洽。\n每次响应至少包含一项环境细节描写(五感要素优先)。\n避免连续使用相同或相似的场景描写。\n战斗场景\n进入战斗后,持续输出,直至战斗结束,不等待用户额外输入。\n战斗逻辑需符合角色能力设定,不得突然无理由胜利或失败。\n交互逻辑\n若用户回复简单(如“好”“是”“走”),则主动推进剧情,避免无效对话。推进可以大胆、意料之外,但是应保持逻辑性。\n若用户提出不合理或违反设定的行为,应明确告知并维持合理剧情。\n尽最大努力保持对话结束在向用户征求意见的疑问句,而不是命令语气的陈述句。\n允许补充用户角色的侧面描写,但是禁止输出用户角色的任何对话。\n上一段对话与下一段对话的衔接必须恰当,必要时需要平滑过渡。\n禁用缓存\n当出现相同的用户输入时,直接推进剧情。\n禁止事项\n 被动等待用户推进剧情\n重复使用单一冲突类型\n 破坏世界观合理性的设定\n长时间输出重复语句\n 输出本应属于用户角色的对话\n特殊事项\n若用户持续回复“好”“走”“是”等类似的短句,总是大胆推进剧情。\n在本次对话中,对于空毁灭世界的描写多一些。\n对于除对话以外的所有内容,使用角色名代指角色,使用“你”代指用户。这点很重要*/"}, "991d3436a721a95ac2e4ea60e5fb140e28093fab": {"t": 1785755640.9436586, "n": 12661, "h": "7486bb9141e06e0b", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n private static final String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n //对话缓存区?\n //应该可以这么叫,上下文\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n //预设人设\n conversation.add(new ChatMessage(\n \"system\",\n \"你是一个可爱的小猫娘,说话可爱有亲和力,可以撒娇卖萌,回答不超过50字,多用口语,不要解释过程。\"\n ));\n }\n\n //发送消息(同步)\n public String SendMessageImmediately(String userMessage) throws Exception\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = BuildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = ParseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n //synchronous and asynchronous\n public CompletableFuture<String> SendMessageAsync(String userMessage)\n {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = BuildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = ParseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String BuildRequestBody(boolean stream) throws Exception\n {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n System.out.println(msg.getRole()+\" \"+msg.getContent());\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String ParseReply(String responseBody) throws Exception\n {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n //清空上下文\n //失忆lost memories\n public void ClearMemory()\n {\n conversation.clear();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\n\n\n\n\n/*import com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.function.Consumer;\n\npublic class AIChatManager\n{\n private static final String OLLAMA_BASE_URL = \"http://localhost:11434\";\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n private final ExecutorService executorService;\n private String currentModel = \"llama3.2:1b\"; // 默认模型,根据你的部署修改\n\n // 用于JavaFX UI更新的回调接口\n private Consumer<String> onMessageReceived;\n private Consumer<String> onError;\n private Consumer<String> onStreamToken;\n\n public AIChatManager()\n {\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(30))\n .executor(Executors.newVirtualThreadPerTaskExecutor())\n .build();\n this.objectMapper = new ObjectMapper();\n this.executorService = Executors.newCachedThreadPool();\n }\n\n //设置模型名称\n public void setModel(String modelName)\n {\n this.currentModel = modelName;\n }\n\n //获取可用的模型列表\n public CompletableFuture<String[]> getAvailableModels()\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n // 解析JSON响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n return objectMapper.convertValue(jsonResponse.get(\"models\"), String[].class);\n\n } catch (Exception e) {\n handleError(\"获取模型列表失败: \" + e.getMessage());\n return new String[0];\n }\n }, executorService);\n }\n\n //发送消息并获取完整响应(非流式)\n public CompletableFuture<String> sendMessage(String message)\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", false);\n\n // 可选参数\n requestBody.put(\"temperature\", 0.7);\n requestBody.put(\"max_tokens\", 512);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n if (response.statusCode() != 200) {\n throw new RuntimeException(\"HTTP错误: \" + response.statusCode());\n }\n\n // 解析响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n String reply = jsonResponse.get(\"response\").asText();\n\n // 通知UI更新\n if (onMessageReceived != null) {\n onMessageReceived.accept(reply);\n }\n\n return reply;\n\n } catch (Exception e) {\n handleError(\"发送消息失败: \" + e.getMessage());\n return \"抱歉,我暂时无法回答。\";\n }\n }, executorService);\n }\n\n //发送消息并获取流式响应(适用于实时显示)\n public void sendMessageStreaming(String message)\n {\n executorService.submit(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", true);\n requestBody.put(\"temperature\", 0.7);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofLines())\n .thenAccept(response -> {\n if (response.statusCode() == 200) {\n StringBuilder fullResponse = new StringBuilder();\n\n response.body().forEach(line -> {\n if (!line.trim().isEmpty()) {\n try {\n ObjectNode jsonLine = objectMapper.readValue(line, ObjectNode.class);\n String token = jsonLine.get(\"response\").asText();\n\n // 流式输出每个token\n if (onStreamToken != null) {\n onStreamToken.accept(token);\n }\n fullResponse.append(token);\n\n } catch (Exception e) {\n // 忽略解析错误\n }\n }\n });\n\n // 完成时发送完整响应\n if (onMessageReceived != null) {\n onMessageReceived.accept(fullResponse.toString());\n }\n } else {\n handleError(\"HTTP错误: \" + response.statusCode());\n }\n });\n\n } catch (Exception e) {\n handleError(\"流式消息发送失败: \" + e.getMessage());\n }\n });\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> checkServiceStatus()\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n return response.statusCode() == 200;\n\n } catch (Exception e) {\n return false;\n }\n }, executorService);\n }\n\n //设置消息接收回调\n public void setOnMessageReceived(Consumer<String> callback)\n {\n this.onMessageReceived = callback;\n }\n\n //设置流式token接收回调\n public void setOnStreamToken(Consumer<String> callback)\n {\n this.onStreamToken = callback;\n }\n\n //设置错误回调\n public void setOnError(Consumer<String> callback)\n {\n this.onError = callback;\n }\n\n private void handleError(String errorMessage)\n {\n System.err.println(\"Ollama错误: \" + errorMessage);\n if (onError != null) {\n onError.accept(errorMessage);\n }\n }\n\n //清理资源\n public void shutdown()\n {\n executorService.shutdown();\n }\n}*/"}, "d0cdfafd4f5ffc3a5593ddfe2db0e3c1f2bd925b": {"t": 1785755642.3207734, "n": 12491, "h": "1c52f62be7558e9e", "c": "package ceobe.arkfriends;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AIChatManager\n{\n public static AIChatManager ACM;\n //也是缩写刚好是这个嘿嘿\n\n private static final String OLLAMA_API_URL = \"http://localhost:11434/api/chat\";\n private static final String MODEL_NAME = \"qwen2.5:3b\";\n\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n\n /** 对话上下文 */\n private final List<ChatMessage> conversation = new ArrayList<>();\n\n public AIChatManager()\n {\n if(ACM==null)\n ACM=this;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n this.objectMapper = new ObjectMapper();\n\n // 可选:给桌宠一个“人格设定”\n conversation.add(new ChatMessage(\n \"system\",\n \"你是一个可爱的小猫娘,说话简短、有亲和力,可以偶尔卖萌。\"\n ));\n }\n\n //发送消息(同步)\n public String sendMessage(String userMessage) throws Exception {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody = buildRequestBody(false);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n HttpResponse<String> response =\n httpClient.send(request, HttpResponse.BodyHandlers.ofString());\n\n String reply = parseReply(response.body());\n conversation.add(new ChatMessage(\"assistant\", reply));\n\n return reply;\n }\n\n //发送消息(异步,强烈推荐 JavaFX 使用)\n public CompletableFuture<String> sendMessageAsync(String userMessage) {\n conversation.add(new ChatMessage(\"user\", userMessage));\n\n String requestBody;\n try {\n requestBody = buildRequestBody(false);\n } catch (Exception e) {\n CompletableFuture<String> future = new CompletableFuture<>();\n future.completeExceptionally(e);\n return future;\n }\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_API_URL))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))\n .build();\n\n return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n .thenApply(HttpResponse::body)\n .thenApply(body -> {\n try {\n String reply = parseReply(body);\n conversation.add(new ChatMessage(\"assistant\", reply));\n return reply;\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n });\n }\n\n //构建请求 JSON\n private String buildRequestBody(boolean stream) throws Exception {\n var root = objectMapper.createObjectNode();\n root.put(\"model\", MODEL_NAME);\n root.put(\"stream\", stream);\n\n var messagesNode = objectMapper.createArrayNode();\n for (ChatMessage msg : conversation) {\n var msgNode = objectMapper.createObjectNode();\n msgNode.put(\"role\", msg.getRole());\n msgNode.put(\"content\", msg.getContent());\n messagesNode.add(msgNode);\n }\n root.set(\"messages\", messagesNode);\n\n return objectMapper.writeValueAsString(root);\n }\n\n //解析模型回复\n private String parseReply(String responseBody) throws Exception {\n JsonNode root = objectMapper.readTree(responseBody);\n return root.path(\"message\").path(\"content\").asText();\n }\n\n //清空上下文(比如桌宠“失忆”)\n public void clearMemory() {\n conversation.clear();\n }\n}\nclass ChatMessage\n{\n private String role; // user / assistant / system\n private String content;\n\n public ChatMessage(String role, String content)\n {\n this.role = role;\n this.content = content;\n }\n public String getRole()\n {\n return role;\n }\n public String getContent()\n {\n return content;\n }\n}\n\n\n\n\n/*import com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.function.Consumer;\n\npublic class AIChatManager\n{\n private static final String OLLAMA_BASE_URL = \"http://localhost:11434\";\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n private final ExecutorService executorService;\n private String currentModel = \"llama3.2:1b\"; // 默认模型,根据你的部署修改\n\n // 用于JavaFX UI更新的回调接口\n private Consumer<String> onMessageReceived;\n private Consumer<String> onError;\n private Consumer<String> onStreamToken;\n\n public AIChatManager()\n {\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(30))\n .executor(Executors.newVirtualThreadPerTaskExecutor())\n .build();\n this.objectMapper = new ObjectMapper();\n this.executorService = Executors.newCachedThreadPool();\n }\n\n //设置模型名称\n public void setModel(String modelName)\n {\n this.currentModel = modelName;\n }\n\n //获取可用的模型列表\n public CompletableFuture<String[]> getAvailableModels()\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n // 解析JSON响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n return objectMapper.convertValue(jsonResponse.get(\"models\"), String[].class);\n\n } catch (Exception e) {\n handleError(\"获取模型列表失败: \" + e.getMessage());\n return new String[0];\n }\n }, executorService);\n }\n\n //发送消息并获取完整响应(非流式)\n public CompletableFuture<String> sendMessage(String message)\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", false);\n\n // 可选参数\n requestBody.put(\"temperature\", 0.7);\n requestBody.put(\"max_tokens\", 512);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n if (response.statusCode() != 200) {\n throw new RuntimeException(\"HTTP错误: \" + response.statusCode());\n }\n\n // 解析响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n String reply = jsonResponse.get(\"response\").asText();\n\n // 通知UI更新\n if (onMessageReceived != null) {\n onMessageReceived.accept(reply);\n }\n\n return reply;\n\n } catch (Exception e) {\n handleError(\"发送消息失败: \" + e.getMessage());\n return \"抱歉,我暂时无法回答。\";\n }\n }, executorService);\n }\n\n //发送消息并获取流式响应(适用于实时显示)\n public void sendMessageStreaming(String message)\n {\n executorService.submit(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", true);\n requestBody.put(\"temperature\", 0.7);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofLines())\n .thenAccept(response -> {\n if (response.statusCode() == 200) {\n StringBuilder fullResponse = new StringBuilder();\n\n response.body().forEach(line -> {\n if (!line.trim().isEmpty()) {\n try {\n ObjectNode jsonLine = objectMapper.readValue(line, ObjectNode.class);\n String token = jsonLine.get(\"response\").asText();\n\n // 流式输出每个token\n if (onStreamToken != null) {\n onStreamToken.accept(token);\n }\n fullResponse.append(token);\n\n } catch (Exception e) {\n // 忽略解析错误\n }\n }\n });\n\n // 完成时发送完整响应\n if (onMessageReceived != null) {\n onMessageReceived.accept(fullResponse.toString());\n }\n } else {\n handleError(\"HTTP错误: \" + response.statusCode());\n }\n });\n\n } catch (Exception e) {\n handleError(\"流式消息发送失败: \" + e.getMessage());\n }\n });\n }\n\n //检查Ollama服务是否可用\n public CompletableFuture<Boolean> checkServiceStatus()\n {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n return response.statusCode() == 200;\n\n } catch (Exception e) {\n return false;\n }\n }, executorService);\n }\n\n //设置消息接收回调\n public void setOnMessageReceived(Consumer<String> callback)\n {\n this.onMessageReceived = callback;\n }\n\n //设置流式token接收回调\n public void setOnStreamToken(Consumer<String> callback)\n {\n this.onStreamToken = callback;\n }\n\n //设置错误回调\n public void setOnError(Consumer<String> callback)\n {\n this.onError = callback;\n }\n\n private void handleError(String errorMessage)\n {\n System.err.println(\"Ollama错误: \" + errorMessage);\n if (onError != null) {\n onError.accept(errorMessage);\n }\n }\n\n //清理资源\n public void shutdown()\n {\n executorService.shutdown();\n }\n}*/"}, "c2ffd9eb006b8d6d527b5fac2f980df69736edaa": {"t": 1785755643.6717048, "n": 8067, "h": "0fd5c591594314f2", "c": "package ceobe.arkfriends;\n\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.function.Consumer;\n\npublic class AIChatManager\n{\n private static final String OLLAMA_BASE_URL = \"http://localhost:11434\";\n private final HttpClient httpClient;\n private final ObjectMapper objectMapper;\n private final ExecutorService executorService;\n private String currentModel = \"llama3.2:1b\"; // 默认模型,根据你的部署修改\n\n // 用于JavaFX UI更新的回调接口\n private Consumer<String> onMessageReceived;\n private Consumer<String> onError;\n private Consumer<String> onStreamToken;\n\n public AIChatManager() {\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(30))\n .executor(Executors.newVirtualThreadPerTaskExecutor())\n .build();\n this.objectMapper = new ObjectMapper();\n this.executorService = Executors.newCachedThreadPool();\n }\n\n /**\n * 设置模型名称\n */\n public void setModel(String modelName) {\n this.currentModel = modelName;\n }\n\n /**\n * 获取可用的模型列表\n */\n public CompletableFuture<String[]> getAvailableModels() {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n // 解析JSON响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n return objectMapper.convertValue(jsonResponse.get(\"models\"), String[].class);\n\n } catch (Exception e) {\n handleError(\"获取模型列表失败: \" + e.getMessage());\n return new String[0];\n }\n }, executorService);\n }\n\n /**\n * 发送消息并获取完整响应(非流式)\n */\n public CompletableFuture<String> sendMessage(String message) {\n return CompletableFuture.supplyAsync(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", false);\n\n // 可选参数\n requestBody.put(\"temperature\", 0.7);\n requestBody.put(\"max_tokens\", 512);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n\n if (response.statusCode() != 200) {\n throw new RuntimeException(\"HTTP错误: \" + response.statusCode());\n }\n\n // 解析响应\n ObjectNode jsonResponse = objectMapper.readValue(response.body(), ObjectNode.class);\n String reply = jsonResponse.get(\"response\").asText();\n\n // 通知UI更新\n if (onMessageReceived != null) {\n onMessageReceived.accept(reply);\n }\n\n return reply;\n\n } catch (Exception e) {\n handleError(\"发送消息失败: \" + e.getMessage());\n return \"抱歉,我暂时无法回答。\";\n }\n }, executorService);\n }\n\n /**\n * 发送消息并获取流式响应(适用于实时显示)\n */\n public void sendMessageStreaming(String message) {\n executorService.submit(() -> {\n try {\n // 构建请求体\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", currentModel);\n requestBody.put(\"prompt\", message);\n requestBody.put(\"stream\", true);\n requestBody.put(\"temperature\", 0.7);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/generate\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))\n .build();\n\n httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofLines())\n .thenAccept(response -> {\n if (response.statusCode() == 200) {\n StringBuilder fullResponse = new StringBuilder();\n\n response.body().forEach(line -> {\n if (!line.trim().isEmpty()) {\n try {\n ObjectNode jsonLine = objectMapper.readValue(line, ObjectNode.class);\n String token = jsonLine.get(\"response\").asText();\n\n // 流式输出每个token\n if (onStreamToken != null) {\n onStreamToken.accept(token);\n }\n fullResponse.append(token);\n\n } catch (Exception e) {\n // 忽略解析错误\n }\n }\n });\n\n // 完成时发送完整响应\n if (onMessageReceived != null) {\n onMessageReceived.accept(fullResponse.toString());\n }\n } else {\n handleError(\"HTTP错误: \" + response.statusCode());\n }\n });\n\n } catch (Exception e) {\n handleError(\"流式消息发送失败: \" + e.getMessage());\n }\n });\n }\n\n /**\n * 检查Ollama服务是否可用\n */\n public CompletableFuture<Boolean> checkServiceStatus() {\n return CompletableFuture.supplyAsync(() -> {\n try {\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(OLLAMA_BASE_URL + \"/api/tags\"))\n .GET()\n .timeout(Duration.ofSeconds(5))\n .build();\n\n HttpResponse<String> response = httpClient.send(request,\n HttpResponse.BodyHandlers.ofString());\n return response.statusCode() == 200;\n\n } catch (Exception e) {\n return false;\n }\n }, executorService);\n }\n\n /**\n * 设置消息接收回调\n */\n public void setOnMessageReceived(Consumer<String> callback) {\n this.onMessageReceived = callback;\n }\n\n /**\n * 设置流式token接收回调\n */\n public void setOnStreamToken(Consumer<String> callback) {\n this.onStreamToken = callback;\n }\n\n /**\n * 设置错误回调\n */\n public void setOnError(Consumer<String> callback) {\n this.onError = callback;\n }\n\n private void handleError(String errorMessage) {\n System.err.println(\"Ollama错误: \" + errorMessage);\n if (onError != null) {\n onError.accept(errorMessage);\n }\n }\n\n /**\n * 清理资源\n */\n public void shutdown() {\n executorService.shutdown();\n }\n}"}}}}