1
This commit is contained in:
@@ -157,13 +157,27 @@ public class SocialMediaController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取大模型接入配置(API 地址、密钥等,与 Jarvis 共用 Redis)
|
* 列出多套大模型接入配置及当前激活 id
|
||||||
*/
|
*/
|
||||||
@GetMapping("/llm-config")
|
@GetMapping("/llm-config")
|
||||||
public AjaxResult getLlmConfig()
|
public AjaxResult listLlmProfiles()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return socialMediaService.getLlmConfig();
|
return socialMediaService.listLlmProfiles();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("列出大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单套配置(编辑)
|
||||||
|
*/
|
||||||
|
@GetMapping("/llm-config/profiles/{id}")
|
||||||
|
public AjaxResult getLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.getLlmProfile(id);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("获取大模型接入配置失败", e);
|
logger.error("获取大模型接入配置失败", e);
|
||||||
return AjaxResult.error("获取失败: " + e.getMessage());
|
return AjaxResult.error("获取失败: " + e.getMessage());
|
||||||
@@ -171,32 +185,92 @@ public class SocialMediaController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存大模型接入配置
|
* 新增一套配置
|
||||||
*/
|
*/
|
||||||
@Log(title = "保存大模型接入配置", businessType = BusinessType.UPDATE)
|
@Log(title = "新增大模型接入配置", businessType = BusinessType.INSERT)
|
||||||
@PostMapping("/llm-config/save")
|
@PostMapping("/llm-config/profiles")
|
||||||
public AjaxResult saveLlmConfig(@RequestBody Map<String, Object> request)
|
public AjaxResult createLlmProfile(@RequestBody Map<String, Object> request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return socialMediaService.saveLlmConfig(request);
|
return socialMediaService.createLlmProfile(request);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("保存大模型接入配置失败", e);
|
logger.error("新增大模型接入配置失败", e);
|
||||||
return AjaxResult.error("保存失败: " + e.getMessage());
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除大模型接入配置(Jarvis 使用 yml 默认 Ollama)
|
* 更新一套配置
|
||||||
*/
|
*/
|
||||||
@Log(title = "重置大模型接入配置", businessType = BusinessType.DELETE)
|
@Log(title = "更新大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
@DeleteMapping("/llm-config")
|
@PutMapping("/llm-config/profiles/{id}")
|
||||||
public AjaxResult resetLlmConfig()
|
public AjaxResult updateLlmProfile(@PathVariable("id") String id, @RequestBody Map<String, Object> request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return socialMediaService.resetLlmConfig();
|
return socialMediaService.updateLlmProfile(id, request);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("重置大模型接入配置失败", e);
|
logger.error("更新大模型接入配置失败", e);
|
||||||
return AjaxResult.error("重置失败: " + e.getMessage());
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一套配置
|
||||||
|
*/
|
||||||
|
@Log(title = "删除大模型接入配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/llm-config/profiles/{id}")
|
||||||
|
public AjaxResult deleteLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.deleteLlmProfile(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("删除大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 激活指定配置为当前使用
|
||||||
|
*/
|
||||||
|
@Log(title = "激活大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/llm-config/active/{id}")
|
||||||
|
public AjaxResult setActiveLlmProfile(@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.setActiveLlmProfile(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("激活大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消激活(Jarvis 使用 yml 默认 Ollama)
|
||||||
|
*/
|
||||||
|
@Log(title = "取消激活大模型接入配置", businessType = BusinessType.UPDATE)
|
||||||
|
@DeleteMapping("/llm-config/active")
|
||||||
|
public AjaxResult clearActiveLlmProfile()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.clearActiveLlmProfile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("取消激活大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有套及旧版单键
|
||||||
|
*/
|
||||||
|
@Log(title = "清空大模型接入配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/llm-config")
|
||||||
|
public AjaxResult resetAllLlmConfig()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return socialMediaService.resetAllLlmConfig();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("清空大模型接入配置失败", e);
|
||||||
|
return AjaxResult.error("清除失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,19 +73,28 @@ public interface ISocialMediaService
|
|||||||
*/
|
*/
|
||||||
Map<String, Object> generateXianyuWenan(String title, String remark);
|
Map<String, Object> generateXianyuWenan(String title, String remark);
|
||||||
|
|
||||||
/**
|
/** 列出多套大模型接入配置及当前激活的 id(与 Jarvis 共用 Redis) */
|
||||||
* 获取社媒大模型接入配置(与 Jarvis 共用 Redis,供 OpenAI 兼容接口或 Ollama)
|
com.ruoyi.common.core.domain.AjaxResult listLlmProfiles();
|
||||||
*/
|
|
||||||
com.ruoyi.common.core.domain.AjaxResult getLlmConfig();
|
|
||||||
|
|
||||||
/**
|
/** 获取单套配置(编辑用,密钥脱敏) */
|
||||||
* 保存社媒大模型接入配置
|
com.ruoyi.common.core.domain.AjaxResult getLlmProfile(String id);
|
||||||
*/
|
|
||||||
com.ruoyi.common.core.domain.AjaxResult saveLlmConfig(Map<String, Object> request);
|
|
||||||
|
|
||||||
/**
|
/** 新增一套配置 */
|
||||||
* 清除 Redis 中的接入配置,Jarvis 侧回退为 application.yml 默认 Ollama
|
com.ruoyi.common.core.domain.AjaxResult createLlmProfile(Map<String, Object> request);
|
||||||
*/
|
|
||||||
com.ruoyi.common.core.domain.AjaxResult resetLlmConfig();
|
/** 更新一套配置 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult updateLlmProfile(String id, Map<String, Object> request);
|
||||||
|
|
||||||
|
/** 删除一套配置 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult deleteLlmProfile(String id);
|
||||||
|
|
||||||
|
/** 激活指定 id,Jarvis 调用时使用该套 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult setActiveLlmProfile(String id);
|
||||||
|
|
||||||
|
/** 取消激活(Jarvis 回退 yml 默认 Ollama) */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult clearActiveLlmProfile();
|
||||||
|
|
||||||
|
/** 清空所有套及旧版单键,Jarvis 回退默认 */
|
||||||
|
com.ruoyi.common.core.domain.AjaxResult resetAllLlmConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,12 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
@@ -60,7 +64,9 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段;须为 Java 正则,匹配到的内容会被删除");
|
put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段;须为 Java 正则,匹配到的内容会被删除");
|
||||||
}};
|
}};
|
||||||
|
|
||||||
/** 与 Jarvis_java SocialMediaLlmClient 使用相同 Redis 键 */
|
/** 多套大模型 JSON 存储,与 Jarvis_java SocialMediaLlmClient 一致 */
|
||||||
|
private static final String LLM_PROFILES_STORE_KEY = "social_media:llm:profiles_store";
|
||||||
|
/** 旧版单套 Redis 键,首次读取时迁移进 profiles_store 后删除 */
|
||||||
private static final String LLM_KEY_MODE = "social_media:llm:mode";
|
private static final String LLM_KEY_MODE = "social_media:llm:mode";
|
||||||
private static final String LLM_KEY_BASE_URL = "social_media:llm:base_url";
|
private static final String LLM_KEY_BASE_URL = "social_media:llm:base_url";
|
||||||
private static final String LLM_KEY_API_KEY = "social_media:llm:api_key";
|
private static final String LLM_KEY_API_KEY = "social_media:llm:api_key";
|
||||||
@@ -518,35 +524,257 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AjaxResult getLlmConfig() {
|
public AjaxResult listLlmProfiles() {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
if (redisTemplate == null) {
|
if (redisTemplate == null) {
|
||||||
data.put("redisAvailable", false);
|
data.put("redisAvailable", false);
|
||||||
data.put("mode", LLM_MODE_OLLAMA);
|
data.put("activeId", null);
|
||||||
data.put("baseUrl", "");
|
data.put("profiles", new ArrayList<>());
|
||||||
data.put("model", "");
|
|
||||||
data.put("hasApiKey", false);
|
|
||||||
data.put("apiKeyMasked", null);
|
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
}
|
}
|
||||||
data.put("redisAvailable", true);
|
data.put("redisAvailable", true);
|
||||||
String modeRaw = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODE));
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
String mode = StringUtils.isEmpty(modeRaw) ? LLM_MODE_OLLAMA : modeRaw.toLowerCase();
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
data.put("mode", mode);
|
if (profiles == null) {
|
||||||
data.put("baseUrl", StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_BASE_URL)));
|
profiles = new JSONObject();
|
||||||
data.put("model", StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODEL)));
|
}
|
||||||
String rawKey = redisTemplate.opsForValue().get(LLM_KEY_API_KEY);
|
String activeId = store.getString("activeId");
|
||||||
boolean hasKey = StringUtils.isNotEmpty(StringUtils.trim(rawKey));
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
data.put("hasApiKey", hasKey);
|
for (String id : profiles.keySet()) {
|
||||||
data.put("apiKeyMasked", maskLlmApiKey(rawKey));
|
JSONObject p = profiles.getJSONObject(id);
|
||||||
|
if (p == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.add(profileToSummaryMap(id, p, StringUtils.isNotEmpty(activeId) && activeId.equals(id)));
|
||||||
|
}
|
||||||
|
list.sort(Comparator.comparing(m -> String.valueOf(m.getOrDefault("name", "")), String.CASE_INSENSITIVE_ORDER));
|
||||||
|
data.put("activeId", activeId);
|
||||||
|
data.put("profiles", list);
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("读取大模型接入配置失败", e);
|
log.error("列出大模型配置失败", e);
|
||||||
return AjaxResult.error("读取失败: " + e.getMessage());
|
return AjaxResult.error("读取失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult getLlmProfile(String id) {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(id)) {
|
||||||
|
return AjaxResult.error("配置 id 不能为空");
|
||||||
|
}
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
|
if (profiles == null || !profiles.containsKey(id)) {
|
||||||
|
return AjaxResult.error("配置不存在");
|
||||||
|
}
|
||||||
|
JSONObject p = profiles.getJSONObject(id);
|
||||||
|
Map<String, Object> row = new HashMap<>();
|
||||||
|
row.put("id", id);
|
||||||
|
row.put("name", StringUtils.trim(p.getString("name")));
|
||||||
|
row.put("mode", normalizeLlmMode(p.getString("mode")));
|
||||||
|
row.put("baseUrl", StringUtils.trim(p.getString("baseUrl")));
|
||||||
|
row.put("model", StringUtils.trim(p.getString("model")));
|
||||||
|
String rawKey = p.getString("apiKey");
|
||||||
|
row.put("hasApiKey", StringUtils.isNotEmpty(StringUtils.trim(rawKey)));
|
||||||
|
row.put("apiKeyMasked", maskLlmApiKey(rawKey));
|
||||||
|
return AjaxResult.success(row);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取大模型配置失败", e);
|
||||||
|
return AjaxResult.error("读取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult createLlmProfile(Map<String, Object> request) {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置,无法保存");
|
||||||
|
}
|
||||||
|
AjaxResult validation = validateLlmProfilePayload(request, true);
|
||||||
|
if (validation != null) {
|
||||||
|
return validation;
|
||||||
|
}
|
||||||
|
String name = request.get("name").toString().trim();
|
||||||
|
String mode = request.get("mode").toString().trim().toLowerCase();
|
||||||
|
String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
|
||||||
|
String model = request.get("model") == null ? "" : request.get("model").toString().trim();
|
||||||
|
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
|
if (profiles == null) {
|
||||||
|
profiles = new JSONObject();
|
||||||
|
store.put("profiles", profiles);
|
||||||
|
}
|
||||||
|
String id = UUID.randomUUID().toString();
|
||||||
|
JSONObject p = new JSONObject();
|
||||||
|
p.put("name", name);
|
||||||
|
p.put("mode", mode);
|
||||||
|
p.put("baseUrl", baseUrl);
|
||||||
|
p.put("model", model);
|
||||||
|
String apiKey = "";
|
||||||
|
Object apiKeyObj = request.get("apiKey");
|
||||||
|
if (apiKeyObj != null && StringUtils.isNotEmpty(apiKeyObj.toString().trim())) {
|
||||||
|
apiKey = apiKeyObj.toString().trim();
|
||||||
|
}
|
||||||
|
p.put("apiKey", apiKey);
|
||||||
|
profiles.put(id, p);
|
||||||
|
if (StringUtils.isEmpty(store.getString("activeId")) && profiles.size() == 1) {
|
||||||
|
store.put("activeId", id);
|
||||||
|
}
|
||||||
|
persistLlmStore(store);
|
||||||
|
log.info("新增大模型配置: id={}, name={}", id, name);
|
||||||
|
Map<String, Object> res = new HashMap<>();
|
||||||
|
res.put("id", id);
|
||||||
|
return AjaxResult.success(res);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("新增大模型配置失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult updateLlmProfile(String id, Map<String, Object> request) {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置,无法保存");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(id)) {
|
||||||
|
return AjaxResult.error("配置 id 不能为空");
|
||||||
|
}
|
||||||
|
AjaxResult validation = validateLlmProfilePayload(request, true);
|
||||||
|
if (validation != null) {
|
||||||
|
return validation;
|
||||||
|
}
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
|
if (profiles == null || !profiles.containsKey(id)) {
|
||||||
|
return AjaxResult.error("配置不存在");
|
||||||
|
}
|
||||||
|
JSONObject p = profiles.getJSONObject(id);
|
||||||
|
p.put("name", request.get("name").toString().trim());
|
||||||
|
String mode = request.get("mode").toString().trim().toLowerCase();
|
||||||
|
p.put("mode", mode);
|
||||||
|
String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
|
||||||
|
String model = request.get("model") == null ? "" : request.get("model").toString().trim();
|
||||||
|
p.put("baseUrl", baseUrl);
|
||||||
|
p.put("model", model);
|
||||||
|
|
||||||
|
boolean clearApiKey = Boolean.TRUE.equals(request.get("clearApiKey"))
|
||||||
|
|| "true".equalsIgnoreCase(String.valueOf(request.get("clearApiKey")));
|
||||||
|
if (clearApiKey) {
|
||||||
|
p.put("apiKey", "");
|
||||||
|
} else {
|
||||||
|
Object apiKeyObj = request.get("apiKey");
|
||||||
|
if (apiKeyObj != null && StringUtils.isNotEmpty(apiKeyObj.toString().trim())) {
|
||||||
|
p.put("apiKey", apiKeyObj.toString().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
persistLlmStore(store);
|
||||||
|
log.info("更新大模型配置: id={}", id);
|
||||||
|
return AjaxResult.success("保存成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新大模型配置失败", e);
|
||||||
|
return AjaxResult.error("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult deleteLlmProfile(String id) {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(id)) {
|
||||||
|
return AjaxResult.error("配置 id 不能为空");
|
||||||
|
}
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
|
if (profiles == null || !profiles.containsKey(id)) {
|
||||||
|
return AjaxResult.error("配置不存在");
|
||||||
|
}
|
||||||
|
profiles.remove(id);
|
||||||
|
String activeId = store.getString("activeId");
|
||||||
|
if (id.equals(activeId)) {
|
||||||
|
if (profiles.isEmpty()) {
|
||||||
|
store.put("activeId", null);
|
||||||
|
} else {
|
||||||
|
store.put("activeId", profiles.keySet().iterator().next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
persistLlmStore(store);
|
||||||
|
log.info("删除大模型配置: id={}", id);
|
||||||
|
return AjaxResult.success("删除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除大模型配置失败", e);
|
||||||
|
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult setActiveLlmProfile(String id) {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(id)) {
|
||||||
|
return AjaxResult.error("配置 id 不能为空");
|
||||||
|
}
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
JSONObject profiles = store.getJSONObject("profiles");
|
||||||
|
if (profiles == null || !profiles.containsKey(id)) {
|
||||||
|
return AjaxResult.error("配置不存在");
|
||||||
|
}
|
||||||
|
store.put("activeId", id);
|
||||||
|
persistLlmStore(store);
|
||||||
|
log.info("已激活大模型配置: id={}", id);
|
||||||
|
return AjaxResult.success("已切换为当前使用的配置");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("激活大模型配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult clearActiveLlmProfile() {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置");
|
||||||
|
}
|
||||||
|
JSONObject store = loadOrMigrateLlmStore();
|
||||||
|
store.put("activeId", null);
|
||||||
|
persistLlmStore(store);
|
||||||
|
log.info("已取消激活大模型配置");
|
||||||
|
return AjaxResult.success("已取消激活,Jarvis 将使用默认 Ollama");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("取消激活大模型配置失败", e);
|
||||||
|
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AjaxResult resetAllLlmConfig() {
|
||||||
|
try {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return AjaxResult.error("Redis未配置");
|
||||||
|
}
|
||||||
|
redisTemplate.delete(LLM_PROFILES_STORE_KEY);
|
||||||
|
redisTemplate.delete(LLM_KEY_MODE);
|
||||||
|
redisTemplate.delete(LLM_KEY_BASE_URL);
|
||||||
|
redisTemplate.delete(LLM_KEY_API_KEY);
|
||||||
|
redisTemplate.delete(LLM_KEY_MODEL);
|
||||||
|
log.info("已清空所有大模型接入配置");
|
||||||
|
return AjaxResult.success("已清空,Jarvis 将使用默认 Ollama");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清空大模型配置失败", e);
|
||||||
|
return AjaxResult.error("清除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String maskLlmApiKey(String secret) {
|
private static String maskLlmApiKey(String secret) {
|
||||||
if (StringUtils.isEmpty(StringUtils.trim(secret))) {
|
if (StringUtils.isEmpty(StringUtils.trim(secret))) {
|
||||||
return null;
|
return null;
|
||||||
@@ -558,78 +786,114 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
return "****" + s.substring(s.length() - 4);
|
return "****" + s.substring(s.length() - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static String normalizeLlmMode(String mode) {
|
||||||
public AjaxResult saveLlmConfig(Map<String, Object> request) {
|
if (StringUtils.isEmpty(mode)) {
|
||||||
try {
|
return LLM_MODE_OLLAMA;
|
||||||
if (redisTemplate == null) {
|
|
||||||
return AjaxResult.error("Redis未配置,无法保存");
|
|
||||||
}
|
|
||||||
String mode = request.get("mode") == null ? "" : request.get("mode").toString().trim().toLowerCase();
|
|
||||||
if (!LLM_MODE_OLLAMA.equals(mode) && !LLM_MODE_OPENAI.equals(mode)) {
|
|
||||||
return AjaxResult.error("接入方式只能是 ollama 或 openai");
|
|
||||||
}
|
|
||||||
String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
|
|
||||||
String model = request.get("model") == null ? "" : request.get("model").toString().trim();
|
|
||||||
|
|
||||||
if (LLM_MODE_OPENAI.equals(mode)) {
|
|
||||||
if (StringUtils.isEmpty(baseUrl)) {
|
|
||||||
return AjaxResult.error("OpenAI 兼容模式须填写 Chat Completions 完整地址(如 https://api.openai.com/v1/chat/completions)");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(model)) {
|
|
||||||
return AjaxResult.error("OpenAI 兼容模式须填写模型名称");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean clearApiKey = Boolean.TRUE.equals(request.get("clearApiKey"))
|
|
||||||
|| "true".equalsIgnoreCase(String.valueOf(request.get("clearApiKey")));
|
|
||||||
if (clearApiKey) {
|
|
||||||
redisTemplate.delete(LLM_KEY_API_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object apiKeyObj = request.get("apiKey");
|
|
||||||
if (!clearApiKey && apiKeyObj != null) {
|
|
||||||
String newKey = apiKeyObj.toString().trim();
|
|
||||||
if (StringUtils.isNotEmpty(newKey)) {
|
|
||||||
redisTemplate.opsForValue().set(LLM_KEY_API_KEY, newKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redisTemplate.opsForValue().set(LLM_KEY_MODE, mode);
|
|
||||||
if (StringUtils.isEmpty(baseUrl)) {
|
|
||||||
redisTemplate.delete(LLM_KEY_BASE_URL);
|
|
||||||
} else {
|
|
||||||
redisTemplate.opsForValue().set(LLM_KEY_BASE_URL, baseUrl);
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(model)) {
|
|
||||||
redisTemplate.delete(LLM_KEY_MODEL);
|
|
||||||
} else {
|
|
||||||
redisTemplate.opsForValue().set(LLM_KEY_MODEL, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("保存大模型接入配置成功, mode={}", mode);
|
|
||||||
return AjaxResult.success("保存成功");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("保存大模型接入配置失败", e);
|
|
||||||
return AjaxResult.error("保存失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
String m = mode.trim().toLowerCase();
|
||||||
|
return LLM_MODE_OPENAI.equals(m) ? LLM_MODE_OPENAI : LLM_MODE_OLLAMA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public AjaxResult resetLlmConfig() {
|
* @return 校验失败时返回 error AjaxResult,成功返回 null
|
||||||
try {
|
*/
|
||||||
if (redisTemplate == null) {
|
private AjaxResult validateLlmProfilePayload(Map<String, Object> request, boolean requireName) {
|
||||||
return AjaxResult.error("Redis未配置");
|
if (request == null) {
|
||||||
}
|
return AjaxResult.error("请求体不能为空");
|
||||||
redisTemplate.delete(LLM_KEY_MODE);
|
|
||||||
redisTemplate.delete(LLM_KEY_BASE_URL);
|
|
||||||
redisTemplate.delete(LLM_KEY_API_KEY);
|
|
||||||
redisTemplate.delete(LLM_KEY_MODEL);
|
|
||||||
log.info("已清除大模型接入 Redis 配置");
|
|
||||||
return AjaxResult.success("已恢复为 Jarvis 默认 Ollama 配置");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("清除大模型接入配置失败", e);
|
|
||||||
return AjaxResult.error("清除失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
Object nameObj = request.get("name");
|
||||||
|
if (requireName && (nameObj == null || StringUtils.isEmpty(StringUtils.trim(nameObj.toString())))) {
|
||||||
|
return AjaxResult.error("请填写配置名称(用于区分多套)");
|
||||||
|
}
|
||||||
|
String mode = request.get("mode") == null ? "" : request.get("mode").toString().trim().toLowerCase();
|
||||||
|
if (!LLM_MODE_OLLAMA.equals(mode) && !LLM_MODE_OPENAI.equals(mode)) {
|
||||||
|
return AjaxResult.error("接入方式只能是 ollama 或 openai");
|
||||||
|
}
|
||||||
|
String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
|
||||||
|
String model = request.get("model") == null ? "" : request.get("model").toString().trim();
|
||||||
|
if (LLM_MODE_OPENAI.equals(mode)) {
|
||||||
|
if (StringUtils.isEmpty(baseUrl)) {
|
||||||
|
return AjaxResult.error("OpenAI 兼容模式须填写 Chat Completions 完整地址");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(model)) {
|
||||||
|
return AjaxResult.error("OpenAI 兼容模式须填写模型名称");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> profileToSummaryMap(String id, JSONObject p, boolean active) {
|
||||||
|
Map<String, Object> m = new HashMap<>();
|
||||||
|
m.put("id", id);
|
||||||
|
m.put("name", StringUtils.trim(p.getString("name")));
|
||||||
|
m.put("mode", normalizeLlmMode(p.getString("mode")));
|
||||||
|
m.put("baseUrl", StringUtils.trim(p.getString("baseUrl")));
|
||||||
|
m.put("model", StringUtils.trim(p.getString("model")));
|
||||||
|
String rawKey = p.getString("apiKey");
|
||||||
|
m.put("hasApiKey", StringUtils.isNotEmpty(StringUtils.trim(rawKey)));
|
||||||
|
m.put("apiKeyMasked", maskLlmApiKey(rawKey));
|
||||||
|
m.put("active", active);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistLlmStore(JSONObject store) {
|
||||||
|
redisTemplate.opsForValue().set(LLM_PROFILES_STORE_KEY, store.toJSONString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取存储;若无 JSON 则尝试从旧版单键迁移并写入。
|
||||||
|
*/
|
||||||
|
private JSONObject loadOrMigrateLlmStore() {
|
||||||
|
String raw = redisTemplate.opsForValue().get(LLM_PROFILES_STORE_KEY);
|
||||||
|
if (StringUtils.isNotEmpty(raw)) {
|
||||||
|
try {
|
||||||
|
JSONObject o = JSON.parseObject(raw);
|
||||||
|
if (o != null) {
|
||||||
|
if (!o.containsKey("profiles") || o.get("profiles") == null) {
|
||||||
|
o.put("profiles", new JSONObject());
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析 LLM profiles_store 失败,将尝试迁移或重建", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return migrateLegacyLlmToStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject migrateLegacyLlmToStore() {
|
||||||
|
JSONObject store = new JSONObject();
|
||||||
|
JSONObject profiles = new JSONObject();
|
||||||
|
String modeRaw = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODE));
|
||||||
|
String baseUrl = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_BASE_URL));
|
||||||
|
String model = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODEL));
|
||||||
|
String apiKeyRaw = redisTemplate.opsForValue().get(LLM_KEY_API_KEY);
|
||||||
|
String apiKey = StringUtils.trim(apiKeyRaw);
|
||||||
|
|
||||||
|
boolean anyLegacy = StringUtils.isNotEmpty(modeRaw) || StringUtils.isNotEmpty(baseUrl)
|
||||||
|
|| StringUtils.isNotEmpty(model) || StringUtils.isNotEmpty(apiKey);
|
||||||
|
if (!anyLegacy) {
|
||||||
|
store.put("profiles", profiles);
|
||||||
|
store.put("activeId", null);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
String id = "migrated-" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
|
||||||
|
JSONObject p = new JSONObject();
|
||||||
|
p.put("name", "迁移自旧版单套配置");
|
||||||
|
p.put("mode", StringUtils.isEmpty(modeRaw) ? LLM_MODE_OLLAMA : modeRaw.toLowerCase());
|
||||||
|
p.put("baseUrl", baseUrl == null ? "" : baseUrl);
|
||||||
|
p.put("model", model == null ? "" : model);
|
||||||
|
p.put("apiKey", apiKey == null ? "" : apiKey);
|
||||||
|
profiles.put(id, p);
|
||||||
|
store.put("profiles", profiles);
|
||||||
|
store.put("activeId", id);
|
||||||
|
persistLlmStore(store);
|
||||||
|
redisTemplate.delete(LLM_KEY_MODE);
|
||||||
|
redisTemplate.delete(LLM_KEY_BASE_URL);
|
||||||
|
redisTemplate.delete(LLM_KEY_API_KEY);
|
||||||
|
redisTemplate.delete(LLM_KEY_MODEL);
|
||||||
|
log.info("已从旧版单键迁移大模型配置到多套存储");
|
||||||
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user