fix(client-web): pin keyed WebRow in boot smoke, show empty-search feedback
- built-boot smoke asserts [data-variant=web][data-tool=web_search/fetch] (the keyed WebRow) instead of [data-web] (which WebBlock draws even on the GenericToolCard fallback, so a silent keyed-registration failure passed). - WebBlock renders an explicit empty-state note when a search returns no answer and no sources, instead of a blank <ol>; the chat row does not surface the raw result content, so the backend's 'No results found.' was otherwise invisible. - README (ui-conversation, ui-primitives) and the frontend Agent Note record the unknown-web-kind null arm, the details-panel flattened body, and the empty-search copy; pairings re-recorded.
This commit is contained in:
@@ -91,6 +91,11 @@
|
||||
font: var(--dsw-font-xs-13);
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font: var(--dsw-font-xs-13);
|
||||
}
|
||||
|
||||
/* The fetch card is a compact summary: the URL over a status/truncation row. */
|
||||
.fetch {
|
||||
display: flex;
|
||||
|
||||
@@ -174,34 +174,42 @@ function WebSearchBlock({ answer, sources, truncated, maxSources = DEFAULT_WEB_M
|
||||
const tailCount = maxSources - headCount
|
||||
const head = capped ? sources.slice(0, headCount) : sources
|
||||
const tail = capped ? sources.slice(sources.length - tailCount) : []
|
||||
// A provider may legitimately return no answer and no sources; the chat WebRow
|
||||
// does not show the raw result content, so without this the user would see an
|
||||
// empty card. Mirror the backend's `No results found.` render text.
|
||||
const empty = (answer === undefined || answer === '') && sources.length === 0
|
||||
return (
|
||||
<div className={clsx(css.block, className)} data-web="search">
|
||||
{answer !== undefined && answer !== '' && (
|
||||
<div className={css.answer}><MarkdownText text={answer} /></div>
|
||||
)}
|
||||
<ol className={css.sources}>
|
||||
{head.map((source, index) => <SourceItem key={index} source={source} ordinal={index + 1} />)}
|
||||
{hidden > 0 && (
|
||||
<li className={css.expandItem}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.expand}
|
||||
aria-expanded={expanded}
|
||||
aria-label={expanded ? '收起来源' : `展开其余 ${hidden} 条来源`}
|
||||
onClick={onToggle}
|
||||
>
|
||||
{expanded ? '收起' : `… 其余 ${hidden} 条来源`}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
{tail.map((source, index) => (
|
||||
<SourceItem
|
||||
key={sources.length - tailCount + index}
|
||||
source={source}
|
||||
ordinal={sources.length - tailCount + index + 1}
|
||||
/>
|
||||
))}
|
||||
</ol>
|
||||
{empty ? (
|
||||
<div className={css.empty}>未找到结果</div>
|
||||
) : (
|
||||
<ol className={css.sources}>
|
||||
{head.map((source, index) => <SourceItem key={index} source={source} ordinal={index + 1} />)}
|
||||
{hidden > 0 && (
|
||||
<li className={css.expandItem}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.expand}
|
||||
aria-expanded={expanded}
|
||||
aria-label={expanded ? '收起来源' : `展开其余 ${hidden} 条来源`}
|
||||
onClick={onToggle}
|
||||
>
|
||||
{expanded ? '收起' : `… 其余 ${hidden} 条来源`}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
{tail.map((source, index) => (
|
||||
<SourceItem
|
||||
key={sources.length - tailCount + index}
|
||||
source={source}
|
||||
ordinal={sources.length - tailCount + index + 1}
|
||||
/>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
{truncated && <div className={css.truncated}>来源列表已截断</div>}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user