diff --git a/packages/ui/src/api/client.ts b/packages/ui/src/api/client.ts index 5a45cff..81aefdb 100644 --- a/packages/ui/src/api/client.ts +++ b/packages/ui/src/api/client.ts @@ -151,6 +151,8 @@ export type { // System Commands types SystemCommandInfo, SystemCommandListResponse, + // Active file types + ActiveFileInfo, } from './types.js'; // API Configuration diff --git a/packages/ui/src/api/types.ts b/packages/ui/src/api/types.ts index 3466a30..630be79 100644 --- a/packages/ui/src/api/types.ts +++ b/packages/ui/src/api/types.ts @@ -1144,3 +1144,17 @@ export interface AllFilesDiagnosticsResponse { /** 诊断响应(联合类型) */ export type DiagnosticsResponse = SingleFileDiagnosticsResponse | AllFilesDiagnosticsResponse; +// ============ 编辑器活动文件相关 ============ + +/** 当前编辑器活动文件信息 */ +export interface ActiveFileInfo { + /** 文件路径 */ + path: string; + /** 文件名 */ + name: string; + /** 文件内容 */ + content: string; + /** 语言类型 */ + language: string; +} + diff --git a/packages/ui/src/components/ChatInput.tsx b/packages/ui/src/components/ChatInput.tsx index c55ec0d..8d44db8 100644 --- a/packages/ui/src/components/ChatInput.tsx +++ b/packages/ui/src/components/ChatInput.tsx @@ -8,7 +8,7 @@ */ import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; -import { Square, Sparkles } from 'lucide-react'; +import { Square, Sparkles, FileCode, Link } from 'lucide-react'; import { motion } from 'framer-motion'; import clsx from 'clsx'; import { CommandMenu, type CommandMenuItem } from './CommandMenu.js'; @@ -19,6 +19,7 @@ import { AgentModeSelector, type AgentModeType } from './AgentModeSelector.js'; import { useCommands } from '../hooks/useCommands.js'; import { useSystemCommands } from '../hooks/useSystemCommands.js'; import { useFileMention } from '../hooks/useFileMention.js'; +import type { ActiveFileInfo } from '../api/types.js'; interface ChatInputProps { onSend: (content: string) => void; @@ -39,6 +40,12 @@ interface ChatInputProps { autoApprove?: boolean; /** 自动授权变更回调 */ onAutoApproveChange?: (enabled: boolean) => void; + /** 当前编辑器活动文件 */ + activeFile?: ActiveFileInfo | null; + /** 是否自动附加当前编辑器文件 */ + autoAttachActiveFile?: boolean; + /** 自动附加开关变更回调 */ + onAutoAttachActiveFileToggle?: (enabled: boolean) => void; } export function ChatInput({ @@ -53,6 +60,9 @@ export function ChatInput({ onAgentModeChange, autoApprove = false, onAutoApproveChange, + activeFile, + autoAttachActiveFile = true, + onAutoAttachActiveFileToggle, }: ChatInputProps) { const [input, setInput] = useState(''); const [showCommandMenu, setShowCommandMenu] = useState(false); @@ -249,7 +259,17 @@ export function ChatInput({ setShowSystemCommandMenu(false); closeFileMenu(); - onSend(trimmed); + // 构建最终消息内容 + let messageContent = trimmed; + + // 自动附加当前编辑器文件(如果启用且文件未在 @提及中) + // 排除 / 斜杠命令和 : 系统命令 + const isCommand = trimmed.startsWith('/') || trimmed.startsWith(':'); + if (!isCommand && autoAttachActiveFile && activeFile && !mentionedFiles.includes(activeFile.path)) { + messageContent = `@${activeFile.path} ${messageContent}`; + } + + onSend(messageContent); setInput(''); // 重置高度 @@ -370,9 +390,47 @@ export function ChatInput({ : 'border-line hover:border-fg-subtle/30 focus-within:border-primary-500 focus-within:shadow-lg focus-within:shadow-primary-500/10' )} > - {/* 已选文件标签 */} - {mentionedFiles.length > 0 && ( -
+ {/* 文件标签区域:活动文件 + 已选文件 */} + {(activeFile || mentionedFiles.length > 0) && ( +
+ {/* 当前编辑器活动文件 */} + {activeFile && !mentionedFiles.includes(activeFile.path) && ( +
+ {/* 自动附加开关 */} + {onAutoAttachActiveFileToggle && ( + + )} + {/* 活动文件标签 */} +
+ + {activeFile.name} + {autoAttachActiveFile && ( + (auto) + )} +
+
+ )} + {/* @提及的文件 */} {mentionedFiles.map((file, index) => ( void; } -export function IDE({ className, sidebarWidth = 256 }: IDEProps) { +export function IDE({ className, sidebarWidth = 256, onActiveFileChange }: IDEProps) { const [tabs, setTabs] = useState([]); const [activeTabId, setActiveTabId] = useState(null); const [workingDirectory, setWorkingDirectory] = useState(''); @@ -134,6 +137,23 @@ export function IDE({ className, sidebarWidth = 256 }: IDEProps) { } }, [activeTabId, tabs]); + // 通知父组件活动文件变化 + useEffect(() => { + if (!onActiveFileChange) return; + + const activeTab = tabs.find((tab) => tab.id === activeTabId); + if (activeTab) { + onActiveFileChange({ + path: activeTab.path, + name: activeTab.name, + content: activeTab.content, + language: activeTab.language, + }); + } else { + onActiveFileChange(null); + } + }, [activeTabId, tabs, onActiveFileChange]); + // 打开文件 const handleFileSelect = useCallback(async (path: string, name: string) => { // 检查是否已打开 diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 4164fc3..210968b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -199,6 +199,8 @@ export type { SingleFileDiagnosticsResponse, AllFilesDiagnosticsResponse, DiagnosticsResponse, + // Active file types + ActiveFileInfo, } from './api/client.js'; // Primitives (shadcn/ui style) diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index a5b09c3..b1a269d 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -23,6 +23,7 @@ import { listSessions, createSession, type Session, + type ActiveFileInfo, } from '@ai-assistant/ui'; import { ChatPage } from './pages/Chat'; @@ -45,6 +46,18 @@ export function App() { return saved ? parseFloat(saved) : 70; }); + // 编辑器联动状态 + const [activeFile, setActiveFile] = useState(null); + const [autoAttachActiveFile, setAutoAttachActiveFile] = useState(() => { + const saved = localStorage.getItem('ai-assistant-auto-attach-file'); + return saved !== 'false'; // 默认开启 + }); + + // 持久化自动附加开关状态 + useEffect(() => { + localStorage.setItem('ai-assistant-auto-attach-file', String(autoAttachActiveFile)); + }, [autoAttachActiveFile]); + // 初始化:加载会话 useEffect(() => { const HAS_SESSIONS_KEY = 'ai-assistant-has-sessions'; @@ -146,7 +159,7 @@ export function App() { className="hidden md:flex flex-col" style={{ width: `${idePanelWidth}%` }} > - +
{/* 可拖拽分割线 */} @@ -175,6 +188,9 @@ export function App() { onOpenLSP={() => setShowLSP(true)} onOpenDiagnostics={() => setShowDiagnostics(true)} onOpenSessions={() => setShowSessions(true)} + activeFile={activeFile} + autoAttachActiveFile={autoAttachActiveFile} + onAutoAttachActiveFileToggle={setAutoAttachActiveFile} /> ) : (
diff --git a/packages/web/src/pages/Chat.tsx b/packages/web/src/pages/Chat.tsx index 06b7fa2..30dfaea 100644 --- a/packages/web/src/pages/Chat.tsx +++ b/packages/web/src/pages/Chat.tsx @@ -16,6 +16,7 @@ import { SubagentProgress, DiagnosticsIndicator, ToolbarOverflowMenu, + type ActiveFileInfo, } from '@ai-assistant/ui'; interface ChatPageProps { @@ -35,6 +36,13 @@ interface ChatPageProps { onOpenLSP?: () => void; onOpenDiagnostics?: () => void; onOpenSessions?: () => void; + // 编辑器联动 + /** 当前编辑器活动文件 */ + activeFile?: ActiveFileInfo | null; + /** 是否自动附加当前编辑器文件 */ + autoAttachActiveFile?: boolean; + /** 自动附加开关变更回调 */ + onAutoAttachActiveFileToggle?: (enabled: boolean) => void; } export function ChatPage({ @@ -52,6 +60,9 @@ export function ChatPage({ onOpenLSP, onOpenDiagnostics, onOpenSessions, + activeFile, + autoAttachActiveFile, + onAutoAttachActiveFileToggle, }: ChatPageProps) { const { messages, @@ -228,6 +239,9 @@ export function ChatPage({ onAgentModeChange={setAgentMode} autoApprove={autoApprove} onAutoApproveChange={setAutoApprove} + activeFile={activeFile} + autoAttachActiveFile={autoAttachActiveFile} + onAutoAttachActiveFileToggle={onAutoAttachActiveFileToggle} /> {/* Permission Dialog */}