优化新会话页面显示布局
This commit is contained in:
@@ -32,6 +32,7 @@ interface ConversationShellProps {
|
|||||||
availableCommands?: AvailableCommandInfo[] | null
|
availableCommands?: AvailableCommandInfo[] | null
|
||||||
attachmentTabId?: string | null
|
attachmentTabId?: string | null
|
||||||
draftStorageKey?: string | null
|
draftStorageKey?: string | null
|
||||||
|
hideInput?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConversationShell({
|
export function ConversationShell({
|
||||||
@@ -55,6 +56,7 @@ export function ConversationShell({
|
|||||||
availableCommands,
|
availableCommands,
|
||||||
attachmentTabId,
|
attachmentTabId,
|
||||||
draftStorageKey,
|
draftStorageKey,
|
||||||
|
hideInput = false,
|
||||||
}: ConversationShellProps) {
|
}: ConversationShellProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 flex-col">
|
<div className="flex h-full min-h-0 flex-col">
|
||||||
@@ -65,24 +67,26 @@ export function ConversationShell({
|
|||||||
onRespond={onRespondPermission}
|
onRespond={onRespondPermission}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatInput
|
{!hideInput && (
|
||||||
status={status}
|
<ChatInput
|
||||||
promptCapabilities={promptCapabilities}
|
status={status}
|
||||||
defaultPath={defaultPath}
|
promptCapabilities={promptCapabilities}
|
||||||
onFocus={onFocus}
|
defaultPath={defaultPath}
|
||||||
onSend={onSend}
|
onFocus={onFocus}
|
||||||
onCancel={onCancel}
|
onSend={onSend}
|
||||||
modes={modes}
|
onCancel={onCancel}
|
||||||
configOptions={configOptions}
|
modes={modes}
|
||||||
modeLoading={modeLoading}
|
configOptions={configOptions}
|
||||||
configOptionsLoading={configOptionsLoading}
|
modeLoading={modeLoading}
|
||||||
selectedModeId={selectedModeId}
|
configOptionsLoading={configOptionsLoading}
|
||||||
onModeChange={onModeChange}
|
selectedModeId={selectedModeId}
|
||||||
onConfigOptionChange={onConfigOptionChange}
|
onModeChange={onModeChange}
|
||||||
availableCommands={availableCommands}
|
onConfigOptionChange={onConfigOptionChange}
|
||||||
attachmentTabId={attachmentTabId}
|
availableCommands={availableCommands}
|
||||||
draftStorageKey={draftStorageKey}
|
attachmentTabId={attachmentTabId}
|
||||||
/>
|
draftStorageKey={draftStorageKey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="px-4 py-2 text-xs text-destructive bg-destructive/5 border-t border-destructive/20">
|
<div className="px-4 py-2 text-xs text-destructive bg-destructive/5 border-t border-destructive/20">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useConnectionLifecycle } from "@/hooks/use-connection-lifecycle"
|
|||||||
import { MessageListView } from "@/components/message/message-list-view"
|
import { MessageListView } from "@/components/message/message-list-view"
|
||||||
import { ConversationShell } from "@/components/chat/conversation-shell"
|
import { ConversationShell } from "@/components/chat/conversation-shell"
|
||||||
import { AgentSelector } from "@/components/chat/agent-selector"
|
import { AgentSelector } from "@/components/chat/agent-selector"
|
||||||
|
import { ChatInput } from "@/components/chat/chat-input"
|
||||||
import {
|
import {
|
||||||
createConversation,
|
createConversation,
|
||||||
openSettingsWindow,
|
openSettingsWindow,
|
||||||
@@ -151,6 +152,7 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
const [agentConnectError, setAgentConnectError] = useState<string | null>(
|
const [agentConnectError, setAgentConnectError] = useState<string | null>(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
const [hasSentMessage, setHasSentMessage] = useState(false)
|
||||||
|
|
||||||
const hasPersistedConversation = dbConversationId != null
|
const hasPersistedConversation = dbConversationId != null
|
||||||
const canAutoConnect =
|
const canAutoConnect =
|
||||||
@@ -425,6 +427,7 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
)
|
)
|
||||||
setSendSignal((prev) => prev + 1)
|
setSendSignal((prev) => prev + 1)
|
||||||
setSyncState(effectiveConversationId, "awaiting_persist")
|
setSyncState(effectiveConversationId, "awaiting_persist")
|
||||||
|
setHasSentMessage(true)
|
||||||
lifecycleSend(draft, selectedModeIdArg)
|
lifecycleSend(draft, selectedModeIdArg)
|
||||||
|
|
||||||
const persistedId = dbConvIdRef.current
|
const persistedId = dbConvIdRef.current
|
||||||
@@ -534,6 +537,9 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
[connConnect, connDisconnect, workingDirForConnection]
|
[connConnect, connDisconnect, workingDirForConnection]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const showDraftHeader = !hasPersistedConversation
|
||||||
|
const isWelcomeMode = showDraftHeader && !hasSentMessage
|
||||||
|
|
||||||
const messageListNode = (
|
const messageListNode = (
|
||||||
<MessageListView
|
<MessageListView
|
||||||
conversationId={effectiveConversationId}
|
conversationId={effectiveConversationId}
|
||||||
@@ -543,11 +549,10 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
sessionStats={detail?.session_stats ?? null}
|
sessionStats={detail?.session_stats ?? null}
|
||||||
detailLoading={detailLoading}
|
detailLoading={detailLoading}
|
||||||
detailError={detailError}
|
detailError={detailError}
|
||||||
|
hideEmptyState={showDraftHeader}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const showDraftHeader = !hasPersistedConversation
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationShell
|
<ConversationShell
|
||||||
status={connStatus}
|
status={connStatus}
|
||||||
@@ -569,8 +574,59 @@ const ConversationTabView = memo(function ConversationTabView({
|
|||||||
availableCommands={connectionCommands}
|
availableCommands={connectionCommands}
|
||||||
attachmentTabId={tabId}
|
attachmentTabId={tabId}
|
||||||
draftStorageKey={draftStorageKey}
|
draftStorageKey={draftStorageKey}
|
||||||
|
hideInput={isWelcomeMode}
|
||||||
>
|
>
|
||||||
{showDraftHeader ? (
|
{isWelcomeMode ? (
|
||||||
|
<div className="flex h-full min-h-0 flex-col items-center justify-center">
|
||||||
|
<div className="flex w-full max-w-2xl flex-col gap-4 px-4">
|
||||||
|
<AgentSelector
|
||||||
|
defaultAgentType={selectedAgent}
|
||||||
|
onSelect={handleAgentSelect}
|
||||||
|
onAgentsLoaded={(agents) => {
|
||||||
|
setAgentsLoaded(true)
|
||||||
|
setUsableAgentCount(
|
||||||
|
agents.filter((agent) => agent.enabled && agent.available)
|
||||||
|
.length
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onOpenAgentsSettings={handleOpenAgentsSettings}
|
||||||
|
disabled={isConnecting || dbConversationId != null}
|
||||||
|
/>
|
||||||
|
{autoConnectError || agentConnectError ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleOpenAgentsSettings}
|
||||||
|
className="w-full cursor-pointer rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-center text-xs text-destructive transition-colors hover:bg-destructive/10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="overflow-hidden text-ellipsis whitespace-nowrap text-center"
|
||||||
|
title={autoConnectError ?? agentConnectError ?? ""}
|
||||||
|
>
|
||||||
|
{autoConnectError ?? agentConnectError}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<ChatInput
|
||||||
|
status={connStatus}
|
||||||
|
promptCapabilities={conn.promptCapabilities}
|
||||||
|
defaultPath={workingDirForConnection}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onSend={handleSend}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
modes={connectionModes}
|
||||||
|
configOptions={connectionConfigOptions}
|
||||||
|
modeLoading={modeLoading}
|
||||||
|
configOptionsLoading={configOptionsLoading}
|
||||||
|
selectedModeId={selectedModeId}
|
||||||
|
onModeChange={setModeId}
|
||||||
|
onConfigOptionChange={handleSetConfigOption}
|
||||||
|
availableCommands={connectionCommands}
|
||||||
|
attachmentTabId={tabId}
|
||||||
|
draftStorageKey={draftStorageKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : showDraftHeader ? (
|
||||||
<div className="flex h-full min-h-0 flex-col">
|
<div className="flex h-full min-h-0 flex-col">
|
||||||
<div className="px-4 pt-3 pb-2">
|
<div className="px-4 pt-3 pb-2">
|
||||||
<AgentSelector
|
<AgentSelector
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface MessageListViewProps {
|
|||||||
sessionStats?: SessionStats | null
|
sessionStats?: SessionStats | null
|
||||||
detailLoading?: boolean
|
detailLoading?: boolean
|
||||||
detailError?: string | null
|
detailError?: string | null
|
||||||
|
hideEmptyState?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResolvedMessageGroup extends MessageGroup {
|
interface ResolvedMessageGroup extends MessageGroup {
|
||||||
@@ -133,6 +134,7 @@ export function MessageListView({
|
|||||||
sessionStats = null,
|
sessionStats = null,
|
||||||
detailLoading = false,
|
detailLoading = false,
|
||||||
detailError = null,
|
detailError = null,
|
||||||
|
hideEmptyState = false,
|
||||||
}: MessageListViewProps) {
|
}: MessageListViewProps) {
|
||||||
const t = useTranslations("Folder.chat.messageList")
|
const t = useTranslations("Folder.chat.messageList")
|
||||||
const sharedT = useTranslations("Folder.chat.shared")
|
const sharedT = useTranslations("Folder.chat.shared")
|
||||||
@@ -237,14 +239,15 @@ export function MessageListView({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const emptyState = useMemo(
|
const emptyState = useMemo(
|
||||||
() => (
|
() =>
|
||||||
<div className="px-4 py-12 text-center">
|
hideEmptyState ? null : (
|
||||||
<p className="text-muted-foreground text-sm">
|
<div className="px-4 py-12 text-center">
|
||||||
{t("emptyConversation")}
|
<p className="text-muted-foreground text-sm">
|
||||||
</p>
|
{t("emptyConversation")}
|
||||||
</div>
|
</p>
|
||||||
),
|
</div>
|
||||||
[t]
|
),
|
||||||
|
[hideEmptyState, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const agentPlanOverlayKey = liveMessage?.id ?? `history-${conversationId}`
|
const agentPlanOverlayKey = liveMessage?.id ?? `history-${conversationId}`
|
||||||
|
|||||||
Reference in New Issue
Block a user