feat: Web-Search Sources anzeigen (Streaming + History + Archive + Shared)
Backend: - web_search_tool_result Sources extrahieren (url, title) - Als 'web_search_result' Message-Typ in DB persistieren - Sources im web_search_end SSE-Event mitsenden - Alle 3 Chat-Pfade: StreamChat, RunAgentChat, ChatNonStreaming Frontend: - renderToolBoxContent: Sources-Sektion mit klickbaren Domain-Links - finishToolBox/addToolBoxStatic: sources-Parameter durchgereicht - History: web_search_result Message-Typ in allen 3 Render-Pfaden - web_search immer sichtbar (auch bei 'modifications' Filter) CSS: - .web-search-sources: Flex-Wrap Container - .web-search-source: Domain-Pill mit Hover-Effekt, Title-Ellipsis
This commit is contained in:
1 parent
116f21105c
commit
f64052caf4
5 files changed
+221
-7
No files matched your search
@@ -161,6 +161,31 @@ public class AnthropicService
|
||||
Content = el.GetProperty("input").ToString()
|
||||
});
|
||||
}
|
||||
else if (blockType == "web_search_tool_result")
|
||||
{
|
||||
// Sources aus dem Ergebnis-Block extrahieren und speichern
|
||||
var sources = new List<object>();
|
||||
if (el.TryGetProperty("content", out var searchContent) && searchContent.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in searchContent.EnumerateArray())
|
||||
{
|
||||
var itemType = item.TryGetProperty("type", out var it) ? it.GetString() : "";
|
||||
if (itemType == "web_search_result")
|
||||
{
|
||||
var url = item.TryGetProperty("url", out var u) ? u.GetString() ?? "" : "";
|
||||
var title = item.TryGetProperty("title", out var tt) ? tt.GetString() ?? "" : "";
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
sources.Add(new { url, title });
|
||||
}
|
||||
}
|
||||
}
|
||||
await _db.SaveMessage(new MessageRecord
|
||||
{
|
||||
ChainId = chainId, Iteration = iterations,
|
||||
Role = "assistant", Type = "web_search_result",
|
||||
Content = JsonSerializer.Serialize(sources)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Tool Results persistieren
|
||||
@@ -638,6 +663,44 @@ public class AnthropicService
|
||||
IsError = isError
|
||||
});
|
||||
}
|
||||
else if (blockType == "web_search_tool_result")
|
||||
{
|
||||
assistantContent.Add(JsonSerializer.Deserialize<JsonElement>(block.GetRawText()));
|
||||
|
||||
var sources = new List<object>();
|
||||
if (block.TryGetProperty("content", out var searchContent) && searchContent.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in searchContent.EnumerateArray())
|
||||
{
|
||||
var itemType = item.TryGetProperty("type", out var it) ? it.GetString() : "";
|
||||
if (itemType == "web_search_result")
|
||||
{
|
||||
var url = item.TryGetProperty("url", out var u2) ? u2.GetString() ?? "" : "";
|
||||
var title = item.TryGetProperty("title", out var tt2) ? tt2.GetString() ?? "" : "";
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
sources.Add(new { url, title });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _db.SaveMessage(new MessageRecord
|
||||
{
|
||||
ChainId = chainId,
|
||||
Iteration = baseIterations + iterations,
|
||||
Role = "assistant",
|
||||
Type = "web_search_result",
|
||||
Content = JsonSerializer.Serialize(sources)
|
||||
});
|
||||
}
|
||||
else if (blockType == "server_tool_use")
|
||||
{
|
||||
var toolId = block.GetProperty("id").GetString() ?? "";
|
||||
var toolName = block.GetProperty("name").GetString() ?? "";
|
||||
JsonElement inputEl;
|
||||
try { inputEl = block.GetProperty("input"); }
|
||||
catch { inputEl = JsonSerializer.Deserialize<JsonElement>("{}"); }
|
||||
assistantContent.Add(new { type = "server_tool_use", id = toolId, name = toolName, input = inputEl });
|
||||
}
|
||||
}
|
||||
|
||||
messages.Add(new Dictionary<string, object> { ["role"] = "assistant", ["content"] = assistantContent });
|
||||
@@ -808,6 +871,40 @@ public class AnthropicService
|
||||
IsError = isError
|
||||
});
|
||||
}
|
||||
else if (blockType == "web_search_tool_result")
|
||||
{
|
||||
assistantContent.Add(JsonSerializer.Deserialize<JsonElement>(block.GetRawText()));
|
||||
var sources = new List<object>();
|
||||
if (block.TryGetProperty("content", out var searchContent) && searchContent.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in searchContent.EnumerateArray())
|
||||
{
|
||||
var itemType = item.TryGetProperty("type", out var it) ? it.GetString() : "";
|
||||
if (itemType == "web_search_result")
|
||||
{
|
||||
var url = item.TryGetProperty("url", out var u2) ? u2.GetString() ?? "" : "";
|
||||
var title = item.TryGetProperty("title", out var tt2) ? tt2.GetString() ?? "" : "";
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
sources.Add(new { url, title });
|
||||
}
|
||||
}
|
||||
}
|
||||
await _db.SaveMessage(new MessageRecord
|
||||
{
|
||||
ChainId = ctx.ChainId, Iteration = iterations,
|
||||
Role = "assistant", Type = "web_search_result",
|
||||
Content = JsonSerializer.Serialize(sources)
|
||||
});
|
||||
}
|
||||
else if (blockType == "server_tool_use")
|
||||
{
|
||||
var toolId = block.GetProperty("id").GetString() ?? "";
|
||||
var toolName = block.GetProperty("name").GetString() ?? "";
|
||||
JsonElement inputEl;
|
||||
try { inputEl = block.GetProperty("input"); }
|
||||
catch { inputEl = JsonSerializer.Deserialize<JsonElement>("{}"); }
|
||||
assistantContent.Add(new { type = "server_tool_use", id = toolId, name = toolName, input = inputEl });
|
||||
}
|
||||
}
|
||||
|
||||
messages.Add(new Dictionary<string, object> { ["role"] = "assistant", ["content"] = assistantContent });
|
||||
@@ -890,6 +987,7 @@ public class AnthropicService
|
||||
|
||||
var blockTypes = new Dictionary<int, string>();
|
||||
var webSearchResultBlocks = new Dictionary<int, object>();
|
||||
var webSearchSources = new Dictionary<int, List<object>>();
|
||||
var executedTools = new List<ToolResult>();
|
||||
var streamErrors = new List<object>();
|
||||
|
||||
@@ -964,6 +1062,24 @@ public class AnthropicService
|
||||
// Gesamten Block als JSON speichern für die Conversation-History
|
||||
var rawBlock = JsonSerializer.Deserialize<JsonElement>(cb.GetRawText());
|
||||
webSearchResultBlocks[idx] = rawBlock;
|
||||
|
||||
// Sources extrahieren für Frontend
|
||||
var sources = new List<object>();
|
||||
if (cb.TryGetProperty("content", out var searchContent) && searchContent.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in searchContent.EnumerateArray())
|
||||
{
|
||||
var itemType = item.TryGetProperty("type", out var it) ? it.GetString() : "";
|
||||
if (itemType == "web_search_result")
|
||||
{
|
||||
var url = item.TryGetProperty("url", out var u) ? u.GetString() ?? "" : "";
|
||||
var title = item.TryGetProperty("title", out var tt) ? tt.GetString() ?? "" : "";
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
sources.Add(new { url, title });
|
||||
}
|
||||
}
|
||||
}
|
||||
webSearchSources[idx] = sources;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1055,7 +1171,8 @@ public class AnthropicService
|
||||
{
|
||||
// Web-Search-Ergebnis von Anthropic – als Raw-JSON in assistantContent aufnehmen
|
||||
assistantContent.Add(webSearchResultBlocks[idx]);
|
||||
await WriteSseEvent(response, "web_search_end", new { id = currentToolId }, cancellationToken);
|
||||
var sources = webSearchSources.TryGetValue(idx, out var s) ? s : new List<object>();
|
||||
await WriteSseEvent(response, "web_search_end", new { id = currentToolId, sources }, cancellationToken);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user