1 line
8.3 KiB
JSON
1 line
8.3 KiB
JSON
{"repo": "3xddd/serverapp", "files": {"AiEval.cpp": {"ca642035dd857b69aaf1a5b7f22f91b29bfa72b2": {"t": 1785746414.3467119, "n": 7069, "h": "d568819cd7297c32", "c": "#include \"pch.h\"\n#include \"AiEval.h\"\n\n#include <cpr/cpr.h>\n#include <nlohmann/json.hpp>\n#include <windows.h>\n\n#include <string>\n\nusing nlohmann::json;\n\nstatic const char* kDeepSeekApiKey = \"sk-d5d2a144f7d244c88f2f329d0ba0c0a6\";\nstatic const char* kDeepSeekModel = \"deepseek-chat\";\n\n// 只限制总字数到 100\nstatic const int kMaxTotalChars = 100;\n\nstatic std::string NormalizeNewlineWin(const std::string& in)\n{\n std::string out;\n out.reserve(in.size() + 16);\n\n for (size_t i = 0; i < in.size(); ++i)\n {\n char ch = in[i];\n if (ch == '\\r')\n {\n if (i + 1 < in.size() && in[i + 1] == '\\n') ++i;\n out += \"\\r\\n\";\n }\n else if (ch == '\\n')\n {\n out += \"\\r\\n\";\n }\n else\n {\n out.push_back(ch);\n }\n }\n return out;\n}\n\n// 只去掉首尾空白,不动中间换行\nstatic void TrimAsciiWhitespace(std::string& s)\n{\n while (!s.empty() && (s.back() == '\\r' || s.back() == '\\n' || s.back() == ' ' || s.back() == '\\t'))\n s.pop_back();\n\n size_t st = 0;\n while (st < s.size() && (s[st] == ' ' || s[st] == '\\t' || s[st] == '\\r' || s[st] == '\\n'))\n ++st;\n\n if (st > 0) s.erase(0, st);\n}\n\nbool AiEval::Run(const CString& evalText, CString& outAiText, CString& outErr)\n{\n try\n {\n outAiText.Empty();\n outErr.Empty();\n\n if (!kDeepSeekApiKey || kDeepSeekApiKey[0] == '\\0' ||\n std::string(kDeepSeekApiKey).find(\"xxxx\") != std::string::npos)\n {\n outErr = _T(\"AiEval.cpp 里没有填写 DeepSeek API Key\");\n return false;\n }\n\n // 评估文本:UTF-16 -> UTF-8\n std::string evalUtf8 = WideToUtf8(std::wstring((LPCWSTR)evalText));\n\n // ★关键:prompt 用宽字符写中文,再转 UTF-8,避免源码编码导致“服务端乱码”\n static const wchar_t* kSystemPromptW =\n LR\"(你是康复建议助手。\n请基于用户提供的佩戴/评估摘要,给出康复建议,表达自然一点即可。基于正常范围,如果有不正常现象请指出并建议改进措施。\n\n硬性要求:\n1) 总字数不超过100字(含标点)。\n2) 必须分行输出:2~4行,每行一句/一小段;行之间用换行分隔,不要空行。\n3) 不要Markdown,不要代码块,不要JSON,不要英文。\n4)能够进行疗效评估。每周压力有效率=(压力在目标区间时长/总佩戴时长)×100%其中合理目标区间为:顶椎30-50N,腰椎20-40N。\n5)正常顶锥压力:30-60N;动态波动±5N\n6)正常腰椎压力范围:20-45N\n7)正常弯曲角度:0-30°)\";\n\n std::string systemPromptUtf8 = WideToUtf8(std::wstring(kSystemPromptW));\n\n json req;\n req[\"model\"] = kDeepSeekModel;\n req[\"stream\"] = false;\n req[\"temperature\"] = 0.8; // 更自由一点(原来 0.2 太“刻板”)\n\n req[\"messages\"] = json::array({\n { {\"role\",\"system\"}, {\"content\", systemPromptUtf8} },\n { {\"role\",\"user\"},\n {\"content\", std::string(\"佩戴/评估摘要:\\n\") + evalUtf8 + \"\\n请按要求输出建议。\"} }\n });\n\n std::string body = req.dump(-1, ' ', false, json::error_handler_t::replace);\n\n cpr::Response r = cpr::Post(\n cpr::Url{ \"https://api.deepseek.com/chat/completions\" },\n cpr::Header{\n {\"Content-Type\",\"application/json; charset=utf-8\"},\n {\"Authorization\", std::string(\"Bearer \") + kDeepSeekApiKey},\n {\"Accept-Encoding\",\"identity\"}\n },\n cpr::Body{ body },\n cpr::ConnectTimeout{ 10000 },\n cpr::Timeout{ 60000 }\n );\n\n if (r.error.code != cpr::ErrorCode::OK)\n {\n CString e;\n e.Format(_T(\"HTTP 请求失败:网络错误\\r\\ncpr错误码:%d\\r\\n错误信息:%S\"),\n (int)r.error.code,\n r.error.message.c_str());\n outErr = e;\n return false;\n }\n\n if (r.status_code < 200 || r.status_code >= 300)\n {\n CString e;\n e.Format(_T(\"HTTP 状态码异常:%d\"), (int)r.status_code);\n outErr = e;\n\n std::string snippet = r.text;\n if (snippet.size() > 800) snippet = snippet.substr(0, 800);\n snippet = NormalizeNewlineWin(snippet);\n\n std::wstring snW = Utf8ToWide(snippet);\n outErr += _T(\"\\r\\n返回体(前800字节):\\r\\n\");\n outErr += CString(snW.c_str());\n return false;\n }\n\n json resp = json::parse(r.text, nullptr, false);\n if (resp.is_discarded() || !resp.is_object())\n {\n outErr = _T(\"返回不是合法JSON\");\n return false;\n }\n\n auto itE = resp.find(\"error\");\n if (itE != resp.end())\n {\n std::string s = itE->dump(-1, ' ', false, json::error_handler_t::replace);\n std::wstring w = Utf8ToWide(NormalizeNewlineWin(s));\n outErr = _T(\"接口返回 error:\\r\\n\");\n outErr += CString(w.c_str());\n return false;\n }\n\n auto itChoices = resp.find(\"choices\");\n if (itChoices == resp.end() || !itChoices->is_array() || itChoices->empty())\n {\n outErr = _T(\"返回缺少 choices 数组\");\n return false;\n }\n\n const json& c0 = (*itChoices)[0];\n auto itMsg = c0.find(\"message\");\n if (itMsg == c0.end() || !itMsg->is_object())\n {\n outErr = _T(\"返回缺少 message 对象\");\n return false;\n }\n\n auto itContent = itMsg->find(\"content\");\n if (itContent == itMsg->end())\n {\n outErr = _T(\"返回缺少 content\");\n return false;\n }\n\n const std::string* pContent = itContent->get_ptr<const std::string*>();\n if (!pContent || pContent->empty())\n {\n outErr = _T(\"AI 返回内容为空或类型不正确\");\n return false;\n }\n\n // content 是 UTF-8(含中文),并保证换行是 \\r\\n\n std::string content = NormalizeNewlineWin(*pContent);\n TrimAsciiWhitespace(content);\n\n // UTF-8 -> UTF-16,避免中文乱码\n std::wstring w = Utf8ToWide(content);\n CString out(w.c_str());\n\n // ★只做总字数限制:<=100(按宽字符计)\n if (out.GetLength() > kMaxTotalChars)\n {\n out = out.Left(kMaxTotalChars);\n // 截断后补一个换行,避免 UI 挤成一行\n if (out.GetLength() >= 2)\n {\n int n = out.GetLength();\n if (!(out.GetAt(n - 2) == '\\r' && out.GetAt(n - 1) == '\\n'))\n out += _T(\"\\r\\n\");\n }\n else\n {\n out += _T(\"\\r\\n\");\n }\n }\n\n outAiText = out;\n return true;\n }\n catch (const std::exception& e)\n {\n CString t;\n t.Format(_T(\"AiEval::Run 捕获异常:%S\"), e.what());\n outErr = t;\n return false;\n }\n catch (...)\n {\n outErr = _T(\"AiEval::Run 捕获未知异常\");\n return false;\n }\n}\n\nstd::string AiEval::WideToUtf8(const std::wstring& w)\n{\n if (w.empty()) return \"\";\n int len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr);\n if (len <= 0) return \"\";\n std::string out(len, '\\0');\n WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &out[0], len, nullptr, nullptr);\n out.pop_back();\n return out;\n}\n\nstd::wstring AiEval::Utf8ToWide(const std::string& u8)\n{\n if (u8.empty()) return L\"\";\n int wlen = MultiByteToWideChar(CP_UTF8, 0, u8.data(), (int)u8.size(), nullptr, 0);\n if (wlen <= 0) return L\"\";\n std::wstring w(wlen, L'\\0');\n MultiByteToWideChar(CP_UTF8, 0, u8.data(), (int)u8.size(), &w[0], wlen);\n return w;\n}\n"}}}} |