fix: simplify code navigation and support Gazebo XML files
Remove redundant code-view cancellation and align button labels. Restore module scrollbar thickness while preserving workspace colors, and support config, SDF and world text files locally and remotely.
This commit is contained in:
@@ -642,7 +642,6 @@ function LocalPcWorkspaceMockup({
|
||||
cancelPlan: cancelAgentPlan,
|
||||
updateCommandExecution: updateAgentCommandExecution,
|
||||
openCodeReference: openAgentCodeReference,
|
||||
cancelCodeReference: cancelAgentCodeReference,
|
||||
stop: stopAgent,
|
||||
clear: clearAgentConversation
|
||||
} = useAiAgent({
|
||||
@@ -4792,7 +4791,6 @@ function LocalPcWorkspaceMockup({
|
||||
if (cancelAgentPlan(messageId)) closeAgentDiffPreviewsForMessage(messageId)
|
||||
}}
|
||||
onOpenAgentCodeReference={(messageId, referenceId) => void openAgentCodeReference(messageId, referenceId, openWorkspaceAgentCodeReference)}
|
||||
onCancelAgentCodeReference={cancelAgentCodeReference}
|
||||
onApplyAgentPlan={(messageId, plan) => void applyAgentPlan(messageId, async () => {
|
||||
await applyWorkspaceAgentPlan(plan)
|
||||
closeAgentDiffPreviewsForMessage(messageId)
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { FileCode2, Loader2, X } from 'lucide-react'
|
||||
import { FileCode2, Loader2 } from 'lucide-react'
|
||||
import { t, useLocaleRender } from '../i18n'
|
||||
import type { AgentCodeReference, AgentCodeReferenceState } from '../features/workspace/useAiAgent'
|
||||
|
||||
export function AgentCodeReferences({ references, states, dark, busy, onOpen, onCancel }: {
|
||||
export function AgentCodeReferences({ references, states, dark, busy, onOpen }: {
|
||||
references: AgentCodeReference[]
|
||||
states?: Record<string, AgentCodeReferenceState>
|
||||
dark: boolean
|
||||
busy?: boolean
|
||||
onOpen?: (id: string) => void
|
||||
onCancel?: (id: string) => void
|
||||
}): React.JSX.Element {
|
||||
useLocaleRender()
|
||||
return <div className="grid min-w-0 gap-3">
|
||||
{references.map(reference => {
|
||||
const state = states?.[reference.id]
|
||||
const opening = state?.status === 'opening'
|
||||
const cancelled = state?.status === 'cancelled'
|
||||
return <div key={reference.id} data-agent-code-reference={reference.id} className={['min-w-0 border-t pt-2', dark ? 'border-white/10' : 'border-[#ddd6fe]'].join(' ')}>
|
||||
<div className="flex min-w-0 items-start gap-2">
|
||||
<FileCode2 size={14} className="mt-0.5 shrink-0 text-[#8b5cf6]" />
|
||||
@@ -27,10 +25,7 @@ export function AgentCodeReferences({ references, states, dark, busy, onOpen, on
|
||||
</div>
|
||||
{state?.error && <p role="alert" className="mt-2 break-words text-[11px] text-[#ef4444]">{t(state.error)}</p>}
|
||||
<div className="mt-2 flex min-w-0 items-center justify-end gap-2">
|
||||
{cancelled ? <span className={dark ? 'text-white/40' : 'text-[#64748b]'}>{t('취소됨')}</span> : <>
|
||||
{state?.status !== 'opened' && <button type="button" disabled={busy || opening || !onCancel} onClick={() => onCancel?.(reference.id)} className={['inline-flex h-8 shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-md border px-3 text-[11px] font-bold disabled:cursor-not-allowed disabled:opacity-40', dark ? 'border-white/15 hover:bg-white/8' : 'border-[#d8dee9] hover:bg-[#e2e8f0]'].join(' ')}><X size={13} />{t('취소')}</button>}
|
||||
<button type="button" disabled={busy || opening || !onOpen} onClick={() => onOpen?.(reference.id)} className="inline-flex h-8 min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md bg-[#7c3aed] px-3 text-[11px] font-bold text-white hover:bg-[#6d28d9] disabled:cursor-not-allowed disabled:opacity-40">{opening ? <Loader2 size={13} className="shrink-0 animate-spin" /> : <FileCode2 size={13} className="shrink-0" />}<span>{opening ? t('파일 여는 중...') : t('코드에서 보기')}</span></button>
|
||||
</>}
|
||||
<button type="button" disabled={busy || opening || !onOpen} onClick={() => onOpen?.(reference.id)} className="inline-flex h-8 min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md bg-[#7c3aed] px-3 text-[11px] font-bold leading-none text-white hover:bg-[#6d28d9] disabled:cursor-not-allowed disabled:opacity-40">{opening ? <Loader2 size={13} className="shrink-0 animate-spin" /> : <FileCode2 size={13} className="shrink-0" />}<span className="relative top-px leading-none">{opening ? t('파일 여는 중...') : t('코드에서 보기')}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function ModuleListView({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="thin-x-scrollbar -mt-2 flex min-h-0 flex-1 overflow-x-auto overflow-y-hidden">
|
||||
<div className="module-content-scrollbar -mt-2 flex min-h-0 flex-1 overflow-x-auto overflow-y-hidden">
|
||||
<div className={['flex h-full min-w-[760px] flex-1 flex-col overflow-hidden border-b', dark ? 'border-white/10 bg-[#242424]' : 'border-[#d8dee9] bg-white'].join(' ')}>
|
||||
<ModuleListToolbar
|
||||
dark={dark}
|
||||
|
||||
@@ -71,7 +71,6 @@ type WorkspaceQaPanelProps = {
|
||||
onRunAgentCommand: (messageId: string, command: AgentCommand) => void
|
||||
onRejectAgentCommand: (messageId: string, command: AgentCommand) => void
|
||||
onOpenAgentCodeReference?: (messageId: string, referenceId: string) => void
|
||||
onCancelAgentCodeReference?: (messageId: string, referenceId: string) => void
|
||||
}
|
||||
|
||||
function MessageCopyButton({ content, dark }: { content: string; dark: boolean }): React.JSX.Element {
|
||||
@@ -146,7 +145,6 @@ export default function WorkspaceQaPanel({
|
||||
onApplyAgentPlan,
|
||||
onCancelAgentPlan,
|
||||
onOpenAgentCodeReference,
|
||||
onCancelAgentCodeReference,
|
||||
onOpenAgentChange,
|
||||
onRunAgentCommand,
|
||||
onRejectAgentCommand
|
||||
@@ -325,7 +323,6 @@ export default function WorkspaceQaPanel({
|
||||
onApply={onApplyAgentPlan}
|
||||
onCancel={onCancelAgentPlan}
|
||||
onOpenCodeReference={onOpenAgentCodeReference}
|
||||
onCancelCodeReference={onCancelAgentCodeReference}
|
||||
onOpenChange={onOpenAgentChange}
|
||||
onRunCommand={onRunAgentCommand}
|
||||
onRejectCommand={onRejectAgentCommand}
|
||||
@@ -388,8 +385,7 @@ function AgentMessages({
|
||||
onOpenChange,
|
||||
onRunCommand,
|
||||
onRejectCommand,
|
||||
onOpenCodeReference,
|
||||
onCancelCodeReference
|
||||
onOpenCodeReference
|
||||
}: {
|
||||
dark: boolean
|
||||
enabled: boolean
|
||||
@@ -406,7 +402,6 @@ function AgentMessages({
|
||||
onRunCommand: (messageId: string, command: AgentCommand) => void
|
||||
onRejectCommand: (messageId: string, command: AgentCommand) => void
|
||||
onOpenCodeReference?: (messageId: string, referenceId: string) => void
|
||||
onCancelCodeReference?: (messageId: string, referenceId: string) => void
|
||||
}): React.JSX.Element {
|
||||
useLocaleRender()
|
||||
if (statusLoading) {
|
||||
@@ -452,7 +447,7 @@ function AgentMessages({
|
||||
<ProgressiveChatResponse message={message} dark={dark} collapseOnSignal={index < messages.length - 1} collapseSignal={messages.length} footerAction={<MessageCopyButton content={agentMessageCopyText(message)} dark={dark} />}>
|
||||
{message.plan && (
|
||||
<>
|
||||
{!!message.plan.codeReferences?.length && <AgentCodeReferences references={message.plan.codeReferences} states={message.codeReferenceStates} dark={dark} busy={busy} onOpen={onOpenCodeReference ? id => onOpenCodeReference(message.id, id) : undefined} onCancel={onCancelCodeReference ? id => onCancelCodeReference(message.id, id) : undefined} />}
|
||||
{!!message.plan.codeReferences?.length && <AgentCodeReferences references={message.plan.codeReferences} states={message.codeReferenceStates} dark={dark} busy={busy} onOpen={onOpenCodeReference ? id => onOpenCodeReference(message.id, id) : undefined} />}
|
||||
{!!message.plan.evidence?.length && (
|
||||
<details className={['border-t pt-2 text-[11px]', dark ? 'border-white/10 text-white/55' : 'border-[#ddd6fe] text-[#64748b]'].join(' ')}>
|
||||
<summary className="cursor-pointer select-none">{t("조회 근거")} · {message.plan.evidence.length}</summary>
|
||||
|
||||
@@ -459,17 +459,11 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
|
||||
: message))
|
||||
}
|
||||
|
||||
const cancelCodeReference = (messageId: string, referenceId: string): void => {
|
||||
updateMessages(current => current.map(message => message.id === messageId && message.plan?.codeReferences?.some(reference => reference.id === referenceId) && message.codeReferenceStates?.[referenceId]?.status !== 'opening'
|
||||
? { ...message, codeReferenceStates: { ...message.codeReferenceStates, [referenceId]: { status: 'cancelled' } } }
|
||||
: message))
|
||||
}
|
||||
|
||||
const openCodeReference = async (messageId: string, referenceId: string, open: (reference: AgentCodeReference, isCurrent: () => boolean) => Promise<void>): Promise<void> => {
|
||||
if (navigatingRef.current || applyingRef.current || abortControllerRef.current || messageStateRef.current.session !== session) return
|
||||
const message = messageStateRef.current.messages.find(message => message.id === messageId)
|
||||
const reference = message?.plan?.codeReferences?.find(reference => reference.id === referenceId)
|
||||
if (!reference || message?.codeReferenceStates?.[referenceId]?.status === 'cancelled') return
|
||||
if (!reference) return
|
||||
const latestMessageId = messageStateRef.current.messages.at(-1)?.id
|
||||
const isCurrent = (): boolean => messageStateRef.current.session === session && messageStateRef.current.messages.some(message => message.id === messageId)
|
||||
const update = (state: AgentCodeReferenceState): void => {
|
||||
@@ -522,7 +516,6 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
|
||||
cancelPlan,
|
||||
updateCommandExecution,
|
||||
openCodeReference,
|
||||
cancelCodeReference,
|
||||
stop,
|
||||
clear
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export function getEditorLanguage(file: string): string {
|
||||
if (file.endsWith('.tsx') || file.endsWith('.ts')) return 'typescript'
|
||||
if (/\.(c|cc|cpp|cxx|h|hh|hpp|hxx)$/i.test(file)) return 'cpp'
|
||||
if (file.endsWith('.json')) return 'json'
|
||||
if (file.endsWith('.xml') || file.endsWith('.urdf') || file.endsWith('.xacro')) return 'xml'
|
||||
if (/\.(xml|urdf|xacro|sdf|world)$/i.test(file) || /(^|\/)model\.config$/i.test(file)) return 'xml'
|
||||
if (file.endsWith('.html')) return 'html'
|
||||
if (file.endsWith('.css')) return 'css'
|
||||
if (file.endsWith('.py')) return 'python'
|
||||
|
||||
@@ -294,4 +294,12 @@
|
||||
background: rgba(148, 163, 184, 0.95);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.module-content-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.65);
|
||||
}
|
||||
|
||||
.module-content-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(148, 163, 184, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user