feat(oss): 完成附件上传功能并测试 doxc 文件,GLM 成功识别
This commit is contained in:
parent
cb80d5cee7
commit
87efcdd296
|
|
@ -46,27 +46,18 @@ def get_client():
|
||||||
|
|
||||||
|
|
||||||
# ── 模型映射 ──────────────────────────────────────────────────────────
|
# ── 模型映射 ──────────────────────────────────────────────────────────
|
||||||
DEFAULT_TEXT_MODEL = "glm-4.5-Air" # glm-4.6 文本统一模型
|
DEFAULT_TEXT_MODEL = "glm-4-flash" # 默认文本模型
|
||||||
DEFAULT_VISION_MODEL = "glm-4.6v"
|
DEFAULT_VISION_MODEL = "glm-4.6v" # 图片/附件识别用 glm-4.6v
|
||||||
|
|
||||||
MODEL_MAP = {
|
|
||||||
"qwen-max": "glm-4.5-Air",
|
|
||||||
"qwen-plus": "glm-4.5-Air",
|
|
||||||
"qwen-turbo": "glm-4.5-Air",
|
|
||||||
"qwen-vl-max": "glm-4.5-Air",
|
|
||||||
"qwen-vl-plus": "glm-4.5-Air",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_model(model: str, has_vision: bool = False) -> str:
|
def resolve_model(model: str, has_vision: bool = False) -> str:
|
||||||
if model.startswith("glm"):
|
# 当消息包含图片或附件时,使用视觉模型
|
||||||
return model
|
if has_vision:
|
||||||
mapped = MODEL_MAP.get(model, DEFAULT_TEXT_MODEL)
|
print(f"[GLM] 检测到图片/附件,使用视觉模型:{model} → {DEFAULT_VISION_MODEL}")
|
||||||
# 当消息包含图片时,强制使用视觉模型
|
|
||||||
if has_vision and mapped != DEFAULT_VISION_MODEL:
|
|
||||||
print(f"[GLM] 检测到图片,模型从 {mapped} 切换为 {DEFAULT_VISION_MODEL}")
|
|
||||||
return DEFAULT_VISION_MODEL
|
return DEFAULT_VISION_MODEL
|
||||||
return mapped
|
# 普通文本对话,保持原模型不变
|
||||||
|
print(f"[GLM] 使用模型:{model}")
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
# ── 文件上传(含 file_id 缓存)───────────────────────────────────────
|
# ── 文件上传(含 file_id 缓存)───────────────────────────────────────
|
||||||
|
|
@ -156,6 +147,10 @@ def build_glm_messages(messages: list, files: list | None = None) -> tuple[list,
|
||||||
img_val.get("url", "") if isinstance(img_val, dict) else img_val
|
img_val.get("url", "") if isinstance(img_val, dict) else img_val
|
||||||
)
|
)
|
||||||
new_content.append(encode_image(img_src))
|
new_content.append(encode_image(img_src))
|
||||||
|
elif t == "file_url":
|
||||||
|
# file_url 类型(PDF/DOCX/TXT 等文档链接)原样透传
|
||||||
|
has_vision = True
|
||||||
|
new_content.append(item)
|
||||||
else:
|
else:
|
||||||
new_content.append({"type": "text", "text": str(item)})
|
new_content.append({"type": "text", "text": str(item)})
|
||||||
glm_messages.append({"role": role, "content": new_content})
|
glm_messages.append({"role": role, "content": new_content})
|
||||||
|
|
@ -164,7 +159,20 @@ def build_glm_messages(messages: list, files: list | None = None) -> tuple[list,
|
||||||
|
|
||||||
# 处理独立附件列表
|
# 处理独立附件列表
|
||||||
if files:
|
if files:
|
||||||
doc_exts = {".pdf", ".doc", ".docx", ".xlsx", ".xls", ".pptx", ".ppt"}
|
doc_exts = {
|
||||||
|
".pdf",
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
|
".xlsx",
|
||||||
|
".xls",
|
||||||
|
".pptx",
|
||||||
|
".ppt",
|
||||||
|
".txt",
|
||||||
|
".md",
|
||||||
|
".csv",
|
||||||
|
".json",
|
||||||
|
".log",
|
||||||
|
}
|
||||||
img_exts = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
img_exts = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||||
inserts = []
|
inserts = []
|
||||||
|
|
||||||
|
|
@ -172,6 +180,20 @@ def build_glm_messages(messages: list, files: list | None = None) -> tuple[list,
|
||||||
parsed = urlparse(file_url)
|
parsed = urlparse(file_url)
|
||||||
filename = parsed.path.split("/")[-1]
|
filename = parsed.path.split("/")[-1]
|
||||||
suffix = Path(filename).suffix.lower()
|
suffix = Path(filename).suffix.lower()
|
||||||
|
|
||||||
|
# ── 远程 URL(OSS 等)→ 直接透传 ─────────────────
|
||||||
|
if file_url.startswith(("http://", "https://")):
|
||||||
|
has_vision = True
|
||||||
|
if suffix in img_exts:
|
||||||
|
inserts.append(
|
||||||
|
{"type": "image_url", "image_url": {"url": file_url}}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 文档/文本类统一走 file_url
|
||||||
|
inserts.append({"type": "file_url", "file_url": {"url": file_url}})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ── 本地文件回退逻辑 ──────────────────────────────
|
||||||
rel = parsed.path.lstrip("/")
|
rel = parsed.path.lstrip("/")
|
||||||
local = Path(rel)
|
local = Path(rel)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
"""
|
||||||
|
测试脚本:上传 PDF / DOCX / TXT 文件到阿里云 OSS → 获取 URL → 发送给 GLM-4.6V 识别
|
||||||
|
|
||||||
|
支持的文件类型:
|
||||||
|
- .pdf → 上传 OSS 后以 file_url 类型发送 URL 给 GLM
|
||||||
|
- .docx → 上传 OSS 后以 file_url 类型发送 URL 给 GLM
|
||||||
|
- .txt → 上传 OSS 后以 file_url 类型发送 URL 给 GLM
|
||||||
|
|
||||||
|
用法:
|
||||||
|
cd server
|
||||||
|
source ~/.bashrc && source .venv/bin/activate
|
||||||
|
python -m utils.test_oss_doc_glm --file <本地文件路径> [--prompt "请总结这份文件"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 确保 server 目录在 sys.path
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
|
||||||
|
from utils.oss_uploader import upload_file
|
||||||
|
from utils.glm_adapter import glm_chat_sync
|
||||||
|
|
||||||
|
|
||||||
|
# 文件类型分组
|
||||||
|
DOC_EXTS = {".pdf", ".doc", ".docx", ".xlsx", ".xls", ".pptx", ".ppt"}
|
||||||
|
TXT_EXTS = {".txt", ".md", ".csv", ".json", ".log"}
|
||||||
|
IMG_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||||
|
# 所有可通过 file_url 发送的类型
|
||||||
|
FILE_URL_EXTS = DOC_EXTS | TXT_EXTS
|
||||||
|
|
||||||
|
|
||||||
|
def detect_file_type(suffix: str) -> str:
|
||||||
|
"""根据后缀判断文件类别: 'file_url' / 'image' / 'unknown'"""
|
||||||
|
suffix = suffix.lower()
|
||||||
|
if suffix in FILE_URL_EXTS:
|
||||||
|
return "file_url"
|
||||||
|
elif suffix in IMG_EXTS:
|
||||||
|
return "image"
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def build_messages_for_file_url(file_url: str, prompt: str) -> list:
|
||||||
|
"""
|
||||||
|
为文档/文本文件构建消息。
|
||||||
|
使用 file_url 类型,直接传递 OSS 的 URL 给 GLM。
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "file_url",
|
||||||
|
"file_url": {"url": file_url},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_messages_for_image(file_url: str, prompt: str) -> list:
|
||||||
|
"""为图片文件构建消息,使用 image_url 类型。"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "image_url", "image_url": {"url": file_url}},
|
||||||
|
{"type": "text", "text": prompt},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="上传 PDF/DOCX/TXT 文件到 OSS 并让 GLM-4.6V 识别"
|
||||||
|
)
|
||||||
|
parser.add_argument("--file", required=True, help="要上传的本地文件路径")
|
||||||
|
parser.add_argument(
|
||||||
|
"--prompt", default="请总结这份文件的主要内容", help="发给 GLM 的提示词"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model", default="glm-4.6v", help="GLM 模型名称(默认: glm-4.6v)"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
file_path = Path(args.file).resolve()
|
||||||
|
if not file_path.exists():
|
||||||
|
print(f"❌ 文件不存在: {file_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
suffix = file_path.suffix.lower()
|
||||||
|
file_type = detect_file_type(suffix)
|
||||||
|
print(f"📂 文件信息:")
|
||||||
|
print(f" 路径: {file_path}")
|
||||||
|
print(f" 后缀: {suffix}")
|
||||||
|
print(f" 类型: {file_type}")
|
||||||
|
print(f" 大小: {file_path.stat().st_size / 1024:.1f} KB")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ── 第一步:上传文件到 OSS ────────────────────────────────
|
||||||
|
print(f"📤 正在上传文件到阿里云 OSS...")
|
||||||
|
oss_result = upload_file(str(file_path))
|
||||||
|
file_url = oss_result["url"]
|
||||||
|
print(f"✅ OSS 上传成功!")
|
||||||
|
print(f" URL: {file_url}")
|
||||||
|
print(f" ETag: {oss_result['etag']}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ── 第二步:根据文件类型构建消息 ──────────────────────────
|
||||||
|
print(f"🔧 正在构建 GLM 消息...")
|
||||||
|
|
||||||
|
if file_type == "file_url":
|
||||||
|
# PDF / DOCX / TXT 等:使用 file_url 类型发送 OSS URL
|
||||||
|
print(f" 策略: 使用 file_url 发送 OSS 链接")
|
||||||
|
messages = build_messages_for_file_url(file_url, args.prompt)
|
||||||
|
elif file_type == "image":
|
||||||
|
# 图片:使用 image_url
|
||||||
|
print(f" 策略: 使用 image_url 发送 OSS 链接")
|
||||||
|
messages = build_messages_for_image(file_url, args.prompt)
|
||||||
|
else:
|
||||||
|
print(f"❌ 不支持的文件类型: {suffix}")
|
||||||
|
print(f" 支持: {', '.join(sorted(FILE_URL_EXTS | IMG_EXTS))}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ── 第三步:发送给 GLM 识别 ──────────────────────────────
|
||||||
|
print(f"🤖 正在请求 GLM ({args.model}) 识别文件...")
|
||||||
|
print(f" 提示词: {args.prompt}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = glm_chat_sync(
|
||||||
|
messages=messages,
|
||||||
|
model=args.model,
|
||||||
|
temperature=0.7,
|
||||||
|
max_tokens=4096,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("─" * 60)
|
||||||
|
print("📝 GLM 回复:")
|
||||||
|
print("─" * 60)
|
||||||
|
print(result["content"])
|
||||||
|
print("─" * 60)
|
||||||
|
|
||||||
|
if result.get("usage"):
|
||||||
|
usage = result["usage"]
|
||||||
|
print(
|
||||||
|
f"\n📊 Token 用量: 输入 {usage['promptTokens']} | "
|
||||||
|
f"输出 {usage['completionTokens']} | "
|
||||||
|
f"总计 {usage['totalTokens']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"\n✅ 测试完成! 使用模型: {result.get('model', args.model)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ GLM 请求失败: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -143,7 +143,6 @@ const props = withDefaults(
|
||||||
showSidebarToggle?: boolean;
|
showSidebarToggle?: boolean;
|
||||||
isWideMode?: boolean;
|
isWideMode?: boolean;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
currentModelId?: string;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
title: "新对话",
|
title: "新对话",
|
||||||
|
|
@ -151,7 +150,6 @@ const props = withDefaults(
|
||||||
showSidebarToggle: true,
|
showSidebarToggle: true,
|
||||||
isWideMode: true,
|
isWideMode: true,
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
currentModelId: "gpt-4",
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -164,7 +162,6 @@ const emit = defineEmits<{
|
||||||
pin: [];
|
pin: [];
|
||||||
archive: [];
|
archive: [];
|
||||||
settings: [];
|
settings: [];
|
||||||
"select-model": [modelId: string];
|
|
||||||
"conversation-settings": [];
|
"conversation-settings": [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
@ -173,25 +170,29 @@ const showMoreMenu = ref(false);
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const currentModel = ref(localStorage.getItem("modelSelect") || "");
|
const currentModel = ref(localStorage.getItem("modelSelect") || "");
|
||||||
|
const currentModelId = ref(settingsStore.getSelectedModelId());
|
||||||
const models: any = ref([]);
|
const models: any = ref([]);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
chatApi.getModels().then((res: any) => {
|
chatApi.getModels().then((res: any) => {
|
||||||
models.value = res;
|
models.value = res;
|
||||||
if (!localStorage.getItem("modelSelect")) {
|
// 初始化模型显示名称
|
||||||
currentModel.value = models.value[0]["name"] || "";
|
const model = models.value?.find((m: any) => m.id === currentModelId.value);
|
||||||
localStorage.setItem("modelSelect", currentModel.value);
|
if (model) {
|
||||||
|
currentModel.value = model.name;
|
||||||
|
} else if (models.value.length > 0) {
|
||||||
|
currentModel.value = models.value[0].name;
|
||||||
|
currentModelId.value = models.value[0].id;
|
||||||
}
|
}
|
||||||
|
localStorage.setItem("modelSelect", currentModel.value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function selectModel(modelId: string, modelName: string) {
|
function selectModel(modelId: string, modelName: string) {
|
||||||
|
currentModel.value = modelName;
|
||||||
|
currentModelId.value = modelId;
|
||||||
localStorage.setItem("modelSelect", modelName);
|
localStorage.setItem("modelSelect", modelName);
|
||||||
const model = models.value?.find((m: any) => m.id === modelId);
|
settingsStore.setSelectedModelId(modelId); // 更新选中的模型 ID
|
||||||
if (model) {
|
|
||||||
currentModel.value = model.name;
|
|
||||||
emit("select-model", modelId);
|
|
||||||
}
|
|
||||||
showModelMenu.value = false;
|
showModelMenu.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,13 @@
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="action-btn send"
|
class="action-btn send"
|
||||||
:class="{ active: canSend }"
|
:class="{ active: canSend, loading: isUploading }"
|
||||||
:disabled="!canSend"
|
:disabled="!canSend"
|
||||||
title="发送消息 (Ctrl+Enter)"
|
:title="isUploading ? '上传中...' : '发送消息 (Ctrl+Enter)'"
|
||||||
@click="handleSend"
|
@click="handleSend"
|
||||||
>
|
>
|
||||||
<Send :size="20" />
|
<Loader2 v-if="isUploading" :size="20" class="animate-spin" />
|
||||||
|
<Send v-else :size="20" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -151,6 +152,7 @@ import {
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Minimize2,
|
Minimize2,
|
||||||
Brain,
|
Brain,
|
||||||
|
Loader2,
|
||||||
} from "@/components/icons";
|
} from "@/components/icons";
|
||||||
import AttachmentPreview from "./AttachmentPreview.vue";
|
import AttachmentPreview from "./AttachmentPreview.vue";
|
||||||
import { generateId } from "@/utils/helpers";
|
import { generateId } from "@/utils/helpers";
|
||||||
|
|
@ -209,11 +211,15 @@ const imageInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const charCount = computed(() => inputText.value.length);
|
const charCount = computed(() => inputText.value.length);
|
||||||
|
const isUploading = computed(() =>
|
||||||
|
attachments.value.some((a) => a.uploading),
|
||||||
|
);
|
||||||
const canSend = computed(() => {
|
const canSend = computed(() => {
|
||||||
return (
|
return (
|
||||||
(inputText.value.trim().length > 0 || attachments.value.length > 0) &&
|
(inputText.value.trim().length > 0 || attachments.value.length > 0) &&
|
||||||
!props.disabled &&
|
!props.disabled &&
|
||||||
charCount.value <= props.maxChars
|
charCount.value <= props.maxChars &&
|
||||||
|
!isUploading.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -355,6 +361,9 @@ async function uploadFileToServer(id: string, file: File) {
|
||||||
attachment.uploading = false;
|
attachment.uploading = false;
|
||||||
attachment.progress = 100;
|
attachment.progress = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示上传成功提示
|
||||||
|
window.$toast && window.$toast('文件上传成功', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('文件上传失败:', error);
|
console.error('文件上传失败:', error);
|
||||||
|
|
||||||
|
|
@ -521,6 +530,12 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
|
||||||
|
|
@ -212,14 +212,20 @@ const { showConversationSettingsModal: visible, settings } =
|
||||||
storeToRefs(settingsStore);
|
storeToRefs(settingsStore);
|
||||||
const availableModels: any = ref([]);
|
const availableModels: any = ref([]);
|
||||||
const modelSelect = ref(localStorage.getItem("modelSelect") || "");
|
const modelSelect = ref(localStorage.getItem("modelSelect") || "");
|
||||||
|
const currentModelId = ref(settingsStore.getSelectedModelId());
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
chatApi.getModels().then((res: any) => {
|
chatApi.getModels().then((res: any) => {
|
||||||
availableModels.value = res;
|
availableModels.value = res;
|
||||||
if (!localStorage.getItem("modelSelect")) {
|
// 初始化模型显示名称
|
||||||
modelSelect.value = availableModels.value[0]["name"] || "";
|
const model = availableModels.value?.find((m: any) => m.id === currentModelId.value);
|
||||||
localStorage.setItem("modelSelect", modelSelect.value);
|
if (model) {
|
||||||
|
modelSelect.value = model.name;
|
||||||
|
} else if (availableModels.value.length > 0) {
|
||||||
|
modelSelect.value = availableModels.value[0].name;
|
||||||
|
currentModelId.value = availableModels.value[0].id;
|
||||||
}
|
}
|
||||||
|
localStorage.setItem("modelSelect", modelSelect.value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -273,8 +279,15 @@ const presetPrompts = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function updateSelect(data: any) {
|
function updateSelect(modelName: any) {
|
||||||
localStorage.setItem("modelSelect", data);
|
modelSelect.value = modelName;
|
||||||
|
localStorage.setItem("modelSelect", modelName);
|
||||||
|
// 根据模型名称找到对应的 ID 并更新
|
||||||
|
const model = availableModels.value?.find((m: any) => m.name === modelName);
|
||||||
|
if (model) {
|
||||||
|
currentModelId.value = model.id;
|
||||||
|
settingsStore.setSelectedModelId(model.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class ChatApi {
|
||||||
|
|
||||||
// 将前端简化的请求翻译为 OpenAI 兼容的规范请求体
|
// 将前端简化的请求翻译为 OpenAI 兼容的规范请求体
|
||||||
const openAiRequest = {
|
const openAiRequest = {
|
||||||
model: request.model || "qwen-plus", // 可能需要指定支持视觉的模型
|
model: request.model || "glm-4-flash", // 可能需要指定支持视觉的模型
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
|
|
@ -248,19 +248,20 @@ class ChatApi {
|
||||||
async getModels(): Promise<ModelInfo[]> {
|
async getModels(): Promise<ModelInfo[]> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "qwen-max",
|
id: "glm-4.6",
|
||||||
name: "通义千问 Max",
|
name: "智普 GLM-4.6",
|
||||||
description: "最强大的模型",
|
description: "最强大的模型",
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
provider: "Aliyun",
|
provider: "Zhipu",
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "qwen-plus",
|
|
||||||
name: "通义千问 Plus",
|
|
||||||
description: "能力均衡",
|
|
||||||
maxTokens: 8192,
|
|
||||||
provider: "Aliyun",
|
|
||||||
},
|
},
|
||||||
|
// GLM-4.5,联网搜索功能有问题
|
||||||
|
// {
|
||||||
|
// id: "glm-4.5",
|
||||||
|
// name: "智普 GLM-4.5",
|
||||||
|
// description: "能力均衡",
|
||||||
|
// maxTokens: 8192,
|
||||||
|
// provider: "Zhipu",
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||||
compactMode: false,
|
compactMode: false,
|
||||||
|
|
||||||
// AI 默认设置
|
// AI 默认设置
|
||||||
defaultModel: 'qwen-plus',
|
defaultModel: 'glm-4.6',
|
||||||
defaultTemperature: 0.7,
|
defaultTemperature: 0.7,
|
||||||
defaultMaxTokens: 4096,
|
defaultMaxTokens: 4096,
|
||||||
defaultSystemPrompt: '你是一个有帮助的 AI 助手。',
|
defaultSystemPrompt: '你是一个有帮助的 AI 助手。',
|
||||||
|
|
@ -34,32 +34,32 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||||
// 可用的 AI 模型
|
// 可用的 AI 模型
|
||||||
const availableModels: AIModel[] = [
|
const availableModels: AIModel[] = [
|
||||||
{
|
{
|
||||||
id: 'qwen-max',
|
id: "glm-4.6",
|
||||||
name: '通义千问 Max',
|
name: "智普 GLM-4.6",
|
||||||
description: '最强大的模型,适合复杂任务',
|
description: "最强大的模型",
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
provider: 'Aliyun',
|
provider: "Zhipu",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "glm-4.5",
|
||||||
|
name: "智普 GLM-4.5",
|
||||||
|
description: "能力均衡",
|
||||||
|
maxTokens: 8192,
|
||||||
|
provider: "Zhipu",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'qwen-plus',
|
id: 'glm-4-flash',
|
||||||
name: '通义千问 Plus',
|
name: '智普 GLM-4-Flash',
|
||||||
description: '能力均衡,更快的响应速度',
|
|
||||||
maxTokens: 8192,
|
|
||||||
provider: 'Aliyun',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'qwen-turbo',
|
|
||||||
name: '通义千问 Turbo',
|
|
||||||
description: '快速高效,适合日常对话',
|
description: '快速高效,适合日常对话',
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
provider: 'Aliyun',
|
provider: 'Zhipu',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'qwen-vl-max',
|
id: 'glm-4v-plus',
|
||||||
name: '通义千问 VL-Max',
|
name: '智普 GLM-4V-Plus',
|
||||||
description: '强大的视觉理解模型',
|
description: '强大的视觉理解模型',
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
provider: 'Aliyun',
|
provider: 'Zhipu',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -212,6 +212,24 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 存储选中模型 ID 的 localStorage key
|
||||||
|
const MODEL_ID_KEY = 'modelSelectId'
|
||||||
|
|
||||||
|
// 获取当前选择的模型 ID
|
||||||
|
function getSelectedModelId(): string {
|
||||||
|
|
||||||
|
return defaultSettings.defaultModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置当前选择的模型 ID
|
||||||
|
function setSelectedModelId(modelId: string) {
|
||||||
|
localStorage.setItem(MODEL_ID_KEY, modelId)
|
||||||
|
// 同时更新 settings 中的 defaultModel
|
||||||
|
settings.value.defaultModel = modelId
|
||||||
|
saveToStorage()
|
||||||
|
}
|
||||||
|
|
||||||
function loadFromStorage() {
|
function loadFromStorage() {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem('chat-settings')
|
const stored = localStorage.getItem('chat-settings')
|
||||||
|
|
@ -280,5 +298,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||||
exportSettings,
|
exportSettings,
|
||||||
importSettings,
|
importSettings,
|
||||||
loadFromStorage,
|
loadFromStorage,
|
||||||
|
getSelectedModelId,
|
||||||
|
setSelectedModelId,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Loading…
Reference in New Issue