diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts index faddaade51..c9bba28a1a 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts @@ -7,6 +7,7 @@ import type { EditorHost } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std'; import type { BaseSelection, ExtensionType } from '@blocksuite/affine/store'; import { ArrowDownBigIcon as ArrowDownIcon } from '@blocksuite/icons/lit'; +import type { Signal } from '@preact/signals-core'; import { css, html, nothing, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -173,6 +174,9 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) accessor reasoningConfig!: AIReasoningConfig; + @property({ attribute: false }) + accessor panelWidth!: Signal; + @query('.chat-panel-messages-container') accessor messagesContainer: HTMLDivElement | null = null; @@ -300,6 +304,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { .affineFeatureFlagService=${this.affineFeatureFlagService} .getSessionId=${this.getSessionId} .retry=${() => this.retry()} + .panelWidth=${this.panelWidth} >`; } else if (isChatAction(item)) { return html` ; + renderHeader() { const isWithDocs = 'content' in this.item && @@ -151,6 +155,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) { `; case 'web_search_exa': @@ -158,6 +163,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) { `; default: @@ -180,6 +186,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) { `; case 'web_search_exa': @@ -187,6 +194,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) { `; default: diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/tool-result-card.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/tool-result-card.ts index bde2f302e2..6da24e66eb 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/tool-result-card.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/tool-result-card.ts @@ -1,18 +1,21 @@ -import { WithDisposable } from '@blocksuite/affine/global/lit'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { ImageProxyService } from '@blocksuite/affine/shared/adapters'; import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std'; import { ToggleDownIcon } from '@blocksuite/icons/lit'; +import { type Signal, signal } from '@preact/signals-core'; import { css, html, nothing, type TemplateResult } from 'lit'; -import { property } from 'lit/decorators.js'; +import { property, state } from 'lit/decorators.js'; interface ToolResult { - title?: string; + title: string; icon?: string | TemplateResult<1>; content?: string; } -export class ToolResultCard extends WithDisposable(ShadowlessElement) { +export class ToolResultCard extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { static override styles = css` .ai-tool-wrapper { padding: 12px; @@ -25,6 +28,8 @@ export class ToolResultCard extends WithDisposable(ShadowlessElement) { justify-content: space-between; align-items: center; gap: 8px; + margin-right: 3px; + cursor: pointer; } .ai-icon { @@ -54,11 +59,23 @@ export class ToolResultCard extends WithDisposable(ShadowlessElement) { display: flex; flex-direction: column; gap: 4px; - margin: 4px 2px 4px 12px; + margin: 8px 2px 4px 12px; padding-left: 20px; border-left: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; } + .ai-tool-results[data-collapsed='true'] { + display: none; + } + + .result-item { + margin-top: 12px; + } + + .result-item:first-child { + margin-top: 0; + } + .result-header { display: flex; justify-content: space-between; @@ -86,19 +103,54 @@ export class ToolResultCard extends WithDisposable(ShadowlessElement) { border-radius: 100%; border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; } + + svg { + width: 24px; + height: 24px; + color: ${unsafeCSSVarV2('icon/primary')}; + } } .result-content { font-size: 12px; line-height: 20px; color: ${unsafeCSSVarV2('text/secondary')}; - margin-top: 8px; + margin-top: 4px; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; } + + .footer-icons { + display: flex; + position: relative; + height: 24px; + align-items: center; + } + + .footer-icon { + width: 18px; + height: 18px; + + img { + width: 18px; + height: 18px; + border-radius: 100%; + border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; + } + + svg { + width: 18px; + height: 18px; + color: ${unsafeCSSVarV2('icon/primary')}; + } + } + + .footer-icon:not(:first-child) { + margin-left: -8px; + } } `; @@ -111,41 +163,35 @@ export class ToolResultCard extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) accessor icon!: TemplateResult<1> | string; + @property({ attribute: false }) + accessor footerIcons: TemplateResult<1>[] | string[] = []; + @property({ attribute: false }) accessor results!: ToolResult[]; - protected override render() { - const imageProxyService = this.host.store.get(ImageProxyService); + @property({ attribute: false }) + accessor width: Signal = signal(undefined); + @state() + private accessor isCollapsed = true; + + protected override render() { return html`
-
-
${this.icon}
+
+
${this.renderIcon(this.icon)}
${this.name}
-
${ToggleDownIcon()}
+ ${this.isCollapsed + ? this.renderFooterIcons() + : html`
${ToggleDownIcon()}
`}
-
+
${this.results.map( result => html` -
+
-
${result.title || 'Untitled'}
- ${result.icon - ? html` -
- ${typeof result.icon === 'string' - ? html`icon { - const target = e.target as HTMLImageElement; - target.style.display = 'none'; - }} - />` - : result.icon} -
- ` - : nothing} +
${result.title}
+
${this.renderIcon(result.icon)}
${result.content ? html`
${result.content}
` @@ -157,4 +203,43 @@ export class ToolResultCard extends WithDisposable(ShadowlessElement) {
`; } + + private renderFooterIcons() { + if (!this.footerIcons || this.footerIcons.length === 0) { + return nothing; + } + + const maxIcons = Number(this.width.value) <= 400 ? 1 : 3; + const visibleIcons = this.footerIcons.slice(0, maxIcons); + + return html` + + `; + } + + private renderIcon(icon: string | TemplateResult<1> | undefined) { + if (!icon) { + return nothing; + } + const imageProxyService = this.host.store.get(ImageProxyService); + if (typeof icon === 'string') { + return html` `; + } + return html`${icon}`; + } + + private toggleCard() { + this.isCollapsed = !this.isCollapsed; + } } diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-crawl.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-crawl.ts index b2fd4efc32..b70a6c52bc 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-crawl.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-crawl.ts @@ -1,6 +1,7 @@ import { WithDisposable } from '@blocksuite/affine/global/lit'; import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std'; import { WebIcon } from '@blocksuite/icons/lit'; +import type { Signal } from '@preact/signals-core'; import { html, nothing } from 'lit'; import { property } from 'lit/decorators.js'; @@ -33,6 +34,9 @@ export class WebCrawlTool extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) accessor host!: EditorHost; + @property({ attribute: false }) + accessor width!: Signal; + renderToolCall() { return html` `; } diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-search.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-search.ts index 564786379f..7aaa78fd2a 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-search.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/tools/web-search.ts @@ -1,6 +1,7 @@ import { WithDisposable } from '@blocksuite/affine/global/lit'; import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std'; import { WebIcon } from '@blocksuite/icons/lit'; +import type { Signal } from '@preact/signals-core'; import { html, nothing } from 'lit'; import { property } from 'lit/decorators.js'; @@ -33,6 +34,9 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) accessor host!: EditorHost; + @property({ attribute: false }) + accessor width!: Signal; + renderToolCall() { return html` item.favicon) + .filter(Boolean); return html` `; }