fix(webui): restore session drag and review findings

This commit is contained in:
chengyongru
2026-08-12 17:26:13 +08:00
committed by chengyongru
parent 4b5319b760
commit 686dd0603e
24 changed files with 494 additions and 169 deletions
+23 -2
View File
@@ -55,6 +55,7 @@ import {
type ChatGroupLabels,
} from "@/lib/chat-groups";
import { deriveTemporaryChatTitle } from "@/lib/temporary-chat";
import { clearDraggedSession, writeDraggedSession } from "@/lib/session-drag";
import { cn } from "@/lib/utils";
import type { ChatSummary, SidebarDensity, SidebarSortMode } from "@/lib/types";
@@ -616,6 +617,7 @@ export const ChatList = memo(function ChatList({
: updated.has(s.chatId) && !topicActive
? "updated"
: null;
const canDragSession = !topicActive && !deleteSelectionMode;
return (
<li
key={s.key}
@@ -648,12 +650,21 @@ export const ChatList = memo(function ChatList({
}
if (!topicActive) onSelect(s.key);
}}
draggable={false}
draggable={canDragSession}
onDragStart={(event) => {
if (!canDragSession) {
event.preventDefault();
return;
}
writeDraggedSession(event.dataTransfer, s.key);
}}
onDragEnd={clearDraggedSession}
aria-current={topicActive ? "page" : undefined}
aria-pressed={deleteSelectionMode ? tabSelected : undefined}
title={tooltipTitle}
className={cn(
"flex min-w-0 flex-1 items-center gap-2 overflow-hidden text-left",
canDragSession && "cursor-grab active:cursor-grabbing",
deleteSelectionMode && "cursor-default",
compact ? "py-1" : "py-1.5",
projectMode && "pl-7",
@@ -1050,6 +1061,7 @@ function ActivePaneRows({
const selected = selectedDeleteKeys.has(pane.key);
const isPinned = pinned.has(pane.key);
const isArchived = archived.has(pane.key);
const canDragSession = !active && !deleteSelectionMode;
return (
<li
@@ -1079,12 +1091,21 @@ function ActivePaneRows({
}
onSelectPane?.(group.tabKey, pane.key);
}}
draggable={false}
draggable={canDragSession}
onDragStart={(event) => {
if (!canDragSession) {
event.preventDefault();
return;
}
writeDraggedSession(event.dataTransfer, pane.key);
}}
onDragEnd={clearDraggedSession}
aria-current={active ? "true" : undefined}
aria-pressed={deleteSelectionMode ? selected : undefined}
title={pane.title}
className={cn(
"flex min-w-0 flex-1 items-center gap-2 overflow-hidden text-left font-medium leading-5",
canDragSession && "cursor-grab active:cursor-grabbing",
compact ? "py-1" : "py-1.5",
deleteSelectionMode && "cursor-default",
)}
@@ -104,37 +104,6 @@ export function splitCapabilityMentionSegments(
return segments.length ? segments : [{ kind: "text", text: value }];
}
export function CliAppMentionText({
text,
cliApps,
mcpPresets = [],
sessionMentions = [],
}: {
text: string;
cliApps: CliAppInfo[];
mcpPresets?: McpPresetInfo[];
sessionMentions?: SessionMention[];
}) {
const segments = splitCapabilityMentionSegments(text, cliApps, mcpPresets, sessionMentions);
if (!segments.some((segment) => segment.kind !== "text")) return <>{text}</>;
return (
<>
{segments.map((segment, index) => {
if (segment.kind === "text") {
return <span key={`text-${index}`}>{segment.text}</span>;
}
return (
<CapabilityMentionToken
key={`${segment.kind}-${index}`}
segment={segment}
variant="message"
/>
);
})}
</>
);
}
export function CapabilityMentionToken({
segment,
variant,
@@ -1,4 +1,10 @@
import { useEffect, useMemo, useState, type ComponentType } from "react";
import {
Suspense,
useEffect,
useMemo,
useState,
type ComponentType,
} from "react";
import {
Check,
ChevronDown,
@@ -135,15 +141,17 @@ export function ChannelSetupPanel({
const PluginPanel = uiContribution?.Panel;
if (PluginPanel) {
return (
<PluginPanel
token={token}
feature={feature}
actionKey={actionKey}
showBrandLogos={showBrandLogos}
chatAppsDocsUrl={chatAppsDocsUrl}
onAction={onAction}
onFeaturesUpdate={onFeaturesUpdate}
/>
<Suspense fallback={<ChannelPluginLoading />}>
<PluginPanel
token={token}
feature={feature}
actionKey={actionKey}
showBrandLogos={showBrandLogos}
chatAppsDocsUrl={chatAppsDocsUrl}
onAction={onAction}
onFeaturesUpdate={onFeaturesUpdate}
/>
</Suspense>
);
}
if (feature.instances !== undefined) {
@@ -432,13 +440,15 @@ function ChannelSetupSurface({
<ChannelSetupActions feature={feature} setup={setup} onNotice={setNotice} />
{mode === "connect" && ConnectFlow ? (
<ConnectFlow
token={token}
feature={feature}
idleLabel={setup.primaryActionLabel ?? tx("settings.channels.connect", "Connect")}
connectRequestId={connectRequestId}
onFeaturesUpdate={onFeaturesUpdate}
/>
<Suspense fallback={<ChannelPluginLoading compact />}>
<ConnectFlow
token={token}
feature={feature}
idleLabel={setup.primaryActionLabel ?? tx("settings.channels.connect", "Connect")}
connectRequestId={connectRequestId}
onFeaturesUpdate={onFeaturesUpdate}
/>
</Suspense>
) : mode === "connect" ? (
<>
<div className="mt-3 flex flex-wrap justify-end gap-2">
@@ -566,3 +576,19 @@ function ChannelSetupSurface({
</form>
);
}
function ChannelPluginLoading({ compact = false }: { compact?: boolean }) {
const { t } = useTranslation();
return (
<div
role="status"
className={cn(
"flex items-center justify-center gap-2 text-sm text-muted-foreground",
compact ? "min-h-12" : "min-h-48",
)}
>
<Loader2 className="h-4 w-4 animate-spin motion-reduce:animate-none" aria-hidden />
{t("settings.status.loading")}
</div>
);
}
@@ -69,6 +69,8 @@ export function ThinkingReasoningShell({
</button>
<div
{...(!expanded ? { inert: "" } : {})}
aria-hidden={!expanded}
className={cn(
"grid transition-[grid-template-rows,opacity] [transition-duration:220ms] ease-out motion-reduce:transition-none",
expanded
@@ -84,7 +86,6 @@ export function ThinkingReasoningShell({
data-fade-bottom={fadeBottom}
onScroll={onScroll}
className="mt-1.5 max-h-[180px] overflow-y-auto pr-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
aria-hidden={!expanded}
>
<div ref={contentRef} className="flex flex-col gap-0.5">
{children}