diff --git a/src/mobile-web/src/styles/components/chat-input.scss b/src/mobile-web/src/styles/components/chat-input.scss index 0cb60a0a2b..b903a26580 100644 --- a/src/mobile-web/src/styles/components/chat-input.scss +++ b/src/mobile-web/src/styles/components/chat-input.scss @@ -315,8 +315,8 @@ .chat-page__action-btn { flex-shrink: 0; - width: var(--openbitfun-space-12); - height: var(--openbitfun-space-12); + width: 44px; + height: 44px; border-radius: 50%; border: none; background: transparent; @@ -339,8 +339,8 @@ .chat-page__send-btn { position: relative; - width: var(--openbitfun-space-12); - height: var(--openbitfun-space-12); + width: 44px; + height: 44px; border-radius: 50%; border: 1px solid var(--openbitfun-color-border-subtle); background: var(--openbitfun-color-action-neutral-surface); @@ -554,7 +554,7 @@ // Conversation composer layout and skin. Shared geometry belongs to MobileComposer. .chat-page__composer { - padding: 0 8px; + padding: 5px; border-color: var(--openbitfun-color-border-subtle); border-radius: 28px; background: var(--openbitfun-color-surface-panel); @@ -564,7 +564,7 @@ .chat-page__composer[data-expanded='true'] { height: auto; max-height: none; - padding: 8px; + padding: 5px; border-radius: 18px; } @@ -575,9 +575,9 @@ .chat-page__composer-leading { display: grid; place-items: center; - flex: 0 0 48px; - width: 48px; - height: 48px; + flex: 0 0 44px; + width: 44px; + height: 44px; padding: 0; border: 0; border-radius: 20px; @@ -608,8 +608,8 @@ } .chat-page__input-wrap.is-expanded .chat-page__action-btn { - width: 48px; - height: 48px; + width: 44px; + height: 44px; color: var(--openbitfun-color-content-primary); } @@ -645,3 +645,8 @@ aspect-ratio: 1; &:hover, &:active, &:focus { border-radius: 50%; } } + +// Keep the action row and collapsed pill on the same inset in both modes. +.chat-page__composer [data-openbitfun-part='toolbar'] { + min-block-size: 44px; +} diff --git a/src/mobile-web/src/styles/components/harmony-native.scss b/src/mobile-web/src/styles/components/harmony-native.scss index 6cb936bcac..6cf20852c4 100644 --- a/src/mobile-web/src/styles/components/harmony-native.scss +++ b/src/mobile-web/src/styles/components/harmony-native.scss @@ -1361,7 +1361,9 @@ .chat-msg__assistant-content { width: 100%; max-width: 100%; - padding: 0 2px 10px 0; + padding: 0 var(--size-gap-3) 10px; + box-sizing: border-box; + min-width: 0; border: 0; border-radius: 0; background: transparent; diff --git a/src/mobile-web/src/styles/host-queue.scss b/src/mobile-web/src/styles/host-queue.scss index ea9380fe7f..75af2e622a 100644 --- a/src/mobile-web/src/styles/host-queue.scss +++ b/src/mobile-web/src/styles/host-queue.scss @@ -10,7 +10,8 @@ font-size: var(--openbitfun-type-body-sm-font-size); line-height: var(--openbitfun-type-body-sm-line-height); - &__header { display: flex; align-items: center; padding: 0 12px; } + // 7px + half a 40px queue control matches the composer: 5px + half of 44px. + &__header { display: flex; align-items: center; padding: 0 7px 0 12px; } &__toggle { display: flex; align-items: center; @@ -53,7 +54,7 @@ &__list { list-style: none; margin: 0; - padding: 0 12px; + padding: 0 7px 0 12px; li { padding: 4px 0; border-top: 1px solid var(--openbitfun-color-border-subtle); } p { margin: 0 0 4px; } } diff --git a/src/mobile-web/tests/fixtures/host-queue.tsx b/src/mobile-web/tests/fixtures/host-queue.tsx index 2d0f0a2aaf..6a48c1960f 100644 --- a/src/mobile-web/tests/fixtures/host-queue.tsx +++ b/src/mobile-web/tests/fixtures/host-queue.tsx @@ -4,6 +4,8 @@ import '@openbitfun/theme-openbitfun/default.css'; import '@openbitfun/ui/mobile.css'; import '../../src/styles/index.scss'; import { createRoot } from 'react-dom/client'; +import { ThinkingBlock } from '../../src/components/ChatTranscript'; +import { MarkdownContent } from '../../src/components/ChatMarkdown'; import ChatComposerBar from '../../src/components/ChatComposerBar'; import { MobileHostQueue } from '../../src/components/MobileHostQueue'; import { useMobileViewport } from '../../src/hooks/useMobileViewport'; @@ -37,7 +39,7 @@ export function mountHostQueueFixture({ count = 1, expanded = true } = {}) { }, []); return
= 900 ? ' chat-page--wide' : ''}`} style={{ '--chat-composer-height': `${height}px` } as React.CSSProperties}>
项目介绍
-

正在检查代码和测试覆盖。

ExecCommand

分析当前项目的实现。

+
} cancelling={false} containerRef={ref} expanded={expanded} imageAnalyzing={false} sending={false} input="继续检查" inputRef={null} modelControls={null} onActivate={noop} onAttach={noop} onCancel={() => calls.push('stop')} diff --git a/src/mobile-web/tests/host-dialog-queue-browser.test.mjs b/src/mobile-web/tests/host-dialog-queue-browser.test.mjs index e21918b097..e73433c7a1 100644 --- a/src/mobile-web/tests/host-dialog-queue-browser.test.mjs +++ b/src/mobile-web/tests/host-dialog-queue-browser.test.mjs @@ -60,7 +60,7 @@ test('queue stays above the measured composer across phone, keyboard-height and const page=await browser.newPage(); for(const [width,height] of [[320,568],[390,844],[390,420],[768,800],[1200,900]]) { await page.setViewport({width,height}); - await page.goto(server.origin+'/tests/fixtures/host-queue.html?count=8'); + await page.goto(server.origin+'/tests/fixtures/host-queue.html?count=8&theme='+ (width===768 ? 'light' : 'dark')); await page.waitForSelector('.host-message-queue li'); await page.waitForFunction(()=>{ const wrap=document.querySelector('.chat-page__input-wrap'); @@ -99,6 +99,21 @@ test('queue stays above the measured composer across phone, keyboard-height and await page.goto(server.origin+'/tests/fixtures/host-queue.html?count=2&expanded=false'); await page.waitForSelector('.host-message-queue li'); assert.equal(await page.$('.chat-page__input'),null,'queue controls also work alongside the collapsed composer'); + const geometry=await page.evaluate(()=>{ + const box=s=>document.querySelector(s).getBoundingClientRect(); + const composer=box('.chat-page__composer'),stop=box('.chat-page__send-btn.is-stop'); + const plus=box('.chat-page__composer-leading'); + const text=box('.chat-msg__assistant-content'); + const icon=box('.chat-thinking [data-openbitfun-part="leading"]'); + return {top:stop.top-composer.top,bottom:composer.bottom-stop.bottom,right:composer.right-stop.right, + left:plus.left-composer.left,width:stop.width,plusWidth:plus.width, + textLeft:text.left+parseFloat(getComputedStyle(document.querySelector('.chat-msg__assistant-content')).paddingLeft),iconLeft:icon.left}; + }); + assert.equal(geometry.width,44); + assert.equal(geometry.plusWidth,44); + for(const edge of ['top','bottom','left']) assert.ok(Math.abs(geometry[edge]-geometry.right)<1,JSON.stringify(geometry)); + assert.ok(Math.abs(geometry.textLeft-geometry.iconLeft)<1,JSON.stringify(geometry)); + await page.screenshot({path:'/tmp/mobile-queue-aligned.png',fullPage:true}); await page.click('[aria-label="排队消息说明"]'); await page.waitForSelector('.host-message-queue__help'); await page.click('[aria-label="排队消息说明"]'); diff --git a/src/web-ui/src/flow_chat/utils/turnCompletionNotice.test.ts b/src/web-ui/src/flow_chat/utils/turnCompletionNotice.test.ts index bffa4deb81..f9810fb9f6 100644 --- a/src/web-ui/src/flow_chat/utils/turnCompletionNotice.test.ts +++ b/src/web-ui/src/flow_chat/utils/turnCompletionNotice.test.ts @@ -9,6 +9,12 @@ describe('getTurnCompletionNotice', () => { } as any)).toBeNull(); }); + it.each([true, false, undefined])('does not warn on user steering (final response: %s)', (hasFinalResponse) => { + expect(getTurnCompletionNotice({ + status: 'completed', finishReason: 'user_steering', hasFinalResponse, + })).toBeNull(); + }); + it('returns null for non-completed turns', () => { expect(getTurnCompletionNotice({ status: 'processing', diff --git a/src/web-ui/src/flow_chat/utils/turnCompletionNotice.ts b/src/web-ui/src/flow_chat/utils/turnCompletionNotice.ts index 9c16236a31..e446194773 100644 --- a/src/web-ui/src/flow_chat/utils/turnCompletionNotice.ts +++ b/src/web-ui/src/flow_chat/utils/turnCompletionNotice.ts @@ -19,7 +19,7 @@ interface NormalizedTurnCompletionNoticeState { hasFinalResponse?: boolean; } -const NORMAL_FINISH_REASONS = new Set(['complete']); +const NORMAL_FINISH_REASONS = new Set(['complete', 'user_steering']); const TURN_COMPLETION_NOTICE_CONFIG: Record = { repeated_tool_failures: {