mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(vscode-ide-companion): add specialized tool call components
- Added ExecuteNodeToolCall for specialized node/npm command execution - Added UpdatedPlanToolCall for enhanced plan visualization with checkboxes - Created ExecuteNode CSS styling - Refactored ReadToolCall to new directory structure - Updated tool call router to handle new component types - Enhanced LayoutComponents with className prop support - Added specialized handling for todo_write and updated_plan tool kinds These additions improve the visualization and handling of various tool call types in the UI.
This commit is contained in:
@@ -35,7 +35,12 @@ export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
// Error case
|
// Error case
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return (
|
return (
|
||||||
<ToolCallContainer label="Execute" status="error" toolCallId={toolCallId}>
|
<ToolCallContainer
|
||||||
|
label="Execute"
|
||||||
|
status="error"
|
||||||
|
toolCallId={toolCallId}
|
||||||
|
className="execute-default-toolcall"
|
||||||
|
>
|
||||||
{/* Branch connector summary (Claude-like) */}
|
{/* Branch connector summary (Claude-like) */}
|
||||||
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 mt-[2px] mb-[2px] flex-row items-start w-full gap-1">
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 mt-[2px] mb-[2px] flex-row items-start w-full gap-1">
|
||||||
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* ExecuteNode tool call styles
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Error content styling */
|
||||||
|
.execute-node-error-content {
|
||||||
|
color: #c74e39;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preformatted content */
|
||||||
|
.execute-node-pre {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--app-monospace-font-family);
|
||||||
|
font-size: 0.85em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error preformatted content */
|
||||||
|
.execute-node-error-pre {
|
||||||
|
color: #c74e39;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Output content styling */
|
||||||
|
.execute-node-output-content {
|
||||||
|
background-color: var(--app-code-background);
|
||||||
|
border: 0.5px solid var(--app-input-border);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 8px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* ExecuteNode tool call component - specialized for node/npm execution operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import type { BaseToolCallProps } from '../shared/types.js';
|
||||||
|
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||||
|
import { safeTitle, groupContent } from '../shared/utils.js';
|
||||||
|
import './ExecuteNode.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized component for ExecuteNode tool calls
|
||||||
|
* Shows: Execute bullet + description + branch connector
|
||||||
|
*/
|
||||||
|
export const ExecuteNodeToolCall: React.FC<BaseToolCallProps> = ({
|
||||||
|
toolCall,
|
||||||
|
}) => {
|
||||||
|
const { title, content, rawInput, toolCallId } = toolCall;
|
||||||
|
const commandText = safeTitle(title);
|
||||||
|
|
||||||
|
// Group content by type
|
||||||
|
const { textOutputs, errors } = groupContent(content);
|
||||||
|
|
||||||
|
// Extract command from rawInput if available
|
||||||
|
let _inputCommand = commandText;
|
||||||
|
if (rawInput && typeof rawInput === 'object') {
|
||||||
|
const inputObj = rawInput as { command?: string };
|
||||||
|
_inputCommand = inputObj.command || commandText;
|
||||||
|
} else if (typeof rawInput === 'string') {
|
||||||
|
_inputCommand = rawInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error case
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return (
|
||||||
|
<ToolCallContainer
|
||||||
|
label="Execute"
|
||||||
|
status="error"
|
||||||
|
toolCallId={toolCallId}
|
||||||
|
className="execute-toolcall"
|
||||||
|
>
|
||||||
|
{/* Branch connector summary (Claude-like) */}
|
||||||
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 mt-[2px] mb-[2px] flex-row items-start w-full gap-1">
|
||||||
|
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||||
|
<span className="flex-shrink-0 w-full">{commandText}</span>
|
||||||
|
</div>
|
||||||
|
{/* Error content */}
|
||||||
|
<div className="execute-node-error-content">
|
||||||
|
<pre className="execute-node-pre execute-node-error-pre">
|
||||||
|
{errors.join('\n')}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success case: show command with branch connector (similar to the example)
|
||||||
|
return (
|
||||||
|
<ToolCallContainer label="Execute" status="success" toolCallId={toolCallId}>
|
||||||
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 mt-[2px] mb-[2px] flex-row items-start w-full gap-1">
|
||||||
|
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||||
|
<span className="flex-shrink-0 w-full">{commandText}</span>
|
||||||
|
</div>
|
||||||
|
{textOutputs.length > 0 && (
|
||||||
|
<div className="execute-node-output-content">
|
||||||
|
<pre className="execute-node-pre">{textOutputs.join('\n')}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* Read tool call component - specialized for file reading operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import type { BaseToolCallProps } from '../shared/types.js';
|
||||||
|
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||||
|
import { groupContent } from '../shared/utils.js';
|
||||||
|
import { FileLink } from '../../ui/FileLink.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized component for Read tool calls
|
||||||
|
* Optimized for displaying file reading operations
|
||||||
|
* Shows: Read filename (no content preview)
|
||||||
|
*/
|
||||||
|
export const ReadToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||||
|
const { content, locations, toolCallId } = toolCall;
|
||||||
|
|
||||||
|
// Group content by type
|
||||||
|
const { errors } = groupContent(content);
|
||||||
|
|
||||||
|
// Error case: show error
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const path = locations?.[0]?.path || '';
|
||||||
|
return (
|
||||||
|
<ToolCallContainer
|
||||||
|
label={'Read'}
|
||||||
|
className="read-tool-call-error"
|
||||||
|
status="error"
|
||||||
|
toolCallId={toolCallId}
|
||||||
|
labelSuffix={
|
||||||
|
path ? (
|
||||||
|
<FileLink
|
||||||
|
path={path}
|
||||||
|
showFullPath={false}
|
||||||
|
className="text-xs font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{errors.join('\n')}
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success case: show which file was read with filename in label
|
||||||
|
if (locations && locations.length > 0) {
|
||||||
|
const path = locations[0].path;
|
||||||
|
return (
|
||||||
|
<ToolCallContainer
|
||||||
|
label={'Read'}
|
||||||
|
className="read-tool-call-success"
|
||||||
|
status="success"
|
||||||
|
toolCallId={toolCallId}
|
||||||
|
labelSuffix={
|
||||||
|
path ? (
|
||||||
|
<FileLink
|
||||||
|
path={path}
|
||||||
|
showFullPath={false}
|
||||||
|
className="text-xs font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{null}
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No file info, don't show
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* UpdatedPlan tool call component - specialized for plan update operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import type { BaseToolCallProps } from '../shared/types.js';
|
||||||
|
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||||
|
import { groupContent, safeTitle } from '../shared/utils.js';
|
||||||
|
import { CheckboxDisplay } from '../../ui/CheckboxDisplay.js';
|
||||||
|
|
||||||
|
type EntryStatus = 'pending' | 'in_progress' | 'completed';
|
||||||
|
|
||||||
|
interface PlanEntry {
|
||||||
|
content: string;
|
||||||
|
status: EntryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapToolStatusToBullet = (
|
||||||
|
status: import('../shared/types.js').ToolCallStatus,
|
||||||
|
): 'success' | 'error' | 'warning' | 'loading' | 'default' => {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed':
|
||||||
|
return 'success';
|
||||||
|
case 'failed':
|
||||||
|
return 'error';
|
||||||
|
case 'in_progress':
|
||||||
|
return 'warning';
|
||||||
|
case 'pending':
|
||||||
|
return 'loading';
|
||||||
|
default:
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse plan entries with - [ ] / - [x] from text as much as possible
|
||||||
|
const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => {
|
||||||
|
const text = textOutputs.join('\n');
|
||||||
|
const lines = text.split(/\r?\n/);
|
||||||
|
const entries: PlanEntry[] = [];
|
||||||
|
|
||||||
|
const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.*)$/;
|
||||||
|
for (const line of lines) {
|
||||||
|
const m = line.match(todoRe);
|
||||||
|
if (m) {
|
||||||
|
const mark = m[1];
|
||||||
|
const title = m[2].trim();
|
||||||
|
const status: EntryStatus =
|
||||||
|
mark === 'x' || mark === 'X'
|
||||||
|
? 'completed'
|
||||||
|
: mark === '-'
|
||||||
|
? 'in_progress'
|
||||||
|
: 'pending';
|
||||||
|
if (title) {
|
||||||
|
entries.push({ content: title, status });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no match is found, fall back to treating non-empty lines as pending items
|
||||||
|
if (entries.length === 0) {
|
||||||
|
for (const line of lines) {
|
||||||
|
const title = line.trim();
|
||||||
|
if (title) {
|
||||||
|
entries.push({ content: title, status: 'pending' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized component for UpdatedPlan tool calls
|
||||||
|
* Optimized for displaying plan update operations
|
||||||
|
*/
|
||||||
|
export const UpdatedPlanToolCall: React.FC<BaseToolCallProps> = ({
|
||||||
|
toolCall,
|
||||||
|
}) => {
|
||||||
|
const { content, status } = toolCall;
|
||||||
|
const { errors, textOutputs } = groupContent(content);
|
||||||
|
|
||||||
|
// Error-first display
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return (
|
||||||
|
<ToolCallContainer label="Updated Plan" status="error">
|
||||||
|
{errors.join('\n')}
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = parsePlanEntries(textOutputs);
|
||||||
|
|
||||||
|
const label = safeTitle(toolCall.title) || 'Updated Plan';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolCallContainer
|
||||||
|
label={label}
|
||||||
|
status={mapToolStatusToBullet(status)}
|
||||||
|
className="update-plan-toolcall"
|
||||||
|
>
|
||||||
|
<ul className="Fr list-none p-0 m-0 flex flex-col gap-1">
|
||||||
|
{entries.map((entry, idx) => {
|
||||||
|
const isDone = entry.status === 'completed';
|
||||||
|
const isIndeterminate = entry.status === 'in_progress';
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className={[
|
||||||
|
'Hr flex items-start gap-2 p-0 rounded text-[var(--app-primary-foreground)]',
|
||||||
|
isDone ? 'fo opacity-70' : '',
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
<label className="flex items-start gap-2">
|
||||||
|
<CheckboxDisplay
|
||||||
|
checked={isDone}
|
||||||
|
indeterminate={isIndeterminate}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={[
|
||||||
|
'vo flex-1 text-xs leading-[1.5] text-[var(--app-primary-foreground)]',
|
||||||
|
isDone
|
||||||
|
? 'line-through text-[var(--app-secondary-foreground)] opacity-70'
|
||||||
|
: 'opacity-85',
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
{entry.content}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</ToolCallContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -10,11 +10,13 @@ import type React from 'react';
|
|||||||
import type { BaseToolCallProps } from './shared/types.js';
|
import type { BaseToolCallProps } from './shared/types.js';
|
||||||
import { shouldShowToolCall } from './shared/utils.js';
|
import { shouldShowToolCall } from './shared/utils.js';
|
||||||
import { GenericToolCall } from './GenericToolCall.js';
|
import { GenericToolCall } from './GenericToolCall.js';
|
||||||
import { ReadToolCall } from './ReadToolCall.js';
|
import { ReadToolCall } from './Read/ReadToolCall.js';
|
||||||
import { WriteToolCall } from './WriteToolCall.js';
|
import { WriteToolCall } from './WriteToolCall.js';
|
||||||
import { EditToolCall } from './Edit/EditToolCall.js';
|
import { EditToolCall } from './Edit/EditToolCall.js';
|
||||||
import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js';
|
import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js';
|
||||||
import { ExecuteToolCall } from './Execute/Execute.js';
|
import { ExecuteToolCall } from './Execute/Execute.js';
|
||||||
|
import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.js';
|
||||||
|
import { ExecuteNodeToolCall } from './ExecuteNode/ExecuteNodeToolCall.js';
|
||||||
import { SearchToolCall } from './SearchToolCall.js';
|
import { SearchToolCall } from './SearchToolCall.js';
|
||||||
import { ThinkToolCall } from './ThinkToolCall.js';
|
import { ThinkToolCall } from './ThinkToolCall.js';
|
||||||
import { TodoWriteToolCall } from './TodoWriteToolCall.js';
|
import { TodoWriteToolCall } from './TodoWriteToolCall.js';
|
||||||
@@ -24,6 +26,7 @@ import { TodoWriteToolCall } from './TodoWriteToolCall.js';
|
|||||||
*/
|
*/
|
||||||
export const getToolCallComponent = (
|
export const getToolCallComponent = (
|
||||||
kind: string,
|
kind: string,
|
||||||
|
toolCall?: import('./shared/types.js').ToolCallData,
|
||||||
): React.FC<BaseToolCallProps> => {
|
): React.FC<BaseToolCallProps> => {
|
||||||
const normalizedKind = kind.toLowerCase();
|
const normalizedKind = kind.toLowerCase();
|
||||||
|
|
||||||
@@ -39,12 +42,35 @@ export const getToolCallComponent = (
|
|||||||
return EditToolCall;
|
return EditToolCall;
|
||||||
|
|
||||||
case 'execute':
|
case 'execute':
|
||||||
|
// Check if this is a node/npm version check command
|
||||||
|
if (toolCall) {
|
||||||
|
const commandText =
|
||||||
|
typeof toolCall.rawInput === 'string'
|
||||||
|
? toolCall.rawInput
|
||||||
|
: typeof toolCall.rawInput === 'object' &&
|
||||||
|
toolCall.rawInput !== null
|
||||||
|
? (toolCall.rawInput as { command?: string }).command || ''
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
if (
|
||||||
|
commandText.includes('node --version') ||
|
||||||
|
commandText.includes('npm --version')
|
||||||
|
) {
|
||||||
|
return ExecuteNodeToolCall;
|
||||||
|
}
|
||||||
|
}
|
||||||
return ExecuteToolCall;
|
return ExecuteToolCall;
|
||||||
|
|
||||||
case 'bash':
|
case 'bash':
|
||||||
case 'command':
|
case 'command':
|
||||||
return BashExecuteToolCall;
|
return BashExecuteToolCall;
|
||||||
|
|
||||||
|
case 'updated_plan':
|
||||||
|
case 'updatedplan':
|
||||||
|
case 'todo_write':
|
||||||
|
return UpdatedPlanToolCall;
|
||||||
|
|
||||||
case 'search':
|
case 'search':
|
||||||
case 'grep':
|
case 'grep':
|
||||||
case 'glob':
|
case 'glob':
|
||||||
@@ -56,7 +82,8 @@ export const getToolCallComponent = (
|
|||||||
return ThinkToolCall;
|
return ThinkToolCall;
|
||||||
|
|
||||||
case 'todowrite':
|
case 'todowrite':
|
||||||
case 'todo_write':
|
return TodoWriteToolCall;
|
||||||
|
// case 'todo_write':
|
||||||
case 'update_todos':
|
case 'update_todos':
|
||||||
return TodoWriteToolCall;
|
return TodoWriteToolCall;
|
||||||
|
|
||||||
@@ -82,7 +109,7 @@ export const ToolCallRouter: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the appropriate component for this kind
|
// Get the appropriate component for this kind
|
||||||
const Component = getToolCallComponent(toolCall.kind);
|
const Component = getToolCallComponent(toolCall.kind, toolCall);
|
||||||
|
|
||||||
// Render the specialized component
|
// Render the specialized component
|
||||||
return <Component toolCall={toolCall} />;
|
return <Component toolCall={toolCall} />;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ interface ToolCallContainerProps {
|
|||||||
toolCallId?: string;
|
toolCallId?: string;
|
||||||
/** Optional trailing content rendered next to label (e.g., clickable filename) */
|
/** Optional trailing content rendered next to label (e.g., clickable filename) */
|
||||||
labelSuffix?: React.ReactNode;
|
labelSuffix?: React.ReactNode;
|
||||||
|
/** Optional custom class name */
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We previously computed a bullet color class in JS, but the current
|
// NOTE: We previously computed a bullet color class in JS, but the current
|
||||||
@@ -40,9 +42,10 @@ export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
|||||||
children,
|
children,
|
||||||
toolCallId: _toolCallId,
|
toolCallId: _toolCallId,
|
||||||
labelSuffix,
|
labelSuffix,
|
||||||
|
className: _className,
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={`qwen-message relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
className={`qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
||||||
>
|
>
|
||||||
{/* Timeline connector line using ::after pseudo-element */}
|
{/* Timeline connector line using ::after pseudo-element */}
|
||||||
<div className="toolcall-content-wrapper flex flex-col gap-1 min-w-0 max-w-full">
|
<div className="toolcall-content-wrapper flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
@@ -50,15 +53,18 @@ export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
|||||||
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
{/* {toolCallId && (
|
{/* TODO: for 调试 */}
|
||||||
|
{_toolCallId && (
|
||||||
<span className="text-[10px] opacity-30">
|
<span className="text-[10px] opacity-30">
|
||||||
[{toolCallId.slice(-8)}]
|
[{_toolCallId.slice(-8)}]
|
||||||
</span>
|
</span>
|
||||||
)} */}
|
)}
|
||||||
{labelSuffix}
|
{labelSuffix}
|
||||||
</div>
|
</div>
|
||||||
{children && (
|
{children && (
|
||||||
<div className="text-[var(--app-secondary-foreground)]">{children}</div>
|
<div className="text-[var(--app-secondary-foreground)] py-2">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user