Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/web-ui/src/flow_chat/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import React, { useRef, useCallback, useEffect, useReducer, useState, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { ArrowUp, Image, ChevronsUp, ChevronsDown, RotateCcw, Plus, X, Sparkles, Loader2, ChevronRight } from 'lucide-react';
import { ArrowUp, Image, ChevronsUp, ChevronsDown, RotateCcw, Plus, X, Sparkles, Loader2, ChevronRight, Files } from 'lucide-react';
import { ContextDropZone, useContextStore } from '../../shared/context-system';
import { useActiveSessionState } from '../hooks/useActiveSessionState';
import { RichTextInput, type MentionState } from './RichTextInput';
Expand Down Expand Up @@ -1297,6 +1297,20 @@ export const ChatInput: React.FC<ChatInputProps> = ({
[handleImageInput]
);

const handleBoostOpenAtContext = useCallback((e: React.SyntheticEvent) => {
e.stopPropagation();
dispatchMode({ type: 'CLOSE_DROPDOWN' });
dispatchInput({ type: 'ACTIVATE' });
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
const el = richTextInputRef.current;
if (el && typeof (el as unknown as { openMention?: () => void }).openMention === 'function') {
(el as unknown as { openMention: () => void }).openMention();
}
});
});
}, []);

const handleOpenSkillsLibrary = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
Expand Down Expand Up @@ -1771,6 +1785,17 @@ export const ChatInput: React.FC<ChatInputProps> = ({
)}

<div className="bitfun-chat-input__boost-section">
<div
role="button"
tabIndex={0}
className="bitfun-chat-input__boost-context-row"
onClick={handleBoostOpenAtContext}
onKeyDown={e => e.key === 'Enter' && handleBoostOpenAtContext(e)}
>
<Files size={14} className="bitfun-chat-input__boost-context-icon" aria-hidden />
<span>{t('chatInput.boostAddContext')}</span>
</div>

<div
role="button"
tabIndex={0}
Expand Down
28 changes: 27 additions & 1 deletion src/web-ui/src/flow_chat/components/RichTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -557,19 +557,45 @@ export const RichTextInput = React.forwardRef<HTMLDivElement, RichTextInputProps
onMentionStateChange?.({ isActive: false, query: '', startOffset: 0 });
}, [createTagElement, getRangeByTextOffsets, handleInput, insertTagAtCursor, onMentionStateChange]);

/** Insert @ at caret and open the file/folder mention picker (e.g. from ChatInput + menu). */
const openMention = useCallback(() => {
const editor = internalRef.current;
if (!editor) return;

editor.focus();
const sel = window.getSelection();
let range: Range | null = null;
if (sel && sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
if (!range || !editor.contains(range.commonAncestorContainer)) {
range = document.createRange();
range.selectNodeContents(editor);
range.collapse(false);
sel?.removeAllRanges();
sel?.addRange(range);
}

document.execCommand('insertText', false, '@');
requestAnimationFrame(() => {
detectMention();
});
}, [detectMention, internalRef]);

// Expose methods to parent
useEffect(() => {
if (internalRef.current) {
(internalRef.current as any).insertTag = insertTagAtCursor;
(internalRef.current as any).insertTagReplacingMention = insertTagReplacingMention;
(internalRef.current as any).openMention = openMention;
(internalRef.current as any).closeMention = () => {
if (mentionStateRef.current.isActive) {
mentionStateRef.current = { isActive: false, query: '', startOffset: 0 };
onMentionStateChange?.({ isActive: false, query: '', startOffset: 0 });
}
};
}
}, [insertTagAtCursor, insertTagReplacingMention, onMentionStateChange, internalRef]);
}, [insertTagAtCursor, insertTagReplacingMention, openMention, onMentionStateChange, internalRef]);

// Initialize and sync value changes from external sources.
// Skip syncing when the change originated from local user input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export const ToolbarModeProvider: React.FC<ToolbarModeProviderProps> = ({ childr

// Update state first so React renders the toolbar UI.
setIsToolbarMode(true);
setIsExpanded(false); // Enter compact mode by default.
setIsExpanded(true); // Enter expanded FlowChat by default.

if (isMaximized) {
await win.unmaximize();
Expand All @@ -156,15 +156,15 @@ export const ToolbarModeProvider: React.FC<ToolbarModeProviderProps> = ({ childr
const margin = Math.round(20 * scaleFactor);
const taskbarHeight = Math.round(50 * scaleFactor); // Estimated taskbar height.

x = monitor.size.width - TOOLBAR_COMPACT_SIZE.width - margin;
y = monitor.size.height - TOOLBAR_COMPACT_SIZE.height - margin - taskbarHeight;
x = monitor.size.width - TOOLBAR_EXPANDED_SIZE.width - margin;
y = monitor.size.height - TOOLBAR_EXPANDED_SIZE.height - margin - taskbarHeight;
}

// Apply window props after toolbar UI renders.
// macOS: avoid decorations toggles to preserve overlay title bar behavior.
const toolbarWindowOps: Array<Promise<unknown>> = [
win.setAlwaysOnTop(true),
win.setSize(new PhysicalSize(TOOLBAR_COMPACT_SIZE.width, TOOLBAR_COMPACT_SIZE.height)),
win.setSize(new PhysicalSize(TOOLBAR_EXPANDED_SIZE.width, TOOLBAR_EXPANDED_SIZE.height)),
win.setPosition(new PhysicalPosition(x, y)),
win.setResizable(true),
win.setSkipTaskbar(true),
Expand All @@ -179,7 +179,7 @@ export const ToolbarModeProvider: React.FC<ToolbarModeProviderProps> = ({ childr
}
await Promise.all(toolbarWindowOps);

await win.setMinSize(new PhysicalSize(TOOLBAR_COMPACT_MIN.width, TOOLBAR_COMPACT_MIN.height));
await win.setMinSize(new PhysicalSize(TOOLBAR_EXPANDED_MIN.width, TOOLBAR_EXPANDED_MIN.height));

} catch (error) {
log.error('Failed to enable toolbar mode', error);
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/src/locales/en-US/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
"addModeTooltip": "Add Plan or Debug",
"boostSectionAgent": "Agent",
"boostSectionContext": "Context",
"boostAddContext": "Reference file or folder",
"boostSkills": "Skills",
"boostSkillsLoading": "Loading skills…",
"boostSkillsEmpty": "No enabled skills",
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/src/locales/zh-CN/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
"addModeTooltip": "附加 Plan 或 Debug",
"boostSectionAgent": "智能体",
"boostSectionContext": "上下文",
"boostAddContext": "引用文件或文件夹",
"boostSkills": "Skills",
"boostSkillsLoading": "正在加载 Skills…",
"boostSkillsEmpty": "暂无已启用的 Skill",
Expand Down
Loading