Files
deepseek-harness/packages/client/ui-chat/src/client/chat/ChatView.module.css
T

305 lines
9.1 KiB
CSS

.frame {
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
flex: 1 1 auto;
/* Floating controls query this frame outside the clipped transcript. */
container-type: inline-size;
}
.root {
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
flex: 1 1 auto;
overflow-x: visible;
overflow-y: clip;
}
.scroll {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
/* Sides = composer clearance + 16px: on narrow viewports the transcript
stays exactly 32px narrower than the input card (the shared width rule). */
padding: 16px calc(var(--dsh-composer-side-clearance) + 16px);
/* Inline-size query container: wide markdown tables size their breakout
against this box's content width (100cqw in AssistantMarkdown's
md-table-wide rule) — the CSS stand-in for deepsuite chat's JS-measured
--dsl-virtual-list-width. */
container-type: inline-size;
}
.root[data-chat-following-tail] .scroll,
:global([data-conversation-scroll]):has(.root[data-chat-following-tail]) {
overflow-anchor: none;
}
:global([data-conversation-scroll]) .frame,
:global([data-conversation-scroll]) .root {
flex: 0 0 auto;
min-height: auto;
height: auto;
}
:global([data-conversation-scroll]) .scroll {
overflow: visible;
flex: 0 0 auto;
min-height: auto;
}
/* Message column: shared chat width (ConversationRoot --dsh-chat-content-width),
centered on the same axis as the input box (which caps at chat + 16px); the
scroller itself stays full-bleed. */
.column {
max-width: var(--dsh-chat-content-width);
width: 100%;
margin: 0 auto;
display: flex;
flex-direction: column;
}
/* The flow slot's display:contents anchor preserves layout but not selector ancestry.
`hidden="until-found"` retains a searchable zero-height box; hidden and empty
Seats do not contribute spacing. */
:is(.column, :global([data-slot="conversation.chat.flow"])) > :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)))
~ :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))),
.column:has(> .older) > :global([data-slot="conversation.chat.flow"])
> :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))) {
margin-top: var(--dsh-chat-flow-gap, 6px);
}
/* Process rows sit 6px apart; an Assistant response is separated from its
neighbours by 12px. */
.flowItem[data-chat-group-part="response"] {
--dsh-chat-flow-gap: 12px;
}
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-group-part="response"]:not([hidden]):not(:empty)
+ :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running) {
--dsh-chat-flow-gap: 12px;
}
/* Settled-flow identity boundary. It is neutral until it becomes the natural
measurement/mount unit for a virtualizer without changing the column gap. */
.flowItem {
min-width: 0;
}
/* Bottom room: sized by the viewport owner while rows above the reader close,
given back only as later content grows into it. */
.turnSpacer {
height: 0;
}
/* While a Turn folds, spacing changes share the closing rows' timing. */
.column[data-chat-motion] > :not([data-chat-motion]),
.column[data-chat-motion] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
transition: margin-top 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
.column[data-input-fold] > :not([data-chat-motion]),
.column[data-input-fold] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
transition: none;
}
@media (prefers-reduced-motion: reduce) {
.column[data-chat-motion] > :not([data-chat-motion]),
.column[data-chat-motion] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
transition: none;
}
}
/* The Turn header has 16px clearance from its input and following content. */
.flowItem[data-chat-flow-kind="turn-process"],
.flowItem[data-turn-process-answer],
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-flow-kind="turn-process"] + :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running) {
--dsh-chat-flow-gap: 16px;
}
/* Empty Seats, including retained empty slot anchors, preserve Turn lookup
without contributing height or the sibling gap. */
.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)) {
height: 0;
}
.callRow {
border-radius: var(--dsw-radius-sm);
}
/* Selection still sets data-selected for details linkage; no outline —
tool rows match Think chrome (no selected ring). */
.hint {
color: var(--dsw-alias-label-tertiary);
font-size: var(--dsh-content-font-size-secondary, 13px);
line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
}
.running {
display: flex;
flex-direction: column;
align-items: flex-start;
--dsh-chat-flow-gap: 12px;
--dsw-alias-label-shimmer: var(--dsw-alias-label-deep-diving-shimmer);
color: var(--dsw-alias-label-deep-diving);
font-size: calc(var(--dsh-content-font-size, 14px) - 2px);
line-height: calc(22px + var(--dsh-content-font-delta, 0px));
}
/* The last visible row before the status must contain output, not an input.
Hidden and empty seats do not separate a user message from the status. */
:is(.column, :global([data-slot="conversation.chat.flow"])) > :nth-last-child(2 of :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)))):is(
.flowItem:not([data-chat-flow-kind="user"]):not([data-chat-flow-kind="steering"]):not([data-chat-flow-kind="turn-trigger"]),
[data-chat-group-key]
) ~ .running > .runningDivider {
display: block;
}
.runningDivider {
display: none;
width: 100%;
height: 0.5px;
margin: 8px 0 10px;
background: color-mix(in srgb, var(--dsw-alias-border-l1) 75%, var(--dsw-alias-border-l2));
pointer-events: none;
}
.runningContent {
display: inline-flex;
align-items: center;
min-width: 0;
gap: 6px;
}
.runningText {
min-width: 0;
font-variant-numeric: tabular-nums;
}
.runningIcon {
position: relative;
display: inline-flex;
flex: none;
width: calc(14px + var(--dsh-content-font-delta, 0px));
height: calc(14px + var(--dsh-content-font-delta, 0px));
/* The fixed footprint contains the animation's layout and paint. */
contain: strict;
overflow: hidden;
}
.runningWhaleAnimated {
display: none;
position: absolute;
inset: 0;
}
.runningWhaleStill {
display: initial;
}
@supports (mask-mode: alpha) and (mask-image: url('')) {
@media (prefers-reduced-motion: no-preference) and (forced-colors: none) {
.runningWhaleAnimated {
display: block;
background: currentColor;
mask: url('./[email protected]') center / 100% 100% no-repeat;
mask-mode: alpha;
/* Keep animated-mask repaints on an icon-sized composited layer. */
will-change: transform;
}
.runningWhaleStill {
display: none;
}
}
}
.openError {
color: var(--dsw-alias-state-error-primary);
font-size: var(--dsh-content-font-size-secondary, 13px);
line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
}
.older {
display: flex;
justify-content: center;
}
.older button {
border: none;
border-radius: var(--dsw-radius-sm);
padding: 4px 12px;
font-size: 12px;
color: var(--dsw-alias-label-secondary);
background: var(--dsw-alias-interactive-bg-hover-solid);
cursor: pointer;
}
.older button:disabled {
cursor: default;
opacity: 0.6;
}
/* Floating controls sit outside the clipped transcript. A standalone Chat
anchors them to its frame; the shared host supplies the sticky scrollport. */
.toBottomSlot {
position: absolute;
right: 0;
bottom: 16px;
left: 0;
/* Above the sticky composer (z-index 7) so the control stays clickable and
visible over the input card. Exception: while an @/slash menu is open the
seat lifts to 9 (ui-conversation ConversationRoot) so the menu is never
covered by this control. */
z-index: 8;
height: 0;
display: flex;
justify-content: flex-end;
padding-right: max(
calc(var(--dsh-composer-side-clearance) + 16px),
calc((100% - var(--dsh-chat-content-width)) / 2)
);
pointer-events: none;
}
:global([data-conversation-scroll]) .toBottomSlot {
position: sticky;
/* Clears the sticky composer stack (docks + input card + stats): the live
height rides --dsh-composer-height (ConversationRoot's seat observer) so
the control follows a growing textarea; the fallback covers the first
paint before the observer fires. */
bottom: calc(var(--dsh-composer-height, 152px) + 16px);
}
.toBottom {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
margin-top: -34px;
padding: 0;
border: 0;
--dsw-elevation-stroke-color: var(--dsw-alias-border-l3);
border-radius: 100px;
corner-shape: round;
color: var(--dsw-alias-label-primary);
background: var(--dsw-alias-button-floating-fill);
box-shadow: var(--dsw-elevation-panel);
cursor: pointer;
pointer-events: auto;
}
.toBottom:hover {
background: var(--dsw-alias-button-floating-hover);
}
/* Host open-path refusal: same dialog family as the workspace folder error. */
.modalAction {
min-width: 72px;
}