移除不必要的字段以简化API响应: • 删除component_path字段,避免重复路径信息 • 删除display_name字段,model_name已足够 • 删除file_size和last_modified字段,减少响应体积 • 保留核心搜索功能,优化用户体验 JSON响应更简洁,只包含必要的搜索结果信息。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
376 lines
13 KiB
C++
376 lines
13 KiB
C++
#include "pch.h"
|
|
#include "ModelSearchHandler.h"
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
|
|
// Main HTTP request handler
|
|
HttpResponse ModelSearchHandler::HandleModelSearchRequest(const HttpRequest& request) {
|
|
HttpResponse response;
|
|
|
|
// Validate request method
|
|
if (request.method != "POST") {
|
|
return FormatErrorResponse(405, "Method not allowed. Use POST.");
|
|
}
|
|
|
|
// Validate request
|
|
std::string error_message;
|
|
if (!ValidateHttpRequest(request, error_message)) {
|
|
return FormatErrorResponse(400, error_message);
|
|
}
|
|
|
|
try {
|
|
// Parse search request
|
|
ModelSearchRequest search_request = ParseSearchRequest(request.body);
|
|
|
|
// Execute search
|
|
ModelSearchResult search_result = ModelSearchEngine::Instance().SearchModels(search_request);
|
|
|
|
// Format response
|
|
if (search_result.success) {
|
|
response = FormatSuccessResponse(search_result);
|
|
} else {
|
|
response = FormatErrorResponse(500, search_result.error_message);
|
|
}
|
|
|
|
}
|
|
catch (const std::exception& e) {
|
|
response = FormatErrorResponse(500, "Request processing error: " + std::string(e.what()));
|
|
}
|
|
catch (...) {
|
|
response = FormatErrorResponse(500, "Unknown error during model search");
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// Request validation method
|
|
bool ModelSearchHandler::ValidateHttpRequest(const HttpRequest& request, std::string& error_message) {
|
|
if (request.body.empty()) {
|
|
error_message = "Request body cannot be empty";
|
|
return false;
|
|
}
|
|
|
|
// Check basic JSON format
|
|
if (request.body.find("{") == std::string::npos || request.body.find("}") == std::string::npos) {
|
|
error_message = "Invalid JSON format in request body";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse search request
|
|
ModelSearchRequest ModelSearchHandler::ParseSearchRequest(const std::string& json_body) {
|
|
ModelSearchRequest request;
|
|
|
|
// Parse required parameters
|
|
request.query = Trim(ExtractJsonValue(json_body, "query"));
|
|
if (request.query.empty()) {
|
|
request.query = Trim(ExtractJsonValue(json_body, "search_query"));
|
|
}
|
|
|
|
// Parse optional parameters
|
|
std::string match_mode = ExtractJsonValue(json_body, "match_mode");
|
|
if (!match_mode.empty()) {
|
|
request.match_mode = match_mode;
|
|
}
|
|
|
|
std::string search_scope = ExtractJsonValue(json_body, "search_scope");
|
|
if (!search_scope.empty()) {
|
|
request.search_scope = search_scope;
|
|
}
|
|
|
|
// Parse model type filter
|
|
std::vector<std::string> model_types = ExtractJsonArrayValue(json_body, "model_types");
|
|
if (!model_types.empty()) {
|
|
request.model_types = model_types;
|
|
}
|
|
|
|
// Parse numeric parameters
|
|
int max_results = ExtractJsonIntValue(json_body, "max_results", 0);
|
|
if (max_results > 0) {
|
|
request.max_results = max_results;
|
|
}
|
|
|
|
double similarity_threshold = ExtractJsonDoubleValue(json_body, "similarity_threshold", -1.0);
|
|
if (similarity_threshold >= 0.0 && similarity_threshold <= 1.0) {
|
|
request.similarity_threshold = similarity_threshold;
|
|
}
|
|
|
|
// Parse boolean parameters
|
|
request.include_components = ExtractJsonBoolValue(json_body, "include_components", request.include_components);
|
|
request.include_features = ExtractJsonBoolValue(json_body, "include_features", request.include_features);
|
|
|
|
return request;
|
|
}
|
|
|
|
// Format success response
|
|
HttpResponse ModelSearchHandler::FormatSuccessResponse(const ModelSearchResult& result) {
|
|
HttpResponse response;
|
|
response.status_code = 200;
|
|
|
|
std::ostringstream json;
|
|
json << "{"
|
|
<< "\"success\": true,"
|
|
<< "\"data\": {"
|
|
<< "\"results\": [";
|
|
|
|
// Build search results list
|
|
bool first_item = true;
|
|
for (const auto& item : result.results) {
|
|
if (!first_item) json << ",";
|
|
first_item = false;
|
|
|
|
json << "{"
|
|
<< "\"model_name\": \"" << EscapeJsonString(item.model_name) << "\","
|
|
<< "\"full_path\": \"" << EscapeJsonString(item.full_path) << "\","
|
|
<< "\"file_path\": \"" << EscapeJsonString(item.file_path) << "\","
|
|
<< "\"model_type\": \"" << EscapeJsonString(item.model_type) << "\","
|
|
<< "\"match_score\": " << item.match_score << ","
|
|
<< "\"match_reason\": \"" << EscapeJsonString(item.match_reason) << "\","
|
|
<< "\"match_type\": \"" << EscapeJsonString(item.match_type) << "\","
|
|
<< "\"is_in_session\": " << (item.is_in_session ? "true" : "false") << ","
|
|
<< "\"is_assembly\": " << (item.is_assembly ? "true" : "false") << ","
|
|
<< "\"component_count\": " << item.component_count << ","
|
|
<< "\"matched_keywords\": [";
|
|
|
|
// Build matched keywords list
|
|
bool first_keyword = true;
|
|
for (const auto& keyword : item.matched_keywords) {
|
|
if (!first_keyword) json << ",";
|
|
first_keyword = false;
|
|
json << "\"" << EscapeJsonString(keyword) << "\"";
|
|
}
|
|
|
|
json << "]"
|
|
<< "}";
|
|
}
|
|
|
|
json << "],"
|
|
<< "\"total_found\": " << result.total_found << ","
|
|
<< "\"returned_count\": " << result.returned_count << ","
|
|
<< "\"search_time_ms\": \"" << EscapeJsonString(result.search_time_ms) << "\","
|
|
<< "\"stats\": {"
|
|
<< "\"session_models_count\": " << result.stats.session_models_count << ","
|
|
<< "\"components_searched\": " << result.stats.components_searched << ","
|
|
<< "\"features_searched\": " << result.stats.features_searched << ","
|
|
<< "\"search_scope_used\": \"" << EscapeJsonString(result.stats.search_scope_used) << "\""
|
|
<< "}"
|
|
<< "},"
|
|
<< "\"error\": null"
|
|
<< "}";
|
|
|
|
response.body = json.str();
|
|
return response;
|
|
}
|
|
|
|
// Format error response
|
|
HttpResponse ModelSearchHandler::FormatErrorResponse(int status_code, const std::string& error_message) {
|
|
HttpResponse response;
|
|
response.status_code = status_code;
|
|
|
|
std::ostringstream json;
|
|
json << "{"
|
|
<< "\"success\": false,"
|
|
<< "\"data\": null,"
|
|
<< "\"error\": \"" << EscapeJsonString(error_message) << "\""
|
|
<< "}";
|
|
|
|
response.body = json.str();
|
|
return response;
|
|
}
|
|
|
|
// JSON parsing helper methods
|
|
std::string ModelSearchHandler::ExtractJsonValue(const std::string& json, const std::string& key) {
|
|
// Find key-value pair "key": "value" or "key":"value"
|
|
std::string key_pattern = "\"" + key + "\"";
|
|
size_t key_pos = json.find(key_pattern);
|
|
|
|
if (key_pos != std::string::npos) {
|
|
// Find colon
|
|
size_t colon_pos = json.find(":", key_pos);
|
|
if (colon_pos != std::string::npos) {
|
|
// Skip spaces to find value start
|
|
size_t value_start = colon_pos + 1;
|
|
while (value_start < json.length() &&
|
|
(json[value_start] == ' ' || json[value_start] == '\t' ||
|
|
json[value_start] == '\n' || json[value_start] == '\r')) {
|
|
value_start++;
|
|
}
|
|
|
|
// Check if it's a string value (starts with double quote)
|
|
if (value_start < json.length() && json[value_start] == '"') {
|
|
value_start++; // Skip opening quote
|
|
size_t value_end = json.find('"', value_start);
|
|
if (value_end != std::string::npos) {
|
|
return json.substr(value_start, value_end - value_start);
|
|
}
|
|
}
|
|
else {
|
|
// Non-string value, find until comma, brace or string end
|
|
size_t value_end = value_start;
|
|
while (value_end < json.length() &&
|
|
json[value_end] != ',' && json[value_end] != '}' &&
|
|
json[value_end] != ']' && json[value_end] != '\n') {
|
|
value_end++;
|
|
}
|
|
|
|
std::string value = Trim(json.substr(value_start, value_end - value_start));
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool ModelSearchHandler::ExtractJsonBoolValue(const std::string& json, const std::string& key, bool default_value) {
|
|
std::string value = ExtractJsonValue(json, key);
|
|
if (value.empty()) {
|
|
return default_value;
|
|
}
|
|
|
|
// Convert to lowercase for comparison
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
|
|
return (value == "true" || value == "1");
|
|
}
|
|
|
|
int ModelSearchHandler::ExtractJsonIntValue(const std::string& json, const std::string& key, int default_value) {
|
|
std::string value = ExtractJsonValue(json, key);
|
|
if (value.empty()) {
|
|
return default_value;
|
|
}
|
|
|
|
try {
|
|
return std::stoi(value);
|
|
}
|
|
catch (...) {
|
|
return default_value;
|
|
}
|
|
}
|
|
|
|
double ModelSearchHandler::ExtractJsonDoubleValue(const std::string& json, const std::string& key, double default_value) {
|
|
std::string value = ExtractJsonValue(json, key);
|
|
if (value.empty()) {
|
|
return default_value;
|
|
}
|
|
|
|
try {
|
|
return std::stod(value);
|
|
}
|
|
catch (...) {
|
|
return default_value;
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> ModelSearchHandler::ExtractJsonArrayValue(const std::string& json, const std::string& key) {
|
|
std::vector<std::string> result;
|
|
|
|
// Find key-value pair "key": [...]
|
|
std::string key_pattern = "\"" + key + "\"";
|
|
size_t key_pos = json.find(key_pattern);
|
|
|
|
if (key_pos != std::string::npos) {
|
|
// Find colon
|
|
size_t colon_pos = json.find(":", key_pos);
|
|
if (colon_pos != std::string::npos) {
|
|
// Skip spaces to find array start
|
|
size_t array_start = colon_pos + 1;
|
|
while (array_start < json.length() &&
|
|
(json[array_start] == ' ' || json[array_start] == '\t' ||
|
|
json[array_start] == '\n' || json[array_start] == '\r')) {
|
|
array_start++;
|
|
}
|
|
|
|
// Check if it's an array (starts with [)
|
|
if (array_start < json.length() && json[array_start] == '[') {
|
|
size_t array_end = json.find(']', array_start);
|
|
if (array_end != std::string::npos) {
|
|
std::string array_content = json.substr(array_start + 1, array_end - array_start - 1);
|
|
|
|
// Parse array elements
|
|
size_t pos = 0;
|
|
while (pos < array_content.length()) {
|
|
// Skip spaces
|
|
while (pos < array_content.length() &&
|
|
(array_content[pos] == ' ' || array_content[pos] == '\t' ||
|
|
array_content[pos] == '\n' || array_content[pos] == '\r')) {
|
|
pos++;
|
|
}
|
|
|
|
if (pos >= array_content.length()) break;
|
|
|
|
// Find string value
|
|
if (array_content[pos] == '"') {
|
|
pos++; // Skip opening quote
|
|
size_t end_quote = array_content.find('"', pos);
|
|
if (end_quote != std::string::npos) {
|
|
result.push_back(array_content.substr(pos, end_quote - pos));
|
|
pos = end_quote + 1;
|
|
}
|
|
}
|
|
|
|
// Find next comma
|
|
size_t next_comma = array_content.find(',', pos);
|
|
if (next_comma != std::string::npos) {
|
|
pos = next_comma + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// JSON escape method
|
|
std::string ModelSearchHandler::EscapeJsonString(const std::string& input) {
|
|
std::string escaped;
|
|
escaped.reserve(input.length() * 2);
|
|
|
|
for (char c : input) {
|
|
switch (c) {
|
|
case '"': escaped += "\\\""; break;
|
|
case '\\': escaped += "\\\\"; break;
|
|
case '\b': escaped += "\\b"; break;
|
|
case '\f': escaped += "\\f"; break;
|
|
case '\n': escaped += "\\n"; break;
|
|
case '\r': escaped += "\\r"; break;
|
|
case '\t': escaped += "\\t"; break;
|
|
default:
|
|
if (c < 0x20) {
|
|
// Control character escape to Unicode
|
|
char buffer[7];
|
|
snprintf(buffer, sizeof(buffer), "\\u%04x", static_cast<unsigned char>(c));
|
|
escaped += buffer;
|
|
} else {
|
|
escaped += c;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return escaped;
|
|
}
|
|
|
|
// Helper utility methods
|
|
std::string ModelSearchHandler::Trim(const std::string& str) {
|
|
size_t start = str.find_first_not_of(" \t\n\r");
|
|
if (start == std::string::npos) return "";
|
|
|
|
size_t end = str.find_last_not_of(" \t\n\r");
|
|
return str.substr(start, end - start + 1);
|
|
}
|
|
|
|
bool ModelSearchHandler::StartsWith(const std::string& str, const std::string& prefix) {
|
|
return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
|
|
}
|
|
|
|
bool ModelSearchHandler::EndsWith(const std::string& str, const std::string& suffix) {
|
|
return str.size() >= suffix.size() &&
|
|
str.substr(str.size() - suffix.size()) == suffix;
|
|
} |