mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 04:00:36 +00:00
feat(webui): migrate icons, Tooltip, WaitingMessage from vscode-ide-companion
- Move icon components (FileIcons, EditIcons, NavigationIcons, StatusIcons, SpecialIcons, StopIcon) from vscode-ide-companion to webui package - Migrate Tooltip component with CSS variable theming support - Migrate WaitingMessage and InterruptedMessage components - Enhance Button component with forwardRef, new variants (ghost, outline), loading state, and icon support - Enhance Input component with forwardRef, error state, label, and helper text - Update vscode-ide-companion to import components from @qwen-code/webui - Remove replaced local components from vscode-ide-companion - Add skipLibCheck to vscode-ide-companion tsconfig for type compatibility
This commit is contained in:
parent
af76450dee
commit
71570540cc
45 changed files with 1049 additions and 308 deletions
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface CloseIconProps {
|
||||
|
|
@ -11,20 +17,20 @@ const CloseIcon: React.FC<CloseIconProps> = ({
|
|||
color = 'currentColor',
|
||||
className = '',
|
||||
}) => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
);
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseIcon;
|
||||
|
|
|
|||
405
packages/webui/src/components/icons/EditIcons.tsx
Normal file
405
packages/webui/src/components/icons/EditIcons.tsx
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Edit mode related icons
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
/**
|
||||
* Edit pencil icon (16x16)
|
||||
* Used for "Ask before edits" mode
|
||||
*/
|
||||
export const EditPencilIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Auto/fast-forward icon (16x16)
|
||||
* Used for "Edit automatically" mode
|
||||
*/
|
||||
export const AutoEditIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Plan mode/bars icon (16x16)
|
||||
* Used for "Plan mode"
|
||||
*/
|
||||
export const PlanModeIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Code brackets icon (20x20)
|
||||
* Used for active file indicator
|
||||
*/
|
||||
export const CodeBracketsIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hide context (eye slash) icon (20x20)
|
||||
* Used to indicate the active selection will NOT be auto-loaded into context
|
||||
*/
|
||||
export const HideContextIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M3.28 2.22a.75.75 0 0 0-1.06 1.06l14.5 14.5a.75.75 0 1 0 1.06-1.06l-1.745-1.745a10.029 10.029 0 0 0 3.3-4.38 1.651 1.651 0 0 0 0-1.185A10.004 10.004 0 0 0 9.999 3a9.956 9.956 0 0 0-4.744 1.194L3.28 2.22ZM7.752 6.69l1.092 1.092a2.5 2.5 0 0 1 3.374 3.373l1.091 1.092a4 4 0 0 0-5.557-5.557Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path d="m10.748 13.93 2.523 2.523a9.987 9.987 0 0 1-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 0 1 0-1.186A10.007 10.007 0 0 1 2.839 6.02L6.07 9.252a4 4 0 0 0 4.678 4.678Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Slash command icon (20x20)
|
||||
* Used for command menu button
|
||||
*/
|
||||
export const SlashCommandIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Link/attachment icon (20x20)
|
||||
* Used for attach context button
|
||||
*/
|
||||
export const LinkIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Open diff icon (16x16)
|
||||
* Used for opening diff in VS Code
|
||||
*/
|
||||
export const OpenDiffIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M13.5 7l-4-4v3h-6v2h6v3l4-4z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Undo edit icon (16x16)
|
||||
* Used for undoing edits in diff views
|
||||
*/
|
||||
export const UndoIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9 10.6667L12.3333 14L9 17.3333M12.3333 14H4.66667C3.56112 14 2.66667 13.1056 2.66667 12V4.66667C2.66667 3.56112 3.56112 2.66667 4.66667 2.66667H13.3333C14.4389 2.66667 15.3333 3.56112 15.3333 4.66667V8.66667"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Redo edit icon (16x16)
|
||||
* Used for redoing edits in diff views
|
||||
*/
|
||||
export const RedoIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M7 10.6667L3.66667 14L7 17.3333M3.66667 14H11.3333C12.4389 14 13.3333 13.1056 13.3333 12V4.66667C13.3333 3.56112 12.4389 2.66667 11.3333 2.66667H2.66667C1.56112 2.66667 0.666667 3.56112 0.666667 4.66667V8.66667"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Replace all icon (16x16)
|
||||
* Used for replacing all occurrences in search/replace
|
||||
*/
|
||||
export const ReplaceAllIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M11.3333 5.33333L14 8L11.3333 10.6667M14 8H6C3.79086 8 2 9.79086 2 12M2.66667 10.6667L0 8L2.66667 5.33333M2.66667 8H10C12.2091 8 14 6.20914 14 4V4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Copy icon (16x16)
|
||||
* Used for copying content
|
||||
*/
|
||||
export const CopyIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
x="4.6665"
|
||||
y="4"
|
||||
width="8"
|
||||
height="8"
|
||||
rx="1.33333"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
/>
|
||||
<path
|
||||
d="M6 6H5.33333C4.04767 6 3 7.04767 3 8.33333V10.6667C3 11.9523 4.04767 13 5.33333 13H7.66667C8.95233 13 10 11.9523 10 10.6667V10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Paste icon (16x16)
|
||||
* Used for pasting content
|
||||
*/
|
||||
export const PasteIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M5.3335 4.66669V4.00002C5.3335 3.62305 5.48315 3.26159 5.75181 2.99293C6.02047 2.72427 6.38193 2.57467 6.7589 2.57467H9.2589C9.63587 2.57467 9.99733 2.72427 10.266 2.99293C10.5346 3.26159 10.6842 3.62305 10.6842 4.00002V4.66669M12.0176 4.66669H12.6842C13.0612 4.66669 13.4227 4.81628 13.6913 5.08494C13.96 5.3536 14.1096 5.71506 14.1096 6.09203V10.9254C14.1096 11.3023 13.96 11.6638 13.6913 11.9325C13.4227 12.2011 13.0612 12.3507 12.6842 12.3507H3.35089C2.97392 12.3507 2.61246 12.2011 2.3438 11.9325C2.07514 11.6638 1.92554 11.3023 1.92554 10.9254V6.09203C1.92554 5.71506 2.07514 5.3536 2.3438 5.08494C2.61246 4.81628 2.97392 4.66669 3.35089 4.66669H4.01756M12.0176 4.66669V7.33335C12.0176 8.06973 11.7253 8.77607 11.2093 9.29205C10.6933 9.80803 9.98698 10.0999 9.2506 10.0999H6.77573C6.03935 10.0999 5.33301 9.80803 4.81703 9.29205C4.30105 8.77607 4.00918 8.06973 4.00918 7.33335V4.66669M12.0176 4.66669H4.01756"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Select all icon (16x16)
|
||||
* Used for selecting all content
|
||||
*/
|
||||
export const SelectAllIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
x="2.6665"
|
||||
y="2"
|
||||
width="10.6667"
|
||||
height="12"
|
||||
rx="1.33333"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
/>
|
||||
<path
|
||||
d="M5.3335 5.33333H8.00016M5.3335 8H10.6668M5.3335 10.6667H10.6668"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
103
packages/webui/src/components/icons/FileIcons.tsx
Normal file
103
packages/webui/src/components/icons/FileIcons.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* File and document related icons
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
/**
|
||||
* File document icon (16x16)
|
||||
* Used for file completion menu
|
||||
*/
|
||||
export const FileIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M9 2H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7l-5-5zm3 7V3.5L10.5 2H10v3a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V2H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1zM6 3h3v2H6V3z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const FileListIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M5 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Save document icon (16x16)
|
||||
* Used for save session button
|
||||
*/
|
||||
export const SaveDocumentIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M2.66663 2.66663H10.6666L13.3333 5.33329V13.3333H2.66663V2.66663Z" />
|
||||
<path d="M8 10.6666V8M8 8V5.33329M8 8H10.6666M8 8H5.33329" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Folder icon (16x16)
|
||||
* Useful for directory entries in completion lists
|
||||
*/
|
||||
export const FolderIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M1.5 3A1.5 1.5 0 0 1 3 1.5h3.086a1.5 1.5 0 0 1 1.06.44L8.5 3H13A1.5 1.5 0 0 1 14.5 4.5v7A1.5 1.5 0 0 1 13 13H3A1.5 1.5 0 0 1 1.5 11.5v-8Z" />
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface IconProps {
|
||||
|
|
@ -12,27 +18,24 @@ const Icon: React.FC<IconProps> = ({
|
|||
size = 24,
|
||||
color = 'currentColor',
|
||||
className = '',
|
||||
}) =>
|
||||
}) => (
|
||||
// This is a placeholder - in a real implementation you might use an icon library
|
||||
(
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill={color}
|
||||
className={className}
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill={color}
|
||||
className={className}
|
||||
>
|
||||
<text
|
||||
x="50%"
|
||||
y="50%"
|
||||
dominantBaseline="middle"
|
||||
textAnchor="middle"
|
||||
fontSize="10"
|
||||
>
|
||||
<text
|
||||
x="50%"
|
||||
y="50%"
|
||||
dominantBaseline="middle"
|
||||
textAnchor="middle"
|
||||
fontSize="10"
|
||||
>
|
||||
{name}
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
;
|
||||
|
||||
{name}
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
export default Icon;
|
||||
|
|
|
|||
212
packages/webui/src/components/icons/NavigationIcons.tsx
Normal file
212
packages/webui/src/components/icons/NavigationIcons.tsx
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Navigation and action icons
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
/**
|
||||
* Chevron down icon (20x20)
|
||||
* Used for dropdown arrows
|
||||
*/
|
||||
export const ChevronDownIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Plus icon (20x20)
|
||||
* Used for new session button
|
||||
*/
|
||||
export const PlusIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Small plus icon (16x16)
|
||||
* Used for default attachment type
|
||||
*/
|
||||
export const PlusSmallIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8 2a.5.5 0 0 1 .5.5V5h2.5a.5.5 0 0 1 0 1H8.5v2.5a.5.5 0 0 1-1 0V6H5a.5.5 0 0 1 0-1h2.5V2.5A.5.5 0 0 1 8 2Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Arrow up icon (20x20)
|
||||
* Used for send message button
|
||||
*/
|
||||
export const ArrowUpIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Close X icon (14x14)
|
||||
* Used for close buttons in banners and dialogs
|
||||
*/
|
||||
export const CloseIcon: React.FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1 1L13 13M1 13L13 1"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CloseSmallIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Search/magnifying glass icon (20x20)
|
||||
* Used for search input
|
||||
*/
|
||||
export const SearchIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9 3.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11ZM2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-3.328A7 7 0 0 1 2 9Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Refresh/reload icon (16x16)
|
||||
* Used for refresh session list
|
||||
*/
|
||||
export const RefreshIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M13.3333 8C13.3333 10.9455 10.9455 13.3333 8 13.3333C5.05451 13.3333 2.66663 10.9455 2.66663 8C2.66663 5.05451 5.05451 2.66663 8 2.66663" />
|
||||
<path d="M10.6666 8L13.3333 8M13.3333 8L13.3333 5.33333M13.3333 8L10.6666 10.6667" />
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface SendIconProps {
|
||||
|
|
@ -11,20 +17,20 @@ const SendIcon: React.FC<SendIconProps> = ({
|
|||
color = 'currentColor',
|
||||
className = '',
|
||||
}) => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
||||
</svg>
|
||||
);
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default SendIcon;
|
||||
|
|
|
|||
79
packages/webui/src/components/icons/SpecialIcons.tsx
Normal file
79
packages/webui/src/components/icons/SpecialIcons.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Special UI icons
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
interface ThinkingIconProps extends IconProps {
|
||||
/**
|
||||
* Whether thinking is enabled (affects styling)
|
||||
*/
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export const ThinkingIcon: React.FC<ThinkingIconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
enabled = false,
|
||||
style,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<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: enabled
|
||||
? 'var(--app-qwen-ivory)'
|
||||
: 'var(--app-secondary-foreground)',
|
||||
fill: enabled
|
||||
? 'var(--app-qwen-ivory)'
|
||||
: 'var(--app-secondary-foreground)',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const TerminalIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.14648 7.14648C5.34175 6.95122 5.65825 6.95122 5.85352 7.14648L8.35352 9.64648C8.44728 9.74025 8.5 9.86739 8.5 10C8.5 10.0994 8.47037 10.1958 8.41602 10.2773L8.35352 10.3535L5.85352 12.8535C5.65825 13.0488 5.34175 13.0488 5.14648 12.8535C4.95122 12.6583 4.95122 12.3417 5.14648 12.1465L7.29297 10L5.14648 7.85352C4.95122 7.65825 4.95122 7.34175 5.14648 7.14648Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path d="M14.5 12C14.7761 12 15 12.2239 15 12.5C15 12.7761 14.7761 13 14.5 13H9.5C9.22386 13 9 12.7761 9 12.5C9 12.2239 9.22386 12 9.5 12H14.5Z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.5 4C17.3284 4 18 4.67157 18 5.5V14.5C18 15.3284 17.3284 16 16.5 16H3.5C2.67157 16 2 15.3284 2 14.5V5.5C2 4.67157 2.67157 4 3.5 4H16.5ZM3.5 5C3.22386 5 3 5.22386 3 5.5V14.5C3 14.7761 3.22386 15 3.5 15H16.5C16.7761 15 17 14.7761 17 14.5V5.5C17 5.22386 16.7761 5 16.5 5H3.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
188
packages/webui/src/components/icons/StatusIcons.tsx
Normal file
188
packages/webui/src/components/icons/StatusIcons.tsx
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Status and state related icons
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
/**
|
||||
* Plan completed icon (14x14)
|
||||
* Used for completed plan items
|
||||
*/
|
||||
export const PlanCompletedIcon: React.FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<circle cx="7" cy="7" r="6" fill="currentColor" opacity="0.2" />
|
||||
<path
|
||||
d="M4 7.5L6 9.5L10 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Plan in progress icon (14x14)
|
||||
* Used for in-progress plan items
|
||||
*/
|
||||
export const PlanInProgressIcon: React.FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
cx="7"
|
||||
cy="7"
|
||||
r="5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Plan pending icon (14x14)
|
||||
* Used for pending plan items
|
||||
*/
|
||||
export const PlanPendingIcon: React.FC<IconProps> = ({
|
||||
size = 14,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
cx="7"
|
||||
cy="7"
|
||||
r="5.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Warning triangle icon (20x20)
|
||||
* Used for warning messages
|
||||
*/
|
||||
export const WarningTriangleIcon: React.FC<IconProps> = ({
|
||||
size = 20,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* User profile icon (16x16)
|
||||
* Used for login command
|
||||
*/
|
||||
export const UserIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SymbolIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8 1a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 7.293V1.5A.5.5 0 0 1 8 1Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SelectionIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5Zm0 4a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Z" />
|
||||
</svg>
|
||||
);
|
||||
33
packages/webui/src/components/icons/StopIcon.tsx
Normal file
33
packages/webui/src/components/icons/StopIcon.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Stop icon for canceling operations
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { IconProps } from './types.js';
|
||||
|
||||
/**
|
||||
* Stop/square icon (16x16)
|
||||
* Used for stop/cancel operations
|
||||
*/
|
||||
export const StopIcon: React.FC<IconProps> = ({
|
||||
size = 16,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<rect x="4" y="4" width="8" height="8" rx="1" />
|
||||
</svg>
|
||||
);
|
||||
57
packages/webui/src/components/icons/index.ts
Normal file
57
packages/webui/src/components/icons/index.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export type { IconProps } from './types.js';
|
||||
|
||||
// File icons
|
||||
export {
|
||||
FileIcon,
|
||||
FileListIcon,
|
||||
SaveDocumentIcon,
|
||||
FolderIcon,
|
||||
} from './FileIcons.js';
|
||||
|
||||
// Navigation icons
|
||||
export {
|
||||
ChevronDownIcon,
|
||||
PlusIcon,
|
||||
PlusSmallIcon,
|
||||
ArrowUpIcon,
|
||||
CloseIcon,
|
||||
CloseSmallIcon,
|
||||
SearchIcon,
|
||||
RefreshIcon,
|
||||
} from './NavigationIcons.js';
|
||||
|
||||
// Edit mode icons
|
||||
export {
|
||||
EditPencilIcon,
|
||||
AutoEditIcon,
|
||||
PlanModeIcon,
|
||||
CodeBracketsIcon,
|
||||
HideContextIcon,
|
||||
SlashCommandIcon,
|
||||
LinkIcon,
|
||||
OpenDiffIcon,
|
||||
UndoIcon,
|
||||
} from './EditIcons.js';
|
||||
|
||||
// Status icons
|
||||
export {
|
||||
PlanCompletedIcon,
|
||||
PlanInProgressIcon,
|
||||
PlanPendingIcon,
|
||||
WarningTriangleIcon,
|
||||
UserIcon,
|
||||
SymbolIcon,
|
||||
SelectionIcon,
|
||||
} from './StatusIcons.js';
|
||||
|
||||
// Special icons
|
||||
export { ThinkingIcon, TerminalIcon } from './SpecialIcons.js';
|
||||
|
||||
// Stop icon
|
||||
export { StopIcon } from './StopIcon.js';
|
||||
22
packages/webui/src/components/icons/types.ts
Normal file
22
packages/webui/src/components/icons/types.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Common icon props interface
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
export interface IconProps extends React.SVGProps<SVGSVGElement> {
|
||||
/**
|
||||
* Icon size (width and height)
|
||||
* @default 16
|
||||
*/
|
||||
size?: number;
|
||||
|
||||
/**
|
||||
* Additional CSS classes
|
||||
*/
|
||||
className?: string;
|
||||
}
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface ContainerProps {
|
||||
|
|
@ -6,7 +12,7 @@ interface ContainerProps {
|
|||
}
|
||||
|
||||
const Container: React.FC<ContainerProps> = ({ children, className = '' }) => (
|
||||
<div className={`container mx-auto px-4 ${className}`}>{children}</div>
|
||||
);
|
||||
<div className={`container mx-auto px-4 ${className}`}>{children}</div>
|
||||
);
|
||||
|
||||
export default Container;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const Footer: React.FC = () => <footer>Footer Component Placeholder</footer>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const Header: React.FC = () => <header>Header Component Placeholder</header>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const Main: React.FC = () => <main>Main Component Placeholder</main>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const Sidebar: React.FC = () => <aside>Sidebar Component Placeholder</aside>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface MessageProps {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const MessageInput: React.FC = () => <div>MessageInput Component Placeholder</div>;
|
||||
const MessageInput: React.FC = () => (
|
||||
<div>MessageInput Component Placeholder</div>
|
||||
);
|
||||
|
||||
export default MessageInput;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
const MessageList: React.FC = () => <div>MessageList Component Placeholder</div>;
|
||||
const MessageList: React.FC = () => (
|
||||
<div>MessageList Component Placeholder</div>
|
||||
);
|
||||
|
||||
export default MessageList;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
interface InterruptedMessageProps {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
// A lightweight status line similar to WaitingMessage but without the left status icon.
|
||||
export const InterruptedMessage: React.FC<InterruptedMessageProps> = ({
|
||||
text = 'Interrupted',
|
||||
}) => (
|
||||
<div className="flex gap-0 items-start text-left py-2 flex-col opacity-85">
|
||||
<div className="interrupted-item w-full relative">
|
||||
<span className="opacity-70 italic">{text}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
interface WaitingMessageProps {
|
||||
loadingMessage: string;
|
||||
}
|
||||
|
||||
// Rotate message every few seconds while waiting
|
||||
const ROTATE_INTERVAL_MS = 3000; // rotate every 3s per request
|
||||
|
||||
// Default witty loading phrases
|
||||
const DEFAULT_LOADING_PHRASES = [
|
||||
'Processing...',
|
||||
'Working on it...',
|
||||
'Just a moment...',
|
||||
'Loading...',
|
||||
'Hold tight...',
|
||||
'Almost there...',
|
||||
];
|
||||
|
||||
export const WaitingMessage: React.FC<WaitingMessageProps> = ({
|
||||
loadingMessage,
|
||||
}) => {
|
||||
// Build a phrase list that starts with the provided message (if any), then witty fallbacks
|
||||
const phrases = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
const list: string[] = [];
|
||||
if (loadingMessage && loadingMessage.trim()) {
|
||||
list.push(loadingMessage);
|
||||
set.add(loadingMessage);
|
||||
}
|
||||
for (const p of DEFAULT_LOADING_PHRASES) {
|
||||
if (!set.has(p)) {
|
||||
list.push(p);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}, [loadingMessage]);
|
||||
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
// Reset to the first phrase whenever the incoming message changes
|
||||
useEffect(() => {
|
||||
setIndex(0);
|
||||
}, [phrases]);
|
||||
|
||||
// Periodically rotate to a different phrase
|
||||
useEffect(() => {
|
||||
if (phrases.length <= 1) {
|
||||
return;
|
||||
}
|
||||
const id = setInterval(() => {
|
||||
setIndex((prev) => {
|
||||
// pick a different random index to avoid immediate repeats
|
||||
let next = Math.floor(Math.random() * phrases.length);
|
||||
if (phrases.length > 1) {
|
||||
let guard = 0;
|
||||
while (next === prev && guard < 5) {
|
||||
next = Math.floor(Math.random() * phrases.length);
|
||||
guard++;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, ROTATE_INTERVAL_MS);
|
||||
return () => clearInterval(id);
|
||||
}, [phrases]);
|
||||
|
||||
return (
|
||||
<div className="waiting-message-outer flex gap-0 items-start text-left py-2 flex-col opacity-85">
|
||||
{/* Use the same left status icon (pseudo-element) style as assistant-message-container */}
|
||||
<div className="assistant-message-container assistant-message-loading waiting-message-inner w-full items-start pl-[30px] relative">
|
||||
<span className="waiting-message-text opacity-70 italic loading-text-shimmer">
|
||||
{phrases[index]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,49 +1,143 @@
|
|||
import type React from 'react';
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
interface ButtonProps {
|
||||
import type React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
/**
|
||||
* Button variant types
|
||||
*/
|
||||
export type ButtonVariant =
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'danger'
|
||||
| 'ghost'
|
||||
| 'outline';
|
||||
|
||||
/**
|
||||
* Button size types
|
||||
*/
|
||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
/**
|
||||
* Button component props interface
|
||||
*/
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** Button content */
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
/** Visual style variant */
|
||||
variant?: ButtonVariant;
|
||||
/** Button size */
|
||||
size?: ButtonSize;
|
||||
/** Loading state - shows spinner and disables button */
|
||||
loading?: boolean;
|
||||
/** Icon to display before children */
|
||||
leftIcon?: React.ReactNode;
|
||||
/** Icon to display after children */
|
||||
rightIcon?: React.ReactNode;
|
||||
/** Full width button */
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
children,
|
||||
onClick,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const baseClasses =
|
||||
'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
/**
|
||||
* Button component with multiple variants and sizes
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Button variant="primary" size="md" onClick={handleClick}>
|
||||
* Click me
|
||||
* </Button>
|
||||
* ```
|
||||
*/
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
loading = false,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
fullWidth = false,
|
||||
className = '',
|
||||
type = 'button',
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const isDisabled = disabled || loading;
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
|
||||
secondary:
|
||||
'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
||||
};
|
||||
const baseClasses =
|
||||
'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'px-2 py-1 text-sm',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-6 py-3 text-lg',
|
||||
};
|
||||
const variantClasses: Record<ButtonVariant, string> = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
|
||||
secondary:
|
||||
'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
||||
ghost:
|
||||
'bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-400',
|
||||
outline:
|
||||
'bg-transparent border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-400',
|
||||
};
|
||||
|
||||
const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : '';
|
||||
const sizeClasses: Record<ButtonSize, string> = {
|
||||
sm: 'px-2 py-1 text-sm gap-1',
|
||||
md: 'px-4 py-2 gap-2',
|
||||
lg: 'px-6 py-3 text-lg gap-2',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabledClass} ${className}`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
const disabledClass = isDisabled
|
||||
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
||||
: '';
|
||||
const widthClass = fullWidth ? 'w-full' : '';
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabledClass} ${widthClass} ${className}`.trim()}
|
||||
disabled={isDisabled}
|
||||
aria-disabled={isDisabled}
|
||||
aria-busy={loading}
|
||||
{...props}
|
||||
>
|
||||
{loading && (
|
||||
<svg
|
||||
className="animate-spin h-4 w-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{!loading && leftIcon}
|
||||
{children}
|
||||
{!loading && rightIcon}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export default Button;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,149 @@
|
|||
import type React from 'react';
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
interface InputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
import type React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
/**
|
||||
* Input size types
|
||||
*/
|
||||
export type InputSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
/**
|
||||
* Input component props interface
|
||||
*/
|
||||
export interface InputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
/** Input size */
|
||||
size?: InputSize;
|
||||
/** Error state */
|
||||
error?: boolean;
|
||||
/** Error message to display */
|
||||
errorMessage?: string;
|
||||
/** Label for the input */
|
||||
label?: string;
|
||||
/** Helper text below input */
|
||||
helperText?: string;
|
||||
/** Left icon/element */
|
||||
leftElement?: React.ReactNode;
|
||||
/** Right icon/element */
|
||||
rightElement?: React.ReactNode;
|
||||
/** Full width input */
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
const Input: React.FC<InputProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
className = '',
|
||||
}) => (
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className={`border rounded px-3 py-2 ${className}`}
|
||||
/>
|
||||
);
|
||||
/**
|
||||
* Input component with multiple sizes and states
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Input
|
||||
* label="Email"
|
||||
* placeholder="Enter your email"
|
||||
* error={!!errors.email}
|
||||
* errorMessage={errors.email}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{
|
||||
size = 'md',
|
||||
error = false,
|
||||
errorMessage,
|
||||
label,
|
||||
helperText,
|
||||
leftElement,
|
||||
rightElement,
|
||||
fullWidth = false,
|
||||
className = '',
|
||||
id,
|
||||
disabled,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const baseClasses =
|
||||
'border rounded transition-colors focus:outline-none focus:ring-2';
|
||||
|
||||
const sizeClasses: Record<InputSize, string> = {
|
||||
sm: 'px-2 py-1 text-sm',
|
||||
md: 'px-3 py-2',
|
||||
lg: 'px-4 py-3 text-lg',
|
||||
};
|
||||
|
||||
const stateClasses = error
|
||||
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500';
|
||||
|
||||
const disabledClasses = disabled
|
||||
? 'bg-gray-100 cursor-not-allowed opacity-60'
|
||||
: 'bg-white';
|
||||
|
||||
const widthClass = fullWidth ? 'w-full' : '';
|
||||
|
||||
const paddingClasses = [
|
||||
leftElement ? 'pl-10' : '',
|
||||
rightElement ? 'pr-10' : '',
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<div className={`${fullWidth ? 'w-full' : 'inline-block'}`}>
|
||||
{label && (
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
className="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="relative">
|
||||
{leftElement && (
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">
|
||||
{leftElement}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={inputId}
|
||||
disabled={disabled}
|
||||
aria-invalid={error}
|
||||
aria-describedby={
|
||||
errorMessage
|
||||
? `${inputId}-error`
|
||||
: helperText
|
||||
? `${inputId}-helper`
|
||||
: undefined
|
||||
}
|
||||
className={`${baseClasses} ${sizeClasses[size]} ${stateClasses} ${disabledClasses} ${widthClass} ${paddingClasses} ${className}`.trim()}
|
||||
{...props}
|
||||
/>
|
||||
{rightElement && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500">
|
||||
{rightElement}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errorMessage && error && (
|
||||
<p id={`${inputId}-error`} className="mt-1 text-sm text-red-600">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
{helperText && !error && (
|
||||
<p id={`${inputId}-helper`} className="mt-1 text-sm text-gray-500">
|
||||
{helperText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export default Input;
|
||||
|
|
|
|||
|
|
@ -1,93 +1,73 @@
|
|||
import React, { useState } from 'react';
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
interface ChildProps {
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
tabIndex?: number;
|
||||
import type React from 'react';
|
||||
|
||||
/**
|
||||
* Tooltip component props
|
||||
*/
|
||||
export interface TooltipProps {
|
||||
/** Content to wrap with tooltip */
|
||||
children: React.ReactNode;
|
||||
/** Tooltip content (can be string or ReactNode) */
|
||||
content: React.ReactNode;
|
||||
/** Tooltip position relative to children */
|
||||
position?: 'top' | 'bottom' | 'left' | 'right';
|
||||
}
|
||||
|
||||
interface TooltipProps {
|
||||
children: React.ReactElement<ChildProps>;
|
||||
content: string;
|
||||
position?: 'top' | 'right' | 'bottom' | 'left';
|
||||
}
|
||||
|
||||
const Tooltip: React.FC<TooltipProps> = ({
|
||||
/**
|
||||
* Tooltip component using CSS group-hover for display
|
||||
* Supports CSS variables for theming
|
||||
*/
|
||||
export const Tooltip: React.FC<TooltipProps> = ({
|
||||
children,
|
||||
content,
|
||||
position = 'top',
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const positionClasses = {
|
||||
top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2',
|
||||
right: 'top-1/2 left-full transform -translate-y-1/2 ml-2',
|
||||
bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2',
|
||||
left: 'top-1/2 right-full transform -translate-y-1/2 mr-2',
|
||||
};
|
||||
|
||||
const arrowPositionClasses = {
|
||||
top: 'top-full left-1/2 transform -translate-x-1/2 -mt-1',
|
||||
right: 'top-1/2 left-0 transform -translate-y-1/2 -ml-1',
|
||||
bottom: 'top-0 left-1/2 transform -translate-x-1/2 -mb-1',
|
||||
left: 'top-1/2 right-0 transform -translate-y-1/2 -mr-1',
|
||||
};
|
||||
|
||||
const tooltipClass = `absolute ${positionClasses[position]} bg-gray-800 text-white text-xs rounded py-1 px-2 pointer-events-none z-10`;
|
||||
const arrowClass = `absolute w-2 h-2 bg-gray-800 transform rotate-45 ${arrowPositionClasses[position]}`;
|
||||
|
||||
return (
|
||||
<div className="relative inline-block">
|
||||
}) => (
|
||||
<div className="relative inline-block">
|
||||
<div className="group relative">
|
||||
{children}
|
||||
<div
|
||||
onMouseEnter={() => setIsVisible(true)}
|
||||
onMouseLeave={() => setIsVisible(false)}
|
||||
onFocus={() => setIsVisible(true)}
|
||||
onBlur={() => setIsVisible(false)}
|
||||
tabIndex={0}
|
||||
className={`
|
||||
absolute z-50 px-2 py-1 text-xs rounded-md shadow-lg
|
||||
bg-[var(--app-primary-background,#1f2937)] border border-[var(--app-input-border,#374151)]
|
||||
text-[var(--app-primary-foreground,#f9fafb)] whitespace-nowrap
|
||||
opacity-0 group-hover:opacity-100 transition-opacity duration-150
|
||||
-translate-x-1/2 left-1/2
|
||||
${
|
||||
position === 'top'
|
||||
? '-translate-y-1 bottom-full mb-1'
|
||||
: position === 'bottom'
|
||||
? 'translate-y-1 top-full mt-1'
|
||||
: position === 'left'
|
||||
? '-translate-x-full left-0 translate-y-[-50%] top-1/2'
|
||||
: 'translate-x-0 right-0 translate-y-[-50%] top-1/2'
|
||||
}
|
||||
pointer-events-none
|
||||
`}
|
||||
>
|
||||
{React.cloneElement(children, {
|
||||
onMouseEnter: () => {
|
||||
setIsVisible(true);
|
||||
const typedChildren = children as React.ReactElement<ChildProps>;
|
||||
if (typeof typedChildren.props.onMouseEnter === 'function') {
|
||||
typedChildren.props.onMouseEnter();
|
||||
{content}
|
||||
<div
|
||||
className={`
|
||||
absolute w-2 h-2 bg-[var(--app-primary-background,#1f2937)] border-l border-b border-[var(--app-input-border,#374151)]
|
||||
-rotate-45
|
||||
${
|
||||
position === 'top'
|
||||
? 'top-full left-1/2 -translate-x-1/2 -translate-y-1/2'
|
||||
: position === 'bottom'
|
||||
? 'bottom-full left-1/2 -translate-x-1/2 translate-y-1/2'
|
||||
: position === 'left'
|
||||
? 'right-full top-1/2 translate-x-1/2 -translate-y-1/2'
|
||||
: 'left-full top-1/2 -translate-x-1/2 -translate-y-1/2'
|
||||
}
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
setIsVisible(false);
|
||||
const typedChildren = children as React.ReactElement<ChildProps>;
|
||||
if (typeof typedChildren.props.onMouseLeave === 'function') {
|
||||
typedChildren.props.onMouseLeave();
|
||||
}
|
||||
},
|
||||
onFocus: () => {
|
||||
setIsVisible(true);
|
||||
const typedChildren = children as React.ReactElement<ChildProps>;
|
||||
if (typeof typedChildren.props.onFocus === 'function') {
|
||||
typedChildren.props.onFocus();
|
||||
}
|
||||
},
|
||||
onBlur: () => {
|
||||
setIsVisible(false);
|
||||
const typedChildren = children as React.ReactElement<ChildProps>;
|
||||
if (typeof typedChildren.props.onBlur === 'function') {
|
||||
typedChildren.props.onBlur();
|
||||
}
|
||||
},
|
||||
tabIndex:
|
||||
(children as React.ReactElement<ChildProps>).props.tabIndex || 0,
|
||||
})}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
{isVisible && (
|
||||
<div className={tooltipClass}>
|
||||
{content}
|
||||
<div className={arrowClass}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Tooltip;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useLocalStorage = <T>(key: string, initialValue: T) => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export const useTheme = () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Shared UI Components Export
|
||||
// Export all shared components from this package
|
||||
|
||||
|
|
@ -24,11 +30,14 @@ export { default as Footer } from './components/layout/Footer';
|
|||
export { default as Message } from './components/messages/Message';
|
||||
export { default as MessageInput } from './components/messages/MessageInput';
|
||||
export { default as MessageList } from './components/messages/MessageList';
|
||||
export { WaitingMessage } from './components/messages/Waiting/WaitingMessage';
|
||||
export { InterruptedMessage } from './components/messages/Waiting/InterruptedMessage';
|
||||
|
||||
// UI Elements
|
||||
export { default as Button } from './components/ui/Button';
|
||||
export { default as Input } from './components/ui/Input';
|
||||
export { default as Tooltip } from './components/ui/Tooltip';
|
||||
export { Tooltip } from './components/ui/Tooltip';
|
||||
export type { TooltipProps } from './components/ui/Tooltip';
|
||||
|
||||
// Permission components
|
||||
export { default as PermissionDrawer } from './components/PermissionDrawer';
|
||||
|
|
@ -38,6 +47,56 @@ export { default as Icon } from './components/icons/Icon';
|
|||
export { default as CloseIcon } from './components/icons/CloseIcon';
|
||||
export { default as SendIcon } from './components/icons/SendIcon';
|
||||
|
||||
// File Icons
|
||||
export {
|
||||
FileIcon,
|
||||
FileListIcon,
|
||||
SaveDocumentIcon,
|
||||
FolderIcon,
|
||||
} from './components/icons/FileIcons';
|
||||
|
||||
// Status Icons
|
||||
export {
|
||||
PlanCompletedIcon,
|
||||
PlanInProgressIcon,
|
||||
PlanPendingIcon,
|
||||
WarningTriangleIcon,
|
||||
UserIcon,
|
||||
SymbolIcon,
|
||||
SelectionIcon,
|
||||
} from './components/icons/StatusIcons';
|
||||
|
||||
// Navigation Icons
|
||||
export {
|
||||
ChevronDownIcon,
|
||||
PlusIcon,
|
||||
PlusSmallIcon,
|
||||
ArrowUpIcon,
|
||||
CloseIcon as CloseXIcon,
|
||||
CloseSmallIcon,
|
||||
SearchIcon,
|
||||
RefreshIcon,
|
||||
} from './components/icons/NavigationIcons';
|
||||
|
||||
// Edit Icons
|
||||
export {
|
||||
EditPencilIcon,
|
||||
AutoEditIcon,
|
||||
PlanModeIcon,
|
||||
CodeBracketsIcon,
|
||||
HideContextIcon,
|
||||
SlashCommandIcon,
|
||||
LinkIcon,
|
||||
OpenDiffIcon,
|
||||
UndoIcon,
|
||||
} from './components/icons/EditIcons';
|
||||
|
||||
// Special Icons
|
||||
export { ThinkingIcon, TerminalIcon } from './components/icons/SpecialIcons';
|
||||
|
||||
// Action Icons
|
||||
export { StopIcon } from './components/icons/StopIcon';
|
||||
|
||||
// Hooks
|
||||
export { useTheme } from './hooks/useTheme';
|
||||
export { useLocalStorage } from './hooks/useLocalStorage';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export interface MessageProps {
|
||||
id: string;
|
||||
content: string;
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'auto';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue