mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
feat(vscode-ide-companion): 增强工具调用与输入表单组件功能
- 新增 InProgressToolCall 组件用于展示进行中的工具调用状态 - 重构 InputForm 为独立组件,提升代码可维护性 - 改进 tool_call_update 处理逻辑,支持创建缺失的初始工具调用 - 添加思考块(thought chunk)日志以便调试 AI 思维过程 - 更新样式以支持新的进行中工具调用卡片显示 - 在权限请求时自动创建对应的工具调用记录 ```
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
import { PermissionDrawer } from './components/PermissionDrawer.js';
|
||||
import { ToolCall, type ToolCallData } from './components/ToolCall.js';
|
||||
import { hasToolCallOutput } from './components/toolcalls/shared/utils.js';
|
||||
import { InProgressToolCall } from './components/InProgressToolCall.js';
|
||||
import { EmptyState } from './components/EmptyState.js';
|
||||
import { PlanDisplay, type PlanEntry } from './components/PlanDisplay.js';
|
||||
import {
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
StreamingMessage,
|
||||
WaitingMessage,
|
||||
} from './components/messages/index.js';
|
||||
import { InputForm } from './components/InputForm.js';
|
||||
|
||||
interface ToolCallUpdate {
|
||||
type: 'tool_call' | 'tool_call_update';
|
||||
@@ -612,8 +614,8 @@ export const App: React.FC = () => {
|
||||
content,
|
||||
locations: update.locations,
|
||||
});
|
||||
} else if (update.type === 'tool_call_update' && existing) {
|
||||
// Update existing tool call
|
||||
} else if (update.type === 'tool_call_update') {
|
||||
// Update existing tool call, or create if it doesn't exist
|
||||
const updatedContent = update.content
|
||||
? update.content.map((item) => ({
|
||||
type: item.type as 'content' | 'diff',
|
||||
@@ -624,14 +626,28 @@ export const App: React.FC = () => {
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
newMap.set(update.toolCallId, {
|
||||
...existing,
|
||||
...(update.kind && { kind: update.kind }),
|
||||
...(update.title && { title: safeTitle(update.title) }),
|
||||
...(update.status && { status: update.status }),
|
||||
...(updatedContent && { content: updatedContent }),
|
||||
...(update.locations && { locations: update.locations }),
|
||||
});
|
||||
if (existing) {
|
||||
// Update existing tool call
|
||||
newMap.set(update.toolCallId, {
|
||||
...existing,
|
||||
...(update.kind && { kind: update.kind }),
|
||||
...(update.title && { title: safeTitle(update.title) }),
|
||||
...(update.status && { status: update.status }),
|
||||
...(updatedContent && { content: updatedContent }),
|
||||
...(update.locations && { locations: update.locations }),
|
||||
});
|
||||
} else {
|
||||
// Create new tool call if it doesn't exist (missed the initial tool_call message)
|
||||
newMap.set(update.toolCallId, {
|
||||
toolCallId: update.toolCallId,
|
||||
kind: update.kind || 'other',
|
||||
title: safeTitle(update.title),
|
||||
status: update.status || 'pending',
|
||||
rawInput: update.rawInput as string | object | undefined,
|
||||
content: updatedContent,
|
||||
locations: update.locations,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newMap;
|
||||
@@ -717,12 +733,14 @@ export const App: React.FC = () => {
|
||||
|
||||
case 'thoughtChunk': {
|
||||
const chunkData = message.data;
|
||||
console.log('[App] 🧠 THOUGHT CHUNK RECEIVED:', chunkData);
|
||||
// Handle thought chunks for AI thinking display
|
||||
const thinkingMessage: TextMessage = {
|
||||
role: 'thinking',
|
||||
content: chunkData.content || chunkData.chunk || '',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
console.log('[App] 🧠 Adding thinking message:', thinkingMessage);
|
||||
setMessages((prev) => [...prev, thinkingMessage]);
|
||||
break;
|
||||
}
|
||||
@@ -760,10 +778,58 @@ export const App: React.FC = () => {
|
||||
// console.log('[App] Set notLoggedInMessage to:', (message.data as { message: string })?.message);
|
||||
// break;
|
||||
|
||||
case 'permissionRequest':
|
||||
case 'permissionRequest': {
|
||||
// Show permission dialog
|
||||
handlePermissionRequest(message.data);
|
||||
|
||||
// Also create a tool call entry for the permission request
|
||||
// This ensures that if it's rejected, we can show it properly
|
||||
const permToolCall = message.data?.toolCall as {
|
||||
toolCallId?: string;
|
||||
kind?: string;
|
||||
title?: string;
|
||||
status?: string;
|
||||
content?: unknown[];
|
||||
locations?: Array<{ path: string; line?: number | null }>;
|
||||
};
|
||||
|
||||
if (permToolCall?.toolCallId) {
|
||||
// Infer kind from title if not provided
|
||||
let kind = permToolCall.kind || 'execute';
|
||||
if (permToolCall.title) {
|
||||
const title = permToolCall.title.toLowerCase();
|
||||
if (title.includes('touch') || title.includes('echo')) {
|
||||
kind = 'execute';
|
||||
} else if (title.includes('read') || title.includes('cat')) {
|
||||
kind = 'read';
|
||||
} else if (title.includes('write') || title.includes('edit')) {
|
||||
kind = 'edit';
|
||||
}
|
||||
}
|
||||
|
||||
handleToolCallUpdate({
|
||||
type: 'tool_call',
|
||||
toolCallId: permToolCall.toolCallId,
|
||||
kind,
|
||||
title: permToolCall.title,
|
||||
status: permToolCall.status || 'pending',
|
||||
content: permToolCall.content as Array<{
|
||||
type: 'content' | 'diff';
|
||||
content?: {
|
||||
type: string;
|
||||
text?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
path?: string;
|
||||
oldText?: string | null;
|
||||
newText?: string;
|
||||
[key: string]: unknown;
|
||||
}>,
|
||||
locations: permToolCall.locations,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'plan':
|
||||
// Update plan entries
|
||||
@@ -972,67 +1038,6 @@ export const App: React.FC = () => {
|
||||
setThinkingEnabled((prev) => !prev);
|
||||
};
|
||||
|
||||
// Get edit mode display info
|
||||
const getEditModeInfo = () => {
|
||||
switch (editMode) {
|
||||
case 'ask':
|
||||
return {
|
||||
text: 'Ask before edits',
|
||||
title: 'Qwen will ask before each edit. Click to switch modes.',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
case 'auto':
|
||||
return {
|
||||
text: 'Edit automatically',
|
||||
title: 'Qwen will edit files automatically. Click to switch modes.',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.53 3.956A1 1 0 0 0 1 4.804v6.392a1 1 0 0 0 1.53.848l5.113-3.196c.16-.1.279-.233.357-.383v2.73a1 1 0 0 0 1.53.849l5.113-3.196a1 1 0 0 0 0-1.696L9.53 3.956A1 1 0 0 0 8 4.804v2.731a.992.992 0 0 0-.357-.383L2.53 3.956Z"></path>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
case 'plan':
|
||||
return {
|
||||
text: 'Plan mode',
|
||||
title: 'Qwen will plan before executing. Click to switch modes.',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4.5 2a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-11a.5.5 0 0 0-.5-.5h-1ZM10.5 2a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-11a.5.5 0 0 0-.5-.5h-1Z"></path>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: 'Unknown mode',
|
||||
title: 'Unknown edit mode',
|
||||
icon: null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1417,9 +1422,28 @@ export const App: React.FC = () => {
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Tool Calls - only show those with actual output */}
|
||||
{/* In-Progress Tool Calls - show only pending/in_progress */}
|
||||
{Array.from(toolCalls.values())
|
||||
.filter((toolCall) => hasToolCallOutput(toolCall))
|
||||
.filter(
|
||||
(toolCall) =>
|
||||
toolCall.status === 'pending' ||
|
||||
toolCall.status === 'in_progress',
|
||||
)
|
||||
.map((toolCall) => (
|
||||
<InProgressToolCall
|
||||
key={toolCall.toolCallId}
|
||||
toolCall={toolCall}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Completed Tool Calls - only show those with actual output */}
|
||||
{Array.from(toolCalls.values())
|
||||
.filter(
|
||||
(toolCall) =>
|
||||
(toolCall.status === 'completed' ||
|
||||
toolCall.status === 'failed') &&
|
||||
hasToolCallOutput(toolCall),
|
||||
)
|
||||
.map((toolCall) => (
|
||||
<ToolCall key={toolCall.toolCallId} toolCall={toolCall} />
|
||||
))}
|
||||
@@ -1477,223 +1501,65 @@ export const App: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="input-form-container">
|
||||
<div className="input-form-wrapper">
|
||||
{/* Context Pills - Removed: now using inline @mentions in input */}
|
||||
<InputForm
|
||||
inputText={inputText}
|
||||
inputFieldRef={inputFieldRef}
|
||||
isStreaming={isStreaming}
|
||||
isComposing={isComposing}
|
||||
editMode={editMode}
|
||||
thinkingEnabled={thinkingEnabled}
|
||||
activeFileName={activeFileName}
|
||||
activeSelection={activeSelection}
|
||||
onInputChange={setInputText}
|
||||
onCompositionStart={() => setIsComposing(true)}
|
||||
onCompositionEnd={() => setIsComposing(false)}
|
||||
onKeyDown={() => {}}
|
||||
onSubmit={handleSubmit}
|
||||
onToggleEditMode={handleToggleEditMode}
|
||||
onToggleThinking={handleToggleThinking}
|
||||
onFocusActiveEditor={() => {
|
||||
vscode.postMessage({
|
||||
type: 'focusActiveEditor',
|
||||
data: {},
|
||||
});
|
||||
}}
|
||||
onShowCommandMenu={async () => {
|
||||
if (inputFieldRef.current) {
|
||||
inputFieldRef.current.focus();
|
||||
|
||||
<form className="input-form" onSubmit={handleSubmit}>
|
||||
<div className="input-form-background"></div>
|
||||
<div className="input-banner"></div>
|
||||
<div className="input-wrapper">
|
||||
<div
|
||||
ref={inputFieldRef}
|
||||
contentEditable="plaintext-only"
|
||||
className="input-field-editable"
|
||||
role="textbox"
|
||||
aria-label="Message input"
|
||||
aria-multiline="true"
|
||||
data-placeholder="Ask Qwen Code …"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
setInputText(target.textContent || '');
|
||||
}}
|
||||
onCompositionStart={() => {
|
||||
setIsComposing(true);
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
setIsComposing(false);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// 如果正在进行中文输入法输入(拼音输入),不处理回车键
|
||||
if (e.key === 'Enter' && !e.shiftKey && !isComposing) {
|
||||
// 如果 CompletionMenu 打开,让它处理 Enter 键(选中文件)
|
||||
if (completion.isOpen) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
}
|
||||
}}
|
||||
suppressContentEditableWarning
|
||||
/>
|
||||
</div>
|
||||
<div className="input-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="action-button edit-mode-button"
|
||||
title={getEditModeInfo().title}
|
||||
onClick={handleToggleEditMode}
|
||||
>
|
||||
{getEditModeInfo().icon}
|
||||
<span>{getEditModeInfo().text}</span>
|
||||
</button>
|
||||
{activeFileName && (
|
||||
<button
|
||||
type="button"
|
||||
className="action-button active-file-indicator"
|
||||
title={`Showing Qwen Code your current file selection: ${activeFileName}${activeSelection ? `#${activeSelection.startLine}-${activeSelection.endLine}` : ''}`}
|
||||
onClick={() => {
|
||||
// Request to focus/reveal the active file
|
||||
vscode.postMessage({
|
||||
type: 'focusActiveEditor',
|
||||
data: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
data-slot="icon"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Zm7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06ZM11.377 2.011a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
{activeFileName}
|
||||
{activeSelection &&
|
||||
` #${activeSelection.startLine}${activeSelection.startLine !== activeSelection.endLine ? `-${activeSelection.endLine}` : ''}`}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="action-divider"></div>
|
||||
{/* Spacer 将右侧按钮推到右边 */}
|
||||
<div className="input-actions-spacer"></div>
|
||||
<button
|
||||
type="button"
|
||||
className={`action-icon-button thinking-button ${thinkingEnabled ? 'active' : ''}`}
|
||||
title={thinkingEnabled ? 'Thinking on' : 'Thinking off'}
|
||||
onClick={handleToggleThinking}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.00293 1.11523L8.35059 1.12402H8.35352C11.9915 1.30834 14.8848 4.31624 14.8848 8C14.8848 11.8025 11.8025 14.8848 8 14.8848C4.19752 14.8848 1.11523 11.8025 1.11523 8C1.11523 7.67691 1.37711 7.41504 1.7002 7.41504C2.02319 7.41514 2.28516 7.67698 2.28516 8C2.28516 11.1563 4.84369 13.7148 8 13.7148C11.1563 13.7148 13.7148 11.1563 13.7148 8C13.7148 4.94263 11.3141 2.4464 8.29492 2.29297V2.29199L7.99609 2.28516H7.9873V2.28418L7.89648 2.27539L7.88281 2.27441V2.27344C7.61596 2.21897 7.41513 1.98293 7.41504 1.7002C7.41504 1.37711 7.67691 1.11523 8 1.11523H8.00293ZM8 3.81543C8.32309 3.81543 8.58496 4.0773 8.58496 4.40039V7.6377L10.9619 8.82715C11.2505 8.97169 11.3678 9.32256 11.2236 9.61133C11.0972 9.86425 10.8117 9.98544 10.5488 9.91504L10.5352 9.91211V9.91016L10.4502 9.87891L10.4385 9.87402V9.87305L7.73828 8.52344C7.54007 8.42433 7.41504 8.22155 7.41504 8V4.40039C7.41504 4.0773 7.67691 3.81543 8 3.81543ZM2.44336 5.12695C2.77573 5.19517 3.02597 5.48929 3.02637 5.8418C3.02637 6.19456 2.7761 6.49022 2.44336 6.55859L2.2959 6.57324C1.89241 6.57324 1.56543 6.24529 1.56543 5.8418C1.56588 5.43853 1.89284 5.1123 2.2959 5.1123L2.44336 5.12695ZM3.46094 2.72949C3.86418 2.72984 4.19017 3.05712 4.19043 3.45996V3.46094C4.19009 3.86393 3.86392 4.19008 3.46094 4.19043H3.45996C3.05712 4.19017 2.72983 3.86419 2.72949 3.46094V3.45996C2.72976 3.05686 3.05686 2.72976 3.45996 2.72949H3.46094ZM5.98926 1.58008C6.32235 1.64818 6.57324 1.94276 6.57324 2.2959L6.55859 2.44336C6.49022 2.7761 6.19456 3.02637 5.8418 3.02637C5.43884 3.02591 5.11251 2.69895 5.1123 2.2959L5.12695 2.14844C5.19504 1.81591 5.48906 1.56583 5.8418 1.56543L5.98926 1.58008Z"
|
||||
strokeWidth="0.27"
|
||||
style={{
|
||||
stroke: 'var(--app-secondary-foreground)',
|
||||
fill: 'var(--app-secondary-foreground)',
|
||||
}}
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="action-icon-button command-button"
|
||||
title="Show command menu (/)"
|
||||
onClick={async () => {
|
||||
if (inputFieldRef.current) {
|
||||
// Focus the input first to ensure cursor is in the right place
|
||||
inputFieldRef.current.focus();
|
||||
const selection = window.getSelection();
|
||||
let position = { top: 0, left: 0 };
|
||||
|
||||
// Get cursor position for menu placement
|
||||
const selection = window.getSelection();
|
||||
let position = { top: 0, left: 0 };
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
try {
|
||||
const range = selection.getRangeAt(0);
|
||||
const rangeRect = range.getBoundingClientRect();
|
||||
if (rangeRect.top > 0 && rangeRect.left > 0) {
|
||||
position = {
|
||||
top: rangeRect.top,
|
||||
left: rangeRect.left,
|
||||
};
|
||||
} else {
|
||||
const inputRect =
|
||||
inputFieldRef.current.getBoundingClientRect();
|
||||
position = { top: inputRect.top, left: inputRect.left };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[App] Error getting cursor position:', error);
|
||||
const inputRect = inputFieldRef.current.getBoundingClientRect();
|
||||
position = { top: inputRect.top, left: inputRect.left };
|
||||
}
|
||||
} else {
|
||||
const inputRect = inputFieldRef.current.getBoundingClientRect();
|
||||
position = { top: inputRect.top, left: inputRect.left };
|
||||
}
|
||||
|
||||
// Try to get precise cursor position
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
try {
|
||||
const range = selection.getRangeAt(0);
|
||||
const rangeRect = range.getBoundingClientRect();
|
||||
if (rangeRect.top > 0 && rangeRect.left > 0) {
|
||||
position = {
|
||||
top: rangeRect.top,
|
||||
left: rangeRect.left,
|
||||
};
|
||||
} else {
|
||||
// Fallback to input element position
|
||||
const inputRect =
|
||||
inputFieldRef.current.getBoundingClientRect();
|
||||
position = {
|
||||
top: inputRect.top,
|
||||
left: inputRect.left,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[App] Error getting cursor position:',
|
||||
error,
|
||||
);
|
||||
const inputRect =
|
||||
inputFieldRef.current.getBoundingClientRect();
|
||||
position = { top: inputRect.top, left: inputRect.left };
|
||||
}
|
||||
} else {
|
||||
// No selection, use input element position
|
||||
const inputRect =
|
||||
inputFieldRef.current.getBoundingClientRect();
|
||||
position = { top: inputRect.top, left: inputRect.left };
|
||||
}
|
||||
|
||||
// Open completion menu with / commands
|
||||
await completion.openCompletion('/', '', position);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12.528 3.047a.75.75 0 0 1 .449.961L8.433 16.504a.75.75 0 1 1-1.41-.512l4.544-12.496a.75.75 0 0 1 .961-.449Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="action-icon-button attach-button"
|
||||
title="Attach context (Cmd/Ctrl + /)"
|
||||
onClick={handleAttachContextClick}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M15.621 4.379a3 3 0 0 0-4.242 0l-7 7a3 3 0 0 0 4.241 4.243h.001l.497-.5a.75.75 0 0 1 1.064 1.057l-.498.501-.002.002a4.5 4.5 0 0 1-6.364-6.364l7-7a4.5 4.5 0 0 1 6.368 6.36l-3.455 3.553A2.625 2.625 0 1 1 9.52 9.52l3.45-3.451a.75.75 0 1 1 1.061 1.06l-3.45 3.451a1.125 1.125 0 0 0 1.587 1.595l3.454-3.553a3 3 0 0 0 0-4.242Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="send-button-icon"
|
||||
disabled={isStreaming || !inputText.trim()}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 17a.75.75 0 0 1-.75-.75V5.612L5.29 9.77a.75.75 0 0 1-1.08-1.04l5.25-5.5a.75.75 0 0 1 1.08 0l5.25 5.5a.75.75 0 1 1-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0 1 10 17Z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
await completion.openCompletion('/', '', position);
|
||||
}
|
||||
}}
|
||||
onAttachContext={handleAttachContextClick}
|
||||
completionIsOpen={completion.isOpen}
|
||||
/>
|
||||
|
||||
{/* Save Session Dialog */}
|
||||
<SaveSessionDialog
|
||||
|
||||
Reference in New Issue
Block a user