fix(webui): restore session drag and review findings
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user