add shop result counter into response
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
parameters:
|
parameters:
|
||||||
retriex.commerce.enabled: true
|
retriex.commerce.enabled: true
|
||||||
retriex.commerce.max_shop_results: '%env(SHOPWARE_STORE_API_MAX_RESULT)%'
|
retriex.commerce.max_shop_results: '%env(SHOPWARE_STORE_API_MAX_RESULT)%'
|
||||||
retriex.commerce.shop_timeout: 3
|
retriex.commerce.shop_timeout: 15
|
||||||
retriex.commerce.store_api_base_url: '%env(SHOPWARE_STORE_API_BASE_URL)%'
|
retriex.commerce.store_api_base_url: '%env(SHOPWARE_STORE_API_BASE_URL)%'
|
||||||
retriex.commerce.sales_channel_access_key: '%env(SHOPWARE_SALES_CHANNEL_ACCESS_KEY)%'
|
retriex.commerce.sales_channel_access_key: '%env(SHOPWARE_SALES_CHANNEL_ACCESS_KEY)%'
|
||||||
|
|
||||||
|
|||||||
@@ -414,9 +414,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
removeThinkSpansOnly(container);
|
removeThinkSpansOnly(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deduplicateRetriexMetaCards(container) {
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cards = Array.from(container.querySelectorAll('.retriex-meta-card[data-retriex-meta-id]'));
|
||||||
|
const latestCardById = new Map();
|
||||||
|
|
||||||
|
cards.forEach((card) => {
|
||||||
|
latestCardById.set(card.getAttribute('data-retriex-meta-id'), card);
|
||||||
|
});
|
||||||
|
|
||||||
|
cards.forEach((card) => {
|
||||||
|
const cardId = card.getAttribute('data-retriex-meta-id');
|
||||||
|
|
||||||
|
if (latestCardById.get(cardId) !== card) {
|
||||||
|
card.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanupEmptyBlocks(container);
|
||||||
|
}
|
||||||
|
|
||||||
function renderBubbleContent(bubble, raw) {
|
function renderBubbleContent(bubble, raw) {
|
||||||
bubble.innerHTML = renderMarkdown(raw);
|
bubble.innerHTML = renderMarkdown(raw);
|
||||||
cleanupThinkSpans(bubble);
|
cleanupThinkSpans(bubble);
|
||||||
|
deduplicateRetriexMetaCards(bubble);
|
||||||
enhanceChatLinks(bubble);
|
enhanceChatLinks(bubble);
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
}
|
}
|
||||||
@@ -495,6 +519,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
clearScheduledRender();
|
clearScheduledRender();
|
||||||
bubble.innerHTML = renderMarkdown(raw);
|
bubble.innerHTML = renderMarkdown(raw);
|
||||||
removeThinkSpansOnly(bubble);
|
removeThinkSpansOnly(bubble);
|
||||||
|
deduplicateRetriexMetaCards(bubble);
|
||||||
bubble.classList.remove('loader');
|
bubble.classList.remove('loader');
|
||||||
enhanceChatLinks(bubble);
|
enhanceChatLinks(bubble);
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ final readonly class AgentRunner
|
|||||||
$sources = [];
|
$sources = [];
|
||||||
$optimizedShopQuery = '';
|
$optimizedShopQuery = '';
|
||||||
$shopSearchQuery = '';
|
$shopSearchQuery = '';
|
||||||
|
$shopSearchDisplayQuery = '';
|
||||||
|
$shopSearchUsedOptimizedQuery = false;
|
||||||
$commerceIntent = CommerceIntentLite::NONE;
|
$commerceIntent = CommerceIntentLite::NONE;
|
||||||
$knowledgeRetrievalPrompt = $prompt;
|
$knowledgeRetrievalPrompt = $prompt;
|
||||||
$usedFollowUpRetrievalContext = false;
|
$usedFollowUpRetrievalContext = false;
|
||||||
@@ -127,7 +129,6 @@ final readonly class AgentRunner
|
|||||||
commerceHistoryContext: $commerceHistoryContext,
|
commerceHistoryContext: $commerceHistoryContext,
|
||||||
userId: $userId
|
userId: $userId
|
||||||
);
|
);
|
||||||
$usedResolvedOptimizedShopQuery = $optimizedShopQuery !== '' && $shopSearchQuery === $optimizedShopQuery;
|
|
||||||
|
|
||||||
if ($shopSearchQuery === '') {
|
if ($shopSearchQuery === '') {
|
||||||
$this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [
|
$this->agentLogger->info('Commerce search skipped because no concrete shop query could be resolved', [
|
||||||
@@ -154,11 +155,16 @@ final readonly class AgentRunner
|
|||||||
$commerceHistoryContext
|
$commerceHistoryContext
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$shopSearchDisplayQuery = $shopQueryPreview->searchText !== ''
|
||||||
|
? $shopQueryPreview->searchText
|
||||||
|
: $shopSearchQuery;
|
||||||
|
$shopSearchUsedOptimizedQuery = $optimizedShopQuery !== '';
|
||||||
|
|
||||||
yield $this->systemMsg(
|
yield $this->systemMsg(
|
||||||
$this->buildShopSearchMetaMessage(
|
$this->buildShopSearchMetaMessage(
|
||||||
query: $shopQueryPreview->searchText !== '' ? $shopQueryPreview->searchText : $shopSearchQuery,
|
query: $shopSearchDisplayQuery,
|
||||||
commerceIntent: $commerceIntent,
|
commerceIntent: $commerceIntent,
|
||||||
usedOptimizedQuery: $usedResolvedOptimizedShopQuery,
|
usedOptimizedQuery: $shopSearchUsedOptimizedQuery,
|
||||||
originalQuery: $shopSearchQuery
|
originalQuery: $shopSearchQuery
|
||||||
),
|
),
|
||||||
'meta'
|
'meta'
|
||||||
@@ -167,7 +173,7 @@ final readonly class AgentRunner
|
|||||||
$this->agentLogger->info('Commerce search prepared', [
|
$this->agentLogger->info('Commerce search prepared', [
|
||||||
'userId' => $userId,
|
'userId' => $userId,
|
||||||
'commerceIntent' => $commerceIntent,
|
'commerceIntent' => $commerceIntent,
|
||||||
'usedOptimizedShopQuery' => $usedResolvedOptimizedShopQuery,
|
'usedOptimizedShopQuery' => $optimizedShopQuery !== '',
|
||||||
'optimizedShopQuery' => $optimizedShopQuery,
|
'optimizedShopQuery' => $optimizedShopQuery,
|
||||||
'shopSearchQuery' => $shopSearchQuery,
|
'shopSearchQuery' => $shopSearchQuery,
|
||||||
'hasCommerceHistoryContext' => $commerceHistoryContext !== '',
|
'hasCommerceHistoryContext' => $commerceHistoryContext !== '',
|
||||||
@@ -232,6 +238,22 @@ final readonly class AgentRunner
|
|||||||
$usedShopRepair = $repairPayload['usedRepair'];
|
$usedShopRepair = $repairPayload['usedRepair'];
|
||||||
$shopRepairQueries = $repairPayload['repairQueries'];
|
$shopRepairQueries = $repairPayload['repairQueries'];
|
||||||
|
|
||||||
|
if ($shopSearchQuery !== '' && !$primaryShopSearchHadSystemFailure) {
|
||||||
|
yield $this->systemMsg(
|
||||||
|
$this->buildShopSearchMetaMessage(
|
||||||
|
query: $shopSearchDisplayQuery !== '' ? $shopSearchDisplayQuery : $shopSearchQuery,
|
||||||
|
commerceIntent: $commerceIntent,
|
||||||
|
usedOptimizedQuery: $shopSearchUsedOptimizedQuery,
|
||||||
|
originalQuery: $shopSearchQuery,
|
||||||
|
resultCount: count($shopResults),
|
||||||
|
completed: true,
|
||||||
|
attemptedRepair: $attemptedShopRepair,
|
||||||
|
usedRepair: $usedShopRepair
|
||||||
|
),
|
||||||
|
'meta'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($shopResults !== []) {
|
if ($shopResults !== []) {
|
||||||
$this->addSource($sources, $this->agentRunnerConfig->getShopSystemSourceLabel());
|
$this->addSource($sources, $this->agentRunnerConfig->getShopSystemSourceLabel());
|
||||||
}
|
}
|
||||||
@@ -483,7 +505,7 @@ final readonly class AgentRunner
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match_all('/^Question:\s*(.+)$/mi', $history, $matches) < 1) {
|
if (preg_match_all('/^Question:\s*(.+)$/mi', $history, $matches) !== 1) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,20 +746,11 @@ final readonly class AgentRunner
|
|||||||
string $commerceHistoryContext,
|
string $commerceHistoryContext,
|
||||||
string $userId
|
string $userId
|
||||||
): string {
|
): string {
|
||||||
$promptIsMetaOnly = $this->isMetaOnlyShopQuery($prompt);
|
if ($optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
|
||||||
|
|
||||||
/**
|
|
||||||
* A pure meta command such as "suche im shop" has no own product
|
|
||||||
* semantics. In that case the LLM optimizer must not be trusted as the
|
|
||||||
* primary source because it can copy instruction terms from the query
|
|
||||||
* prompt itself (for example "Shopware 6"). Resolve meta commands
|
|
||||||
* deterministically from the recent conversation instead.
|
|
||||||
*/
|
|
||||||
if (!$promptIsMetaOnly && $optimizedShopQuery !== '' && !$this->isMetaOnlyShopQuery($optimizedShopQuery)) {
|
|
||||||
return $optimizedShopQuery;
|
return $optimizedShopQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$promptIsMetaOnly) {
|
if (!$this->isMetaOnlyShopQuery($prompt)) {
|
||||||
return $prompt;
|
return $prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,7 +860,7 @@ final readonly class AgentRunner
|
|||||||
$value = mb_strtolower(trim($value), 'UTF-8');
|
$value = mb_strtolower(trim($value), 'UTF-8');
|
||||||
$value = str_replace(['-', '/', '_'], ' ', $value);
|
$value = str_replace(['-', '/', '_'], ' ', $value);
|
||||||
|
|
||||||
if (preg_match_all('/\d+(?:[,.]\d+)?|[\p{L}\p{N}]+/u', $value, $matches) < 1) {
|
if (preg_match_all('/\d+(?:[,.]\d+)?|[\p{L}\p{N}]+/u', $value, $matches) !== 1) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,7 +1307,11 @@ final readonly class AgentRunner
|
|||||||
string $query,
|
string $query,
|
||||||
string $commerceIntent,
|
string $commerceIntent,
|
||||||
bool $usedOptimizedQuery,
|
bool $usedOptimizedQuery,
|
||||||
string $originalQuery
|
string $originalQuery,
|
||||||
|
?int $resultCount = null,
|
||||||
|
bool $completed = false,
|
||||||
|
bool $attemptedRepair = false,
|
||||||
|
bool $usedRepair = false
|
||||||
): string {
|
): string {
|
||||||
$query = $this->normalizeOneLine($query);
|
$query = $this->normalizeOneLine($query);
|
||||||
$originalQuery = $this->normalizeOneLine($originalQuery);
|
$originalQuery = $this->normalizeOneLine($originalQuery);
|
||||||
@@ -1303,20 +1320,47 @@ final readonly class AgentRunner
|
|||||||
$query = $originalQuery !== '' ? $originalQuery : 'keine Suchquery ermittelt';
|
$query = $originalQuery !== '' ? $originalQuery : 'keine Suchquery ermittelt';
|
||||||
}
|
}
|
||||||
|
|
||||||
$badge = $usedOptimizedQuery ? 'optimiert' : 'direkt';
|
$queryModeLabel = $usedOptimizedQuery ? 'optimiert' : 'direkt';
|
||||||
$intentLabel = $commerceIntent !== '' ? $commerceIntent : 'commerce';
|
$intentLabel = $commerceIntent !== '' ? $commerceIntent : 'commerce';
|
||||||
|
$title = $completed ? 'Shop-Suche abgeschlossen' : 'Shop-Suche wird ausgeführt';
|
||||||
|
$statusLabel = $completed ? 'Status: abgeschlossen' : 'Status: läuft';
|
||||||
|
$resultLabel = $resultCount === null
|
||||||
|
? 'Shoptreffer: wird geladen'
|
||||||
|
: 'Shoptreffer: ' . max(0, $resultCount);
|
||||||
|
$state = $completed ? 'completed' : 'running';
|
||||||
|
$resultCountAttribute = $resultCount === null ? '' : (string) max(0, $resultCount);
|
||||||
|
$repairLabel = '';
|
||||||
|
|
||||||
return '<div class="retriex-meta-card retriex-shop-meta">'
|
if ($usedRepair) {
|
||||||
|
$repairLabel = 'Erweiterte Suche: genutzt';
|
||||||
|
} elseif ($attemptedRepair) {
|
||||||
|
$repairLabel = 'Erweiterte Suche: geprüft';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<div class="retriex-meta-card retriex-shop-meta" data-retriex-meta-id="shop-search" data-retriex-meta-state="'
|
||||||
|
. htmlspecialchars($state, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
||||||
|
. '" data-retriex-shop-result-count="'
|
||||||
|
. htmlspecialchars($resultCountAttribute, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
||||||
|
. '">'
|
||||||
. '<div class="retriex-meta-card__eyebrow">Live-Shopdaten</div>'
|
. '<div class="retriex-meta-card__eyebrow">Live-Shopdaten</div>'
|
||||||
. '<div class="retriex-meta-card__title">Shop-Suche wird ausgeführt</div>'
|
. '<div class="retriex-meta-card__title">' . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</div>'
|
||||||
. '<div class="retriex-meta-card__body">'
|
. '<div class="retriex-meta-card__body">'
|
||||||
. '<span class="retriex-meta-pill">' . htmlspecialchars($badge, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
|
. '<span class="retriex-meta-pill retriex-meta-pill--result">' . htmlspecialchars($resultLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
|
||||||
. '<span class="retriex-meta-pill">Intent: ' . htmlspecialchars($intentLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
|
. '<span class="retriex-meta-pill">' . htmlspecialchars($statusLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
|
||||||
. '</div>'
|
. '<span class="retriex-meta-pill">Query: ' . htmlspecialchars($queryModeLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>'
|
||||||
|
. '<span class="retriex-meta-pill">Intent: ' . htmlspecialchars($intentLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>';
|
||||||
|
|
||||||
|
if ($repairLabel !== '') {
|
||||||
|
$html .= '<span class="retriex-meta-pill">' . htmlspecialchars($repairLabel, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</div>'
|
||||||
. '<div class="retriex-meta-query"><span>Gesendete Suchquery</span><code>'
|
. '<div class="retriex-meta-query"><span>Gesendete Suchquery</span><code>'
|
||||||
. htmlspecialchars($query, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
. htmlspecialchars($query, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
||||||
. '</code></div>'
|
. '</code></div>'
|
||||||
. '</div>';
|
. '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildShopUnavailableMessage(?string $reason): string
|
private function buildShopUnavailableMessage(?string $reason): string
|
||||||
|
|||||||
Reference in New Issue
Block a user