mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
feat(webui): Infrastructure Setup (Prerequisites)
This commit is contained in:
parent
ec0586b135
commit
af76450dee
23 changed files with 4367 additions and 374 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -63,3 +63,6 @@ patch_output.log
|
||||||
docs-site/.next
|
docs-site/.next
|
||||||
# content is a symlink to ../docs
|
# content is a symlink to ../docs
|
||||||
docs-site/content
|
docs-site/content
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
storybook-static
|
||||||
|
|
|
||||||
522
eslint.config.js
522
eslint.config.js
|
|
@ -1,3 +1,6 @@
|
||||||
|
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||||
|
import storybook from "eslint-plugin-storybook";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2025 Google LLC
|
* Copyright 2025 Google LLC
|
||||||
|
|
@ -13,277 +16,260 @@ import importPlugin from 'eslint-plugin-import';
|
||||||
import vitest from '@vitest/eslint-plugin';
|
import vitest from '@vitest/eslint-plugin';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config({
|
||||||
{
|
// Global ignores
|
||||||
// Global ignores
|
ignores: [
|
||||||
ignores: [
|
'node_modules/*',
|
||||||
'node_modules/*',
|
'packages/**/dist/**',
|
||||||
'packages/**/dist/**',
|
'bundle/**',
|
||||||
'bundle/**',
|
'package/bundle/**',
|
||||||
'package/bundle/**',
|
'.integration-tests/**',
|
||||||
'.integration-tests/**',
|
'packages/**/.integration-test/**',
|
||||||
'packages/**/.integration-test/**',
|
'dist/**',
|
||||||
'dist/**',
|
'docs-site/.next/**',
|
||||||
'docs-site/.next/**',
|
'docs-site/out/**',
|
||||||
'docs-site/out/**',
|
],
|
||||||
|
}, eslint.configs.recommended, ...tseslint.configs.recommended, reactHooks.configs['recommended-latest'], reactPlugin.configs.flat.recommended, // Add this if you are using React 17+
|
||||||
|
reactPlugin.configs.flat['jsx-runtime'], {
|
||||||
|
// Settings for eslint-plugin-react
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
// Import specific config
|
||||||
|
files: ['packages/cli/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
|
||||||
|
plugins: {
|
||||||
|
import: importPlugin,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...importPlugin.configs.recommended.rules,
|
||||||
|
...importPlugin.configs.typescript.rules,
|
||||||
|
'import/no-default-export': 'warn',
|
||||||
|
'import/no-unresolved': 'off', // Disable for now, can be noisy with monorepos/paths
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
// General overrides and rules for the project (TS/TSX files)
|
||||||
|
files: ['packages/*/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
|
||||||
|
plugins: {
|
||||||
|
import: importPlugin,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.es2021,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// We use TypeScript for React components; prop-types are unnecessary
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
// General Best Practice Rules (subset adapted for flat config)
|
||||||
|
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
||||||
|
'arrow-body-style': ['error', 'as-needed'],
|
||||||
|
curly: ['error', 'multi-line'],
|
||||||
|
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||||
|
'@typescript-eslint/consistent-type-assertions': [
|
||||||
|
'error',
|
||||||
|
{ assertionStyle: 'as' },
|
||||||
|
],
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': [
|
||||||
|
'error',
|
||||||
|
{ accessibility: 'no-public' },
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/no-inferrable-types': [
|
||||||
|
'error',
|
||||||
|
{ ignoreParameters: true, ignoreProperties: true },
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-type-imports': [
|
||||||
|
'error',
|
||||||
|
{ disallowTypeAnnotations: false },
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-namespace': ['error', { allowDeclarations: true }],
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'import/no-internal-modules': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allow: [
|
||||||
|
'react-dom/test-utils',
|
||||||
|
'react-dom/client',
|
||||||
|
'memfs/lib/volume.js',
|
||||||
|
'yargs/**',
|
||||||
|
'msw/node',
|
||||||
|
'**/generated/**',
|
||||||
|
'./styles/tailwind.css',
|
||||||
|
'./styles/App.css',
|
||||||
|
'./styles/style.css'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'import/no-relative-packages': 'error',
|
||||||
|
'no-cond-assign': 'error',
|
||||||
|
'no-debugger': 'error',
|
||||||
|
'no-duplicate-case': 'error',
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'CallExpression[callee.name="require"]',
|
||||||
|
message: 'Avoid using require(). Use ES6 imports instead.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'ThrowStatement > Literal:not([value=/^\\w+Error:/])',
|
||||||
|
message:
|
||||||
|
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-unsafe-finally': 'error',
|
||||||
|
'no-unused-expressions': 'off', // Disable base rule
|
||||||
|
'@typescript-eslint/no-unused-expressions': [
|
||||||
|
// Enable TS version
|
||||||
|
'error',
|
||||||
|
{ allowShortCircuit: true, allowTernary: true },
|
||||||
|
],
|
||||||
|
'no-var': 'error',
|
||||||
|
'object-shorthand': 'error',
|
||||||
|
'one-var': ['error', 'never'],
|
||||||
|
'prefer-arrow-callback': 'error',
|
||||||
|
'prefer-const': ['error', { destructuring: 'all' }],
|
||||||
|
radix: 'error',
|
||||||
|
'default-case': 'error',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
files: ['packages/*/src/**/*.test.{ts,tsx}', 'packages/**/test/**/*.test.{ts,tsx}'],
|
||||||
|
plugins: {
|
||||||
|
vitest,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...vitest.configs.recommended.rules,
|
||||||
|
'vitest/expect-expect': 'off',
|
||||||
|
'vitest/no-commented-out-tests': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
eslint.configs.recommended,
|
}, // extra settings for scripts that we run directly with node
|
||||||
...tseslint.configs.recommended,
|
{
|
||||||
reactHooks.configs['recommended-latest'],
|
files: ['./scripts/**/*.js', 'esbuild.config.js', 'packages/*/scripts/**/*.js'],
|
||||||
reactPlugin.configs.flat.recommended,
|
languageOptions: {
|
||||||
reactPlugin.configs.flat['jsx-runtime'], // Add this if you are using React 17+
|
globals: {
|
||||||
{
|
...globals.node,
|
||||||
// Settings for eslint-plugin-react
|
process: 'readonly',
|
||||||
settings: {
|
console: 'readonly',
|
||||||
react: {
|
},
|
||||||
version: 'detect',
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
files: ['packages/vscode-ide-companion/esbuild.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
process: 'readonly',
|
||||||
|
console: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
},
|
||||||
|
}, // extra settings for scripts that we run directly with node
|
||||||
|
{
|
||||||
|
files: ['packages/vscode-ide-companion/scripts/**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
process: 'readonly',
|
||||||
|
console: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
},
|
||||||
|
}, // extra settings for core package scripts
|
||||||
|
{
|
||||||
|
files: ['packages/core/scripts/**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
process: 'readonly',
|
||||||
|
console: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
},
|
||||||
|
}, // Prettier config must be last
|
||||||
|
prettierConfig, // extra settings for scripts that we run directly with node
|
||||||
|
{
|
||||||
|
files: ['./integration-tests/**/*.{js,ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
process: 'readonly',
|
||||||
|
console: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}, // Settings for docs-site directory
|
||||||
|
{
|
||||||
|
files: ['docs-site/**/*.{js,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
rules: {
|
||||||
// Import specific config
|
// Allow relaxed rules for documentation site
|
||||||
files: ['packages/cli/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
plugins: {
|
'react/prop-types': 'off',
|
||||||
import: importPlugin,
|
'react/react-in-jsx-scope': 'off',
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
'import/resolver': {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...importPlugin.configs.recommended.rules,
|
|
||||||
...importPlugin.configs.typescript.rules,
|
|
||||||
'import/no-default-export': 'warn',
|
|
||||||
'import/no-unresolved': 'off', // Disable for now, can be noisy with monorepos/paths
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
}, storybook.configs["flat/recommended"]);
|
||||||
// General overrides and rules for the project (TS/TSX files)
|
|
||||||
files: ['packages/*/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
|
|
||||||
plugins: {
|
|
||||||
import: importPlugin,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
'import/resolver': {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
...globals.es2021,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
// We use TypeScript for React components; prop-types are unnecessary
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
// General Best Practice Rules (subset adapted for flat config)
|
|
||||||
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
|
||||||
'arrow-body-style': ['error', 'as-needed'],
|
|
||||||
curly: ['error', 'multi-line'],
|
|
||||||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
|
||||||
'@typescript-eslint/consistent-type-assertions': [
|
|
||||||
'error',
|
|
||||||
{ assertionStyle: 'as' },
|
|
||||||
],
|
|
||||||
'@typescript-eslint/explicit-member-accessibility': [
|
|
||||||
'error',
|
|
||||||
{ accessibility: 'no-public' },
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
|
||||||
'@typescript-eslint/no-inferrable-types': [
|
|
||||||
'error',
|
|
||||||
{ ignoreParameters: true, ignoreProperties: true },
|
|
||||||
],
|
|
||||||
'@typescript-eslint/consistent-type-imports': [
|
|
||||||
'error',
|
|
||||||
{ disallowTypeAnnotations: false },
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-namespace': ['error', { allowDeclarations: true }],
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'import/no-internal-modules': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
allow: [
|
|
||||||
'react-dom/test-utils',
|
|
||||||
'react-dom/client',
|
|
||||||
'memfs/lib/volume.js',
|
|
||||||
'yargs/**',
|
|
||||||
'msw/node',
|
|
||||||
'**/generated/**',
|
|
||||||
'./styles/tailwind.css',
|
|
||||||
'./styles/App.css',
|
|
||||||
'./styles/style.css'
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'import/no-relative-packages': 'error',
|
|
||||||
'no-cond-assign': 'error',
|
|
||||||
'no-debugger': 'error',
|
|
||||||
'no-duplicate-case': 'error',
|
|
||||||
'no-restricted-syntax': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
selector: 'CallExpression[callee.name="require"]',
|
|
||||||
message: 'Avoid using require(). Use ES6 imports instead.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: 'ThrowStatement > Literal:not([value=/^\\w+Error:/])',
|
|
||||||
message:
|
|
||||||
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-unsafe-finally': 'error',
|
|
||||||
'no-unused-expressions': 'off', // Disable base rule
|
|
||||||
'@typescript-eslint/no-unused-expressions': [
|
|
||||||
// Enable TS version
|
|
||||||
'error',
|
|
||||||
{ allowShortCircuit: true, allowTernary: true },
|
|
||||||
],
|
|
||||||
'no-var': 'error',
|
|
||||||
'object-shorthand': 'error',
|
|
||||||
'one-var': ['error', 'never'],
|
|
||||||
'prefer-arrow-callback': 'error',
|
|
||||||
'prefer-const': ['error', { destructuring: 'all' }],
|
|
||||||
radix: 'error',
|
|
||||||
'default-case': 'error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['packages/*/src/**/*.test.{ts,tsx}', 'packages/**/test/**/*.test.{ts,tsx}'],
|
|
||||||
plugins: {
|
|
||||||
vitest,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...vitest.configs.recommended.rules,
|
|
||||||
'vitest/expect-expect': 'off',
|
|
||||||
'vitest/no-commented-out-tests': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// extra settings for scripts that we run directly with node
|
|
||||||
{
|
|
||||||
files: ['./scripts/**/*.js', 'esbuild.config.js', 'packages/*/scripts/**/*.js'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
process: 'readonly',
|
|
||||||
console: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['packages/vscode-ide-companion/esbuild.js'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
process: 'readonly',
|
|
||||||
console: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-restricted-syntax': 'off',
|
|
||||||
'@typescript-eslint/no-require-imports': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// extra settings for scripts that we run directly with node
|
|
||||||
{
|
|
||||||
files: ['packages/vscode-ide-companion/scripts/**/*.js'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
process: 'readonly',
|
|
||||||
console: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-restricted-syntax': 'off',
|
|
||||||
'@typescript-eslint/no-require-imports': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// extra settings for core package scripts
|
|
||||||
{
|
|
||||||
files: ['packages/core/scripts/**/*.js'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
process: 'readonly',
|
|
||||||
console: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-restricted-syntax': 'off',
|
|
||||||
'@typescript-eslint/no-require-imports': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Prettier config must be last
|
|
||||||
prettierConfig,
|
|
||||||
// extra settings for scripts that we run directly with node
|
|
||||||
{
|
|
||||||
files: ['./integration-tests/**/*.{js,ts,tsx}'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
process: 'readonly',
|
|
||||||
console: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Settings for docs-site directory
|
|
||||||
{
|
|
||||||
files: ['docs-site/**/*.{js,jsx}'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
// Allow relaxed rules for documentation site
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
'react/react-in-jsx-scope': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
|
||||||
2664
package-lock.json
generated
2664
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -152,7 +152,7 @@
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@qwen-code/webui": "workspace:*",
|
"@qwen-code/webui": "*",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|
|
||||||
25
packages/webui/.storybook/main.ts
Normal file
25
packages/webui/.storybook/main.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used to resolve the absolute path of a package.
|
||||||
|
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
|
||||||
|
*/
|
||||||
|
function getAbsolutePath(value: string): string {
|
||||||
|
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
|
||||||
|
}
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
|
addons: [
|
||||||
|
getAbsolutePath('@chromatic-com/storybook'),
|
||||||
|
getAbsolutePath('@storybook/addon-vitest'),
|
||||||
|
getAbsolutePath('@storybook/addon-a11y'),
|
||||||
|
getAbsolutePath('@storybook/addon-docs'),
|
||||||
|
getAbsolutePath('@storybook/addon-onboarding'),
|
||||||
|
],
|
||||||
|
framework: getAbsolutePath('@storybook/react-vite'),
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
11
packages/webui/.storybook/preview.css
Normal file
11
packages/webui/.storybook/preview.css
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import '../src/styles/variables.css';
|
||||||
15
packages/webui/.storybook/preview.ts
Normal file
15
packages/webui/.storybook/preview.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { Preview } from '@storybook/react-vite';
|
||||||
|
import './preview.css';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
428
packages/webui/WEBUI_MIGRATION_PLAN_EN.md
Normal file
428
packages/webui/WEBUI_MIGRATION_PLAN_EN.md
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
# WebUI Component Library Extraction Plan
|
||||||
|
|
||||||
|
## 1. Background and Goals
|
||||||
|
|
||||||
|
### 1.1 Background
|
||||||
|
|
||||||
|
`packages/vscode-ide-companion` is a VSCode extension whose core content is a WebView page with UI components provided by React. As the product line expands, more scenarios require building products with Web UI:
|
||||||
|
|
||||||
|
- **Chrome Browser Extension** - Sidebar chat interface
|
||||||
|
- **Web Chat Page** - Pure web application
|
||||||
|
- **Conversation Share Page** - Render conversations as static HTML
|
||||||
|
|
||||||
|
For excellent software engineering architecture, we need to unify and reuse UI components across products.
|
||||||
|
|
||||||
|
### 1.2 Goals
|
||||||
|
|
||||||
|
1. Extract components from `vscode-ide-companion/src/webview/` into an independent `@qwen-code/webui` package
|
||||||
|
2. Establish a layered architecture: Pure UI components + Business UI components
|
||||||
|
3. Use Vite + Storybook for development and component showcase
|
||||||
|
4. Abstract platform capabilities through Platform Context for cross-platform reuse
|
||||||
|
5. Provide Tailwind CSS preset to ensure UI consistency across products
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Current State Analysis
|
||||||
|
|
||||||
|
### 2.1 Current Code Structure
|
||||||
|
|
||||||
|
`packages/vscode-ide-companion/src/webview/` contains 77 files:
|
||||||
|
|
||||||
|
```
|
||||||
|
webview/
|
||||||
|
├── App.tsx # Main entry
|
||||||
|
├── components/
|
||||||
|
│ ├── icons/ # 8 icon components
|
||||||
|
│ ├── layout/ # 8 layout components
|
||||||
|
│ │ ├── ChatHeader.tsx
|
||||||
|
│ │ ├── InputForm.tsx
|
||||||
|
│ │ ├── SessionSelector.tsx
|
||||||
|
│ │ ├── EmptyState.tsx
|
||||||
|
│ │ ├── Onboarding.tsx
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── messages/ # Message display components
|
||||||
|
│ │ ├── UserMessage.tsx
|
||||||
|
│ │ ├── Assistant/
|
||||||
|
│ │ ├── MarkdownRenderer/
|
||||||
|
│ │ ├── ThinkingMessage.tsx
|
||||||
|
│ │ ├── Waiting/
|
||||||
|
│ │ └── toolcalls/ # 16 tool call components
|
||||||
|
│ ├── PermissionDrawer/ # Permission request drawer
|
||||||
|
│ └── Tooltip.tsx
|
||||||
|
├── hooks/ # Custom hooks
|
||||||
|
├── handlers/ # Message handlers
|
||||||
|
├── styles/ # CSS styles
|
||||||
|
└── utils/ # Utility functions
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Key Dependency Analysis
|
||||||
|
|
||||||
|
**Platform Coupling Points:**
|
||||||
|
|
||||||
|
- `useVSCode` hook - Calls `acquireVsCodeApi()` for message communication
|
||||||
|
- `handlers/` - Handles VSCode message protocol
|
||||||
|
- Some type definitions come from `../types/` directory
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ App.tsx (Entry) │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ hooks/ │ handlers/ │ components/ │
|
||||||
|
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
|
||||||
|
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
|
||||||
|
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
|
||||||
|
│ └─... │ └─... │ ├─messages/ │
|
||||||
|
│ │ │ └─PermDrawer/ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ VSCode API (acquireVsCodeApi) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Target Architecture
|
||||||
|
|
||||||
|
### 3.1 Layered Architecture Design
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Layer 3: Platform Adapters │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
|
||||||
|
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||||
|
├─────────┼────────────────┼────────────────┼────────────┤
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Platform Context Provider │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 2: Chat Components │
|
||||||
|
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||||
|
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
|
||||||
|
│ └────────────┘ └────────────┘ └────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 1: Primitives (Pure UI) │
|
||||||
|
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
|
||||||
|
│ └────────┘ └────────┘ └────────┘ └────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Platform Context Design
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// @qwen-code/webui/src/context/PlatformContext.ts
|
||||||
|
interface PlatformContext {
|
||||||
|
// Message communication
|
||||||
|
postMessage: (message: unknown) => void;
|
||||||
|
onMessage: (handler: (message: unknown) => void) => () => void;
|
||||||
|
|
||||||
|
// File operations
|
||||||
|
openFile?: (path: string) => void;
|
||||||
|
attachFile?: () => void;
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
login?: () => void;
|
||||||
|
|
||||||
|
// Platform info
|
||||||
|
platform: 'vscode' | 'chrome' | 'web' | 'share';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Technical Solution
|
||||||
|
|
||||||
|
### 4.1 Build Configuration (Vite Library Mode)
|
||||||
|
|
||||||
|
**Output formats:**
|
||||||
|
|
||||||
|
- ESM (`dist/index.js`) - Primary format
|
||||||
|
- CJS (`dist/index.cjs`) - Compatibility
|
||||||
|
- TypeScript declarations (`dist/index.d.ts`)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['react', 'react-dom'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Tailwind Preset Solution
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// @qwen-code/webui/tailwind.preset.js
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'app-primary': 'var(--app-primary)',
|
||||||
|
'app-background': 'var(--app-primary-background)',
|
||||||
|
'app-foreground': 'var(--app-primary-foreground)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Consumer's tailwind.config.js
|
||||||
|
module.exports = {
|
||||||
|
presets: [require('@qwen-code/webui/tailwind.preset')],
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{ts,tsx}',
|
||||||
|
'./node_modules/@qwen-code/webui/dist/**/*.js',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Storybook Configuration
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/webui/
|
||||||
|
├── .storybook/
|
||||||
|
│ ├── main.ts # Storybook config
|
||||||
|
│ ├── preview.ts # Global decorators
|
||||||
|
│ └── manager.ts # UI config
|
||||||
|
└── src/
|
||||||
|
└── stories/ # Story files
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Component Migration Classification
|
||||||
|
|
||||||
|
### 5.1 Batch 1: No-dependency Components (Ready to migrate)
|
||||||
|
|
||||||
|
| Component | Source Path | Complexity | Notes |
|
||||||
|
| ------------------ | ------------------------ | ---------- | --------------------------- |
|
||||||
|
| Icons | `components/icons/` | Low | 8 icon components, pure SVG |
|
||||||
|
| Tooltip | `components/Tooltip.tsx` | Low | Pure UI |
|
||||||
|
| WaitingMessage | `messages/Waiting/` | Low | Loading state display |
|
||||||
|
| InterruptedMessage | `messages/Waiting/` | Low | Interrupted state display |
|
||||||
|
|
||||||
|
### 5.2 Batch 2: Light-dependency Components (Need props abstraction)
|
||||||
|
|
||||||
|
| Component | Source Path | Dependency | Refactoring |
|
||||||
|
| ---------------- | ------------------------------ | ----------- | ---------------- |
|
||||||
|
| UserMessage | `messages/UserMessage.tsx` | onFileClick | Props injection |
|
||||||
|
| AssistantMessage | `messages/Assistant/` | onFileClick | Props injection |
|
||||||
|
| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | Props injection |
|
||||||
|
| MarkdownRenderer | `messages/MarkdownRenderer/` | None | Direct migration |
|
||||||
|
| EmptyState | `layout/EmptyState.tsx` | None | Direct migration |
|
||||||
|
| ChatHeader | `layout/ChatHeader.tsx` | callbacks | Props injection |
|
||||||
|
|
||||||
|
### 5.3 Batch 3: Medium-dependency Components (Need Context)
|
||||||
|
|
||||||
|
| Component | Source Path | Dependency | Refactoring |
|
||||||
|
| ------------------- | ---------------------------- | --------------------- | ----------------- |
|
||||||
|
| InputForm | `layout/InputForm.tsx` | Multiple callbacks | Context + Props |
|
||||||
|
| SessionSelector | `layout/SessionSelector.tsx` | session data | Props injection |
|
||||||
|
| CompletionMenu | `layout/CompletionMenu.tsx` | items data | Props injection |
|
||||||
|
| PermissionDrawer | `PermissionDrawer/` | callbacks | Context + Props |
|
||||||
|
| ToolCall components | `messages/toolcalls/` | Various tool displays | Modular migration |
|
||||||
|
|
||||||
|
### 5.4 Batch 4: Heavy-dependency (Keep in platform package)
|
||||||
|
|
||||||
|
| Component/Module | Notes |
|
||||||
|
| ---------------- | ------------------------------------------------- |
|
||||||
|
| App.tsx | Main entry, contains business orchestration logic |
|
||||||
|
| hooks/ | Most require platform adaptation |
|
||||||
|
| handlers/ | VSCode message handling |
|
||||||
|
| Onboarding | Authentication related, platform-specific |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Incremental Migration Strategy
|
||||||
|
|
||||||
|
### 6.1 Migration Principles
|
||||||
|
|
||||||
|
1. **Bidirectional compatibility**: During migration, vscode-ide-companion can import from both webui and local
|
||||||
|
2. **One-by-one replacement**: For each migrated component, replace import path in VSCode extension and verify
|
||||||
|
3. **No breaking changes**: Ensure the extension builds and runs normally after each migration
|
||||||
|
|
||||||
|
### 6.2 Migration Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer ──► @qwen-code/webui ──► vscode-ide-companion
|
||||||
|
│ │ │
|
||||||
|
│ 1. Copy component to webui │
|
||||||
|
│ 2. Add Story for verification │
|
||||||
|
│ 3. Export from index.ts │
|
||||||
|
│ │ │
|
||||||
|
│ └──────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 4. Update import path
|
||||||
|
│ 5. Delete original component
|
||||||
|
│ 6. Build and test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Example: Migrating Icons
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
|
||||||
|
export { FileIcon } from './FileIcons.js';
|
||||||
|
|
||||||
|
// After: Update import
|
||||||
|
import { FileIcon } from '@qwen-code/webui';
|
||||||
|
// or import { FileIcon } from '@qwen-code/webui/icons';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Task Breakdown
|
||||||
|
|
||||||
|
### Phase 0: Infrastructure Setup (Prerequisites)
|
||||||
|
|
||||||
|
- [ ] **T0-1**: Vite build configuration
|
||||||
|
- [ ] **T0-2**: Storybook configuration
|
||||||
|
- [ ] **T0-3**: Tailwind preset creation
|
||||||
|
- [ ] **T0-4**: Platform Context definition
|
||||||
|
- [ ] **T0-5**: Shared types migration
|
||||||
|
|
||||||
|
### Phase 1: Pure UI Components Migration
|
||||||
|
|
||||||
|
- [ ] **T1-1**: Icons components migration (8 files)
|
||||||
|
- [ ] **T1-2**: Tooltip component migration
|
||||||
|
- [ ] **T1-3**: WaitingMessage / InterruptedMessage migration
|
||||||
|
- [ ] **T1-4**: Basic Button/Input components refinement
|
||||||
|
|
||||||
|
### Phase 2: Message Components Migration
|
||||||
|
|
||||||
|
- [ ] **T2-1**: MarkdownRenderer migration
|
||||||
|
- [ ] **T2-2**: UserMessage migration
|
||||||
|
- [ ] **T2-3**: AssistantMessage migration
|
||||||
|
- [ ] **T2-4**: ThinkingMessage migration
|
||||||
|
|
||||||
|
### Phase 3: Layout Components Migration
|
||||||
|
|
||||||
|
- [ ] **T3-1**: ChatHeader migration
|
||||||
|
- [ ] **T3-2**: EmptyState migration
|
||||||
|
- [ ] **T3-3**: InputForm migration (requires Context)
|
||||||
|
- [ ] **T3-4**: SessionSelector migration
|
||||||
|
- [ ] **T3-5**: CompletionMenu migration
|
||||||
|
|
||||||
|
### Phase 4: Complex Components Migration
|
||||||
|
|
||||||
|
- [ ] **T4-1**: PermissionDrawer migration
|
||||||
|
- [ ] **T4-2**: ToolCall series components migration (16 files)
|
||||||
|
|
||||||
|
### Phase 5: Platform Adapters
|
||||||
|
|
||||||
|
- [ ] **T5-1**: VSCode Adapter implementation
|
||||||
|
- [ ] **T5-2**: Chrome Extension Adapter
|
||||||
|
- [ ] **T5-3**: Web/Share Page Adapter
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Risks and Considerations
|
||||||
|
|
||||||
|
### 8.1 Common Pitfalls
|
||||||
|
|
||||||
|
1. **Tailwind Class Name Tree Shaking**
|
||||||
|
- Problem: Tailwind class names may be removed after library bundling
|
||||||
|
- Solution: Consumer's `content` config needs to include `node_modules/@qwen-code/webui`
|
||||||
|
|
||||||
|
2. **CSS Variable Scope**
|
||||||
|
- Problem: Variables like `var(--app-primary)` need to be defined by consumers
|
||||||
|
- Solution: Provide default CSS variables file, or define fallbacks in Tailwind preset
|
||||||
|
|
||||||
|
3. **React Version Compatibility**
|
||||||
|
- Current vscode-ide-companion uses React 19, webui's peerDependencies is React 18
|
||||||
|
- Need to update peerDependencies to `"react": "^18.0.0 || ^19.0.0"`
|
||||||
|
|
||||||
|
4. **ESM/CJS Compatibility**
|
||||||
|
- VSCode extensions may require CJS format
|
||||||
|
- Vite needs to be configured for dual format output
|
||||||
|
|
||||||
|
### 8.2 Industry References
|
||||||
|
|
||||||
|
- **Radix UI**: Pure Headless components, styles completely controlled by consumers
|
||||||
|
- **shadcn/ui**: Copy components into project, rather than importing as dependency
|
||||||
|
- **Ant Design**: Complete component library, customization through ConfigProvider
|
||||||
|
|
||||||
|
### 8.3 Acceptance Criteria
|
||||||
|
|
||||||
|
Each migration task completion requires:
|
||||||
|
|
||||||
|
1. Component has corresponding Storybook Story
|
||||||
|
2. Import in vscode-ide-companion has been updated
|
||||||
|
3. Extension builds successfully (`npm run build:vscode`)
|
||||||
|
4. Extension functionality works (manual testing or existing tests pass)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Time Estimation
|
||||||
|
|
||||||
|
| Phase | Tasks | Estimated Days | Parallelizable |
|
||||||
|
| ------- | ----- | -------------- | -------------- |
|
||||||
|
| Phase 0 | 5 | 2-3 days | Partially |
|
||||||
|
| Phase 1 | 4 | 1-2 days | Fully |
|
||||||
|
| Phase 2 | 4 | 2-3 days | Fully |
|
||||||
|
| Phase 3 | 5 | 3-4 days | Partially |
|
||||||
|
| Phase 4 | 2 | 3-4 days | Yes |
|
||||||
|
| Phase 5 | 3 | 2-3 days | Yes |
|
||||||
|
|
||||||
|
**Total**: Approximately 13-19 person-days (sequential execution), can be reduced to 1-2 weeks with parallel work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Development and Debugging Workflow
|
||||||
|
|
||||||
|
### 10.1 Component Development Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Development Workflow │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 1. Develop/Modify Component │
|
||||||
|
│ └── Edit files in @qwen-code/webui/src/ │
|
||||||
|
│ │
|
||||||
|
│ 2. Debug with Storybook │
|
||||||
|
│ └── npm run storybook (port 6006) │
|
||||||
|
│ └── View component in isolation │
|
||||||
|
│ └── Test different props/states │
|
||||||
|
│ │
|
||||||
|
│ 3. Build Library │
|
||||||
|
│ └── npm run build │
|
||||||
|
│ └── Outputs: dist/index.js, dist/index.cjs, dist/index.d.ts │
|
||||||
|
│ │
|
||||||
|
│ 4. Use in VSCode Extension │
|
||||||
|
│ └── import { Component } from '@qwen-code/webui' │
|
||||||
|
│ └── No UI code modifications in vscode-ide-companion │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Debugging Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start Storybook for component development
|
||||||
|
cd packages/webui
|
||||||
|
npm run storybook
|
||||||
|
|
||||||
|
# Watch mode for library development
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build library for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
npm run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 Key Principles
|
||||||
|
|
||||||
|
1. **Single Source of Truth**: All UI components live in `@qwen-code/webui`
|
||||||
|
2. **Storybook First**: Debug and validate components in Storybook before integration
|
||||||
|
3. **No UI Code in Consumers**: `vscode-ide-companion` only imports and uses components
|
||||||
|
4. **Platform Abstraction**: Use `PlatformContext` for platform-specific behaviors
|
||||||
428
packages/webui/WEBUI_MIGRATION_PLAN_ZH.md
Normal file
428
packages/webui/WEBUI_MIGRATION_PLAN_ZH.md
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
# WebUI 组件库抽离计划
|
||||||
|
|
||||||
|
## 一、背景与目标
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
|
||||||
|
`packages/vscode-ide-companion` 是一个 VSCode 插件,其核心内容是一个 WebView 页面,大量 UI 部分由 React 组件提供。随着产品线扩展,越来越多的场景需要构建包含 Web UI 的产品:
|
||||||
|
|
||||||
|
- **Chrome 浏览器扩展** - 侧边栏聊天界面
|
||||||
|
- **Web 端聊天页面** - 纯 Web 应用
|
||||||
|
- **对话分享页面** - 将对话渲染为静态 HTML
|
||||||
|
|
||||||
|
对于优秀的软件工程架构,我们需要让 UI 做到统一且可复用。
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
|
||||||
|
1. 将 `vscode-ide-companion/src/webview/` 中的组件抽离到独立的 `@qwen-code/webui` 包
|
||||||
|
2. 建立分层架构:纯 UI 组件 + 业务 UI 组件
|
||||||
|
3. 使用 Vite + Storybook 进行开发和组件展示
|
||||||
|
4. 通过 Platform Context 抽象平台能力,实现跨平台复用
|
||||||
|
5. 提供 Tailwind CSS 预设,保证多产品 UI 一致性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、现状分析
|
||||||
|
|
||||||
|
### 2.1 当前代码结构
|
||||||
|
|
||||||
|
`packages/vscode-ide-companion/src/webview/` 包含 77 个文件:
|
||||||
|
|
||||||
|
```
|
||||||
|
webview/
|
||||||
|
├── App.tsx # 主入口
|
||||||
|
├── components/
|
||||||
|
│ ├── icons/ # 8 个图标组件
|
||||||
|
│ ├── layout/ # 8 个布局组件
|
||||||
|
│ │ ├── ChatHeader.tsx
|
||||||
|
│ │ ├── InputForm.tsx
|
||||||
|
│ │ ├── SessionSelector.tsx
|
||||||
|
│ │ ├── EmptyState.tsx
|
||||||
|
│ │ ├── Onboarding.tsx
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── messages/ # 消息展示组件
|
||||||
|
│ │ ├── UserMessage.tsx
|
||||||
|
│ │ ├── Assistant/
|
||||||
|
│ │ ├── MarkdownRenderer/
|
||||||
|
│ │ ├── ThinkingMessage.tsx
|
||||||
|
│ │ ├── Waiting/
|
||||||
|
│ │ └── toolcalls/ # 16 个工具调用组件
|
||||||
|
│ ├── PermissionDrawer/ # 权限请求抽屉
|
||||||
|
│ └── Tooltip.tsx
|
||||||
|
├── hooks/ # 自定义 hooks
|
||||||
|
├── handlers/ # 消息处理器
|
||||||
|
├── styles/ # CSS 样式
|
||||||
|
└── utils/ # 工具函数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 关键依赖分析
|
||||||
|
|
||||||
|
**平台耦合点:**
|
||||||
|
|
||||||
|
- `useVSCode` hook - 调用 `acquireVsCodeApi()` 进行消息通信
|
||||||
|
- `handlers/` - 处理 VSCode 消息协议
|
||||||
|
- 部分类型定义来自 `../types/` 目录
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ App.tsx (入口) │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ hooks/ │ handlers/ │ components/ │
|
||||||
|
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
|
||||||
|
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
|
||||||
|
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
|
||||||
|
│ └─... │ └─... │ ├─messages/ │
|
||||||
|
│ │ │ └─PermDrawer/ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ VSCode API (acquireVsCodeApi) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、目标架构
|
||||||
|
|
||||||
|
### 3.1 分层架构设计
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Layer 3: Platform Adapters │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
|
||||||
|
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||||
|
├─────────┼────────────────┼────────────────┼────────────┤
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Platform Context Provider │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 2: Chat Components │
|
||||||
|
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||||
|
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
|
||||||
|
│ └────────────┘ └────────────┘ └────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 1: Primitives (纯 UI) │
|
||||||
|
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
|
||||||
|
│ └────────┘ └────────┘ └────────┘ └────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Platform Context 设计
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// @qwen-code/webui/src/context/PlatformContext.ts
|
||||||
|
interface PlatformContext {
|
||||||
|
// 消息通信
|
||||||
|
postMessage: (message: unknown) => void;
|
||||||
|
onMessage: (handler: (message: unknown) => void) => () => void;
|
||||||
|
|
||||||
|
// 文件操作
|
||||||
|
openFile?: (path: string) => void;
|
||||||
|
attachFile?: () => void;
|
||||||
|
|
||||||
|
// 认证
|
||||||
|
login?: () => void;
|
||||||
|
|
||||||
|
// 平台信息
|
||||||
|
platform: 'vscode' | 'chrome' | 'web' | 'share';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、技术方案
|
||||||
|
|
||||||
|
### 4.1 构建配置(Vite Library Mode)
|
||||||
|
|
||||||
|
**输出格式:**
|
||||||
|
|
||||||
|
- ESM (`dist/index.js`) - 主要格式
|
||||||
|
- CJS (`dist/index.cjs`) - 兼容性
|
||||||
|
- TypeScript 声明 (`dist/index.d.ts`)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['react', 'react-dom'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Tailwind 预设方案
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// @qwen-code/webui/tailwind.preset.js
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'app-primary': 'var(--app-primary)',
|
||||||
|
'app-background': 'var(--app-primary-background)',
|
||||||
|
'app-foreground': 'var(--app-primary-foreground)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 消费方 tailwind.config.js
|
||||||
|
module.exports = {
|
||||||
|
presets: [require('@qwen-code/webui/tailwind.preset')],
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{ts,tsx}',
|
||||||
|
'./node_modules/@qwen-code/webui/dist/**/*.js',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Storybook 配置
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/webui/
|
||||||
|
├── .storybook/
|
||||||
|
│ ├── main.ts # Storybook 配置
|
||||||
|
│ ├── preview.ts # 全局装饰器
|
||||||
|
│ └── manager.ts # UI 配置
|
||||||
|
└── src/
|
||||||
|
└── stories/ # Story 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、组件迁移分类
|
||||||
|
|
||||||
|
### 5.1 第一批:无依赖组件(可立即迁移)
|
||||||
|
|
||||||
|
| 组件 | 来源路径 | 复杂度 | 说明 |
|
||||||
|
| ------------------ | ------------------------ | ------ | -------------------- |
|
||||||
|
| Icons | `components/icons/` | 低 | 8 个图标组件,纯 SVG |
|
||||||
|
| Tooltip | `components/Tooltip.tsx` | 低 | 纯 UI |
|
||||||
|
| WaitingMessage | `messages/Waiting/` | 低 | 加载状态展示 |
|
||||||
|
| InterruptedMessage | `messages/Waiting/` | 低 | 中断状态展示 |
|
||||||
|
|
||||||
|
### 5.2 第二批:轻度依赖组件(需要抽象 props)
|
||||||
|
|
||||||
|
| 组件 | 来源路径 | 依赖 | 改造方式 |
|
||||||
|
| ---------------- | ------------------------------ | ----------- | --------------- |
|
||||||
|
| UserMessage | `messages/UserMessage.tsx` | onFileClick | 通过 props 注入 |
|
||||||
|
| AssistantMessage | `messages/Assistant/` | onFileClick | 通过 props 注入 |
|
||||||
|
| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | 通过 props 注入 |
|
||||||
|
| MarkdownRenderer | `messages/MarkdownRenderer/` | 无 | 直接迁移 |
|
||||||
|
| EmptyState | `layout/EmptyState.tsx` | 无 | 直接迁移 |
|
||||||
|
| ChatHeader | `layout/ChatHeader.tsx` | callbacks | 通过 props 注入 |
|
||||||
|
|
||||||
|
### 5.3 第三批:中度依赖组件(需要 Context)
|
||||||
|
|
||||||
|
| 组件 | 来源路径 | 依赖 | 改造方式 |
|
||||||
|
| ---------------- | ---------------------------- | -------------- | --------------- |
|
||||||
|
| InputForm | `layout/InputForm.tsx` | 多个 callbacks | Context + Props |
|
||||||
|
| SessionSelector | `layout/SessionSelector.tsx` | session 数据 | Props 注入 |
|
||||||
|
| CompletionMenu | `layout/CompletionMenu.tsx` | items 数据 | Props 注入 |
|
||||||
|
| PermissionDrawer | `PermissionDrawer/` | 回调函数 | Context + Props |
|
||||||
|
| ToolCall 组件 | `messages/toolcalls/` | 多种工具展示 | 分模块迁移 |
|
||||||
|
|
||||||
|
### 5.4 第四批:重度依赖(保留在平台包)
|
||||||
|
|
||||||
|
| 组件/模块 | 说明 |
|
||||||
|
| ---------- | ------------------------ |
|
||||||
|
| App.tsx | 总入口,包含业务编排逻辑 |
|
||||||
|
| hooks/ | 大部分需要平台适配 |
|
||||||
|
| handlers/ | VSCode 消息处理 |
|
||||||
|
| Onboarding | 认证相关,平台特定 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、渐进式迁移策略
|
||||||
|
|
||||||
|
### 6.1 迁移原则
|
||||||
|
|
||||||
|
1. **双向兼容**:迁移期间,vscode-ide-companion 可以同时从 webui 和本地导入
|
||||||
|
2. **逐个替换**:每迁移一个组件,在 VSCode 插件中替换导入路径并验证
|
||||||
|
3. **不破坏现有功能**:确保每次迁移后插件可正常构建和运行
|
||||||
|
|
||||||
|
### 6.2 迁移流程
|
||||||
|
|
||||||
|
```
|
||||||
|
开发者 ──► @qwen-code/webui ──► vscode-ide-companion
|
||||||
|
│ │ │
|
||||||
|
│ 1. 复制组件到 webui │
|
||||||
|
│ 2. 添加 Story 验证 │
|
||||||
|
│ 3. 从 index.ts 导出 │
|
||||||
|
│ │ │
|
||||||
|
│ └──────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 4. 更新 import 路径
|
||||||
|
│ 5. 删除原组件文件
|
||||||
|
│ 6. 构建测试验证
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 示例:迁移 Icons
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
|
||||||
|
export { FileIcon } from './FileIcons.js';
|
||||||
|
|
||||||
|
// After: 修改导入
|
||||||
|
import { FileIcon } from '@qwen-code/webui';
|
||||||
|
// 或 import { FileIcon } from '@qwen-code/webui/icons';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、任务拆分
|
||||||
|
|
||||||
|
### Phase 0: 基础设施搭建(前置任务)
|
||||||
|
|
||||||
|
- [ ] **T0-1**: Vite 构建配置
|
||||||
|
- [ ] **T0-2**: Storybook 配置
|
||||||
|
- [ ] **T0-3**: Tailwind 预设创建
|
||||||
|
- [ ] **T0-4**: Platform Context 定义
|
||||||
|
- [ ] **T0-5**: 类型定义迁移(共享 types)
|
||||||
|
|
||||||
|
### Phase 1: 纯 UI 组件迁移
|
||||||
|
|
||||||
|
- [ ] **T1-1**: Icons 组件迁移(8 个文件)
|
||||||
|
- [ ] **T1-2**: Tooltip 组件迁移
|
||||||
|
- [ ] **T1-3**: WaitingMessage / InterruptedMessage 迁移
|
||||||
|
- [ ] **T1-4**: 基础 Button/Input 组件完善
|
||||||
|
|
||||||
|
### Phase 2: 消息组件迁移
|
||||||
|
|
||||||
|
- [ ] **T2-1**: MarkdownRenderer 迁移
|
||||||
|
- [ ] **T2-2**: UserMessage 迁移
|
||||||
|
- [ ] **T2-3**: AssistantMessage 迁移
|
||||||
|
- [ ] **T2-4**: ThinkingMessage 迁移
|
||||||
|
|
||||||
|
### Phase 3: 布局组件迁移
|
||||||
|
|
||||||
|
- [ ] **T3-1**: ChatHeader 迁移
|
||||||
|
- [ ] **T3-2**: EmptyState 迁移
|
||||||
|
- [ ] **T3-3**: InputForm 迁移(需要 Context)
|
||||||
|
- [ ] **T3-4**: SessionSelector 迁移
|
||||||
|
- [ ] **T3-5**: CompletionMenu 迁移
|
||||||
|
|
||||||
|
### Phase 4: 复杂组件迁移
|
||||||
|
|
||||||
|
- [ ] **T4-1**: PermissionDrawer 迁移
|
||||||
|
- [ ] **T4-2**: ToolCall 系列组件迁移(16 个文件)
|
||||||
|
|
||||||
|
### Phase 5: 平台适配器
|
||||||
|
|
||||||
|
- [ ] **T5-1**: VSCode Adapter 实现
|
||||||
|
- [ ] **T5-2**: Chrome Extension Adapter
|
||||||
|
- [ ] **T5-3**: Web/Share Page Adapter
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、风险与注意事项
|
||||||
|
|
||||||
|
### 8.1 常见坑点
|
||||||
|
|
||||||
|
1. **Tailwind 类名 Tree Shaking**
|
||||||
|
- 问题:组件库打包后 Tailwind 类名可能被移除
|
||||||
|
- 解决:消费方的 `content` 配置需要包含 `node_modules/@qwen-code/webui`
|
||||||
|
|
||||||
|
2. **CSS 变量作用域**
|
||||||
|
- 问题:`var(--app-primary)` 等变量需要在消费方定义
|
||||||
|
- 解决:提供默认 CSS 变量文件,或在 Tailwind 预设中定义 fallback
|
||||||
|
|
||||||
|
3. **React 版本兼容**
|
||||||
|
- 当前 vscode-ide-companion 使用 React 19,webui 的 peerDependencies 是 React 18
|
||||||
|
- 需要更新 peerDependencies 为 `"react": "^18.0.0 || ^19.0.0"`
|
||||||
|
|
||||||
|
4. **ESM/CJS 兼容**
|
||||||
|
- VSCode 扩展可能需要 CJS 格式
|
||||||
|
- Vite 需要配置双格式输出
|
||||||
|
|
||||||
|
### 8.2 业界参考
|
||||||
|
|
||||||
|
- **Radix UI**: 纯 Headless 组件,样式完全由消费方控制
|
||||||
|
- **shadcn/ui**: 复制组件到项目中,而非作为依赖引入
|
||||||
|
- **Ant Design**: 完整的组件库,通过 ConfigProvider 进行定制
|
||||||
|
|
||||||
|
### 8.3 验收标准
|
||||||
|
|
||||||
|
每个迁移任务完成后需要:
|
||||||
|
|
||||||
|
1. 组件有对应的 Storybook Story
|
||||||
|
2. vscode-ide-companion 中的导入已更新
|
||||||
|
3. 插件可正常构建 (`npm run build:vscode`)
|
||||||
|
4. 插件功能正常(手动测试或已有测试通过)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、预估时间
|
||||||
|
|
||||||
|
| 阶段 | 任务数 | 预估人天 | 可并行 |
|
||||||
|
| ------- | ------ | -------- | ---------- |
|
||||||
|
| Phase 0 | 5 | 2-3 天 | 部分可并行 |
|
||||||
|
| Phase 1 | 4 | 1-2 天 | 全部可并行 |
|
||||||
|
| Phase 2 | 4 | 2-3 天 | 全部可并行 |
|
||||||
|
| Phase 3 | 5 | 3-4 天 | 部分可并行 |
|
||||||
|
| Phase 4 | 2 | 3-4 天 | 可并行 |
|
||||||
|
| Phase 5 | 3 | 2-3 天 | 可并行 |
|
||||||
|
|
||||||
|
**总计**:约 13-19 人天(单人顺序执行),如果多人并行可缩短至 1-2 周
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、开发与调试流程
|
||||||
|
|
||||||
|
### 10.1 组件开发流程
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 开发工作流程 │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 1. 开发/修改组件 │
|
||||||
|
│ └── 在 @qwen-code/webui/src/ 中编辑文件 │
|
||||||
|
│ │
|
||||||
|
│ 2. 使用 Storybook 调试 │
|
||||||
|
│ └── npm run storybook (端口 6006) │
|
||||||
|
│ └── 独立查看组件 │
|
||||||
|
│ └── 测试不同的 props/状态 │
|
||||||
|
│ │
|
||||||
|
│ 3. 构建组件库 │
|
||||||
|
│ └── npm run build │
|
||||||
|
│ └── 输出: dist/index.js, dist/index.cjs, dist/index.d.ts │
|
||||||
|
│ │
|
||||||
|
│ 4. 在 VSCode 插件中使用 │
|
||||||
|
│ └── import { Component } from '@qwen-code/webui' │
|
||||||
|
│ └── vscode-ide-companion 中不再修改 UI 代码 │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 调试命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动 Storybook 进行组件开发
|
||||||
|
cd packages/webui
|
||||||
|
npm run storybook
|
||||||
|
|
||||||
|
# 监听模式进行库开发
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 类型检查
|
||||||
|
npm run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 核心原则
|
||||||
|
|
||||||
|
1. **单一数据源**: 所有 UI 组件都在 `@qwen-code/webui` 中
|
||||||
|
2. **Storybook 优先**: 在集成前先在 Storybook 中调试和验证组件
|
||||||
|
3. **消费方不修改 UI 代码**: `vscode-ide-companion` 只导入和使用组件
|
||||||
|
4. **平台抽象**: 使用 `PlatformContext` 处理平台特定行为
|
||||||
|
|
@ -2,28 +2,60 @@
|
||||||
"name": "@qwen-code/webui",
|
"name": "@qwen-code/webui",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Shared UI components for Qwen Code packages",
|
"description": "Shared UI components for Qwen Code packages",
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/index.esm.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
},
|
||||||
|
"./tailwind.preset": "./tailwind.preset.cjs",
|
||||||
|
"./styles.css": "./dist/styles.css"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"tailwind.preset.cjs"
|
||||||
|
],
|
||||||
|
"sideEffects": [
|
||||||
|
"**/*.css"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && rollup -c",
|
"dev": "vite build --watch",
|
||||||
"dev": "tsc --watch",
|
"build": "vite build",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext .ts,.tsx",
|
"lint": "eslint src --ext .ts,.tsx",
|
||||||
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
||||||
"typecheck": "tsc --noEmit"
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"autoprefixer": "^10.4.0",
|
||||||
|
"postcss": "^8.4.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"@rollup/plugin-typescript": "^11.0.0",
|
"vite": "^5.0.0",
|
||||||
"rollup": "^4.0.0",
|
"vite-plugin-dts": "^3.7.0",
|
||||||
"rollup-plugin-dts": "^6.0.0"
|
"storybook": "^10.1.11",
|
||||||
|
"@storybook/react-vite": "^10.1.11",
|
||||||
|
"@chromatic-com/storybook": "^5.0.0",
|
||||||
|
"@storybook/addon-vitest": "^10.1.11",
|
||||||
|
"@storybook/addon-a11y": "^10.1.11",
|
||||||
|
"@storybook/addon-docs": "^10.1.11",
|
||||||
|
"@storybook/addon-onboarding": "^10.1.11",
|
||||||
|
"eslint-plugin-storybook": "^10.1.11",
|
||||||
|
"playwright": "^1.57.0",
|
||||||
|
"@vitest/browser": "^3.2.4",
|
||||||
|
"@vitest/coverage-v8": "^3.2.4"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"qwen",
|
"qwen",
|
||||||
|
|
|
||||||
13
packages/webui/postcss.config.cjs
Normal file
13
packages/webui/postcss.config.cjs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import typescript from '@rollup/plugin-typescript';
|
|
||||||
import { dts } from 'rollup-plugin-dts';
|
|
||||||
import pkg from './package.json' with { type: 'json' };
|
|
||||||
|
|
||||||
const name = pkg.name;
|
|
||||||
|
|
||||||
export default [
|
|
||||||
// Browser-friendly version
|
|
||||||
{
|
|
||||||
input: 'src/index.ts',
|
|
||||||
output: {
|
|
||||||
name,
|
|
||||||
file: 'dist/index.min.js',
|
|
||||||
format: 'iife',
|
|
||||||
globals: {
|
|
||||||
react: 'React',
|
|
||||||
'react-dom': 'ReactDOM',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
external: ['react', 'react-dom'],
|
|
||||||
plugins: [
|
|
||||||
typescript({
|
|
||||||
tsconfig: './tsconfig.json',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// ES module version
|
|
||||||
{
|
|
||||||
input: 'src/index.ts',
|
|
||||||
output: [
|
|
||||||
{ file: 'dist/index.esm.js', format: 'es' },
|
|
||||||
{ file: 'dist/index.cjs.js', format: 'cjs' },
|
|
||||||
],
|
|
||||||
external: ['react', 'react-dom'],
|
|
||||||
plugins: [
|
|
||||||
typescript({
|
|
||||||
tsconfig: './tsconfig.json',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// Type declarations
|
|
||||||
{
|
|
||||||
input: 'dist/dts/src/index.d.ts',
|
|
||||||
output: [{ file: 'dist/index.d.ts', format: 'es' }],
|
|
||||||
plugins: [dts()],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
83
packages/webui/src/components/ui/Button.stories.tsx
Normal file
83
packages/webui/src/components/ui/Button.stories.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button component for user interactions.
|
||||||
|
* Supports multiple variants and sizes.
|
||||||
|
*/
|
||||||
|
const meta: Meta<typeof Button> = {
|
||||||
|
title: 'UI/Button',
|
||||||
|
component: Button,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['primary', 'secondary', 'danger'],
|
||||||
|
description: 'Visual style variant',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'md', 'lg'],
|
||||||
|
description: 'Button size',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state',
|
||||||
|
},
|
||||||
|
onClick: { action: 'clicked' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Primary Button',
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Secondary Button',
|
||||||
|
variant: 'secondary',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Danger: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Danger Button',
|
||||||
|
variant: 'danger',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Small Button',
|
||||||
|
size: 'sm',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Large Button',
|
||||||
|
size: 'lg',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Disabled Button',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
68
packages/webui/src/components/ui/Tooltip.stories.tsx
Normal file
68
packages/webui/src/components/ui/Tooltip.stories.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
import Tooltip from './Tooltip';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tooltip component for displaying contextual information on hover.
|
||||||
|
* Supports four positions: top, right, bottom, left.
|
||||||
|
*/
|
||||||
|
const meta: Meta<typeof Tooltip> = {
|
||||||
|
title: 'UI/Tooltip',
|
||||||
|
component: Tooltip,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
position: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['top', 'right', 'bottom', 'left'],
|
||||||
|
description: 'Tooltip position relative to trigger',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Tooltip content text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Top: Story = {
|
||||||
|
args: {
|
||||||
|
content: 'Tooltip on top',
|
||||||
|
position: 'top',
|
||||||
|
children: <Button>Hover me</Button>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Right: Story = {
|
||||||
|
args: {
|
||||||
|
content: 'Tooltip on right',
|
||||||
|
position: 'right',
|
||||||
|
children: <Button>Hover me</Button>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Bottom: Story = {
|
||||||
|
args: {
|
||||||
|
content: 'Tooltip on bottom',
|
||||||
|
position: 'bottom',
|
||||||
|
children: <Button>Hover me</Button>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Left: Story = {
|
||||||
|
args: {
|
||||||
|
content: 'Tooltip on left',
|
||||||
|
position: 'left',
|
||||||
|
children: <Button>Hover me</Button>,
|
||||||
|
},
|
||||||
|
};
|
||||||
89
packages/webui/src/context/PlatformContext.tsx
Normal file
89
packages/webui/src/context/PlatformContext.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform types supported by the webui library
|
||||||
|
*/
|
||||||
|
export type PlatformType = 'vscode' | 'chrome' | 'web' | 'share';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform context interface for cross-platform component reuse.
|
||||||
|
* Each platform adapter implements this interface.
|
||||||
|
*/
|
||||||
|
export interface PlatformContextValue {
|
||||||
|
/** Current platform identifier */
|
||||||
|
platform: PlatformType;
|
||||||
|
|
||||||
|
/** Send message to platform host */
|
||||||
|
postMessage: (message: unknown) => void;
|
||||||
|
|
||||||
|
/** Subscribe to messages from platform host */
|
||||||
|
onMessage: (handler: (message: unknown) => void) => () => void;
|
||||||
|
|
||||||
|
/** Open a file in the platform's editor (optional) */
|
||||||
|
openFile?: (path: string) => void;
|
||||||
|
|
||||||
|
/** Trigger file attachment dialog (optional) */
|
||||||
|
attachFile?: () => void;
|
||||||
|
|
||||||
|
/** Trigger platform login flow (optional) */
|
||||||
|
login?: () => void;
|
||||||
|
|
||||||
|
/** Copy text to clipboard */
|
||||||
|
copyToClipboard?: (text: string) => Promise<void>;
|
||||||
|
|
||||||
|
/** Platform-specific feature flags */
|
||||||
|
features?: {
|
||||||
|
canOpenFile?: boolean;
|
||||||
|
canAttachFile?: boolean;
|
||||||
|
canLogin?: boolean;
|
||||||
|
canCopy?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default noop implementation for platforms without message support
|
||||||
|
*/
|
||||||
|
const defaultContext: PlatformContextValue = {
|
||||||
|
platform: 'web',
|
||||||
|
postMessage: () => {},
|
||||||
|
onMessage: () => () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform context for accessing platform-specific capabilities
|
||||||
|
*/
|
||||||
|
export const PlatformContext =
|
||||||
|
createContext<PlatformContextValue>(defaultContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to access platform context
|
||||||
|
*/
|
||||||
|
export function usePlatform(): PlatformContextValue {
|
||||||
|
return useContext(PlatformContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider component props
|
||||||
|
*/
|
||||||
|
export interface PlatformProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
value: PlatformContextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform context provider component
|
||||||
|
*/
|
||||||
|
export function PlatformProvider({ children, value }: PlatformProviderProps) {
|
||||||
|
return (
|
||||||
|
<PlatformContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</PlatformContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
// Shared UI Components Export
|
// Shared UI Components Export
|
||||||
// Export all shared components from this package
|
// Export all shared components from this package
|
||||||
|
|
||||||
|
// Context
|
||||||
|
export {
|
||||||
|
PlatformContext,
|
||||||
|
PlatformProvider,
|
||||||
|
usePlatform,
|
||||||
|
} from './context/PlatformContext';
|
||||||
|
export type {
|
||||||
|
PlatformContextValue,
|
||||||
|
PlatformProviderProps,
|
||||||
|
PlatformType,
|
||||||
|
} from './context/PlatformContext';
|
||||||
|
|
||||||
// Layout components
|
// Layout components
|
||||||
export { default as Container } from './components/layout/Container';
|
export { default as Container } from './components/layout/Container';
|
||||||
export { default as Header } from './components/layout/Header';
|
export { default as Header } from './components/layout/Header';
|
||||||
|
|
@ -33,3 +45,10 @@ export { useLocalStorage } from './hooks/useLocalStorage';
|
||||||
// Types
|
// Types
|
||||||
export type { Theme } from './types/theme';
|
export type { Theme } from './types/theme';
|
||||||
export type { MessageProps } from './types/messages';
|
export type { MessageProps } from './types/messages';
|
||||||
|
export type { ChatMessage, MessageRole, PlanEntry } from './types/chat';
|
||||||
|
export type {
|
||||||
|
ToolCallStatus,
|
||||||
|
ToolCallLocation,
|
||||||
|
ToolCallContentItem,
|
||||||
|
ToolCallUpdate,
|
||||||
|
} from './types/toolCall';
|
||||||
|
|
|
||||||
52
packages/webui/src/styles/variables.css
Normal file
52
packages/webui/src/styles/variables.css
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default CSS variables for @qwen-code/webui
|
||||||
|
* Consumers can override these variables to customize the theme.
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
/* Primary colors */
|
||||||
|
--app-primary: #3b82f6;
|
||||||
|
--app-primary-hover: #2563eb;
|
||||||
|
--app-primary-foreground: #ffffff;
|
||||||
|
|
||||||
|
/* Background colors */
|
||||||
|
--app-background: #ffffff;
|
||||||
|
--app-background-secondary: #f3f4f6;
|
||||||
|
--app-background-tertiary: #e5e7eb;
|
||||||
|
|
||||||
|
/* Foreground/text colors */
|
||||||
|
--app-foreground: #111827;
|
||||||
|
--app-foreground-secondary: #6b7280;
|
||||||
|
--app-foreground-muted: #9ca3af;
|
||||||
|
|
||||||
|
/* Border colors */
|
||||||
|
--app-border: #e5e7eb;
|
||||||
|
--app-border-focus: #3b82f6;
|
||||||
|
|
||||||
|
/* Status colors */
|
||||||
|
--app-success: #10b981;
|
||||||
|
--app-warning: #f59e0b;
|
||||||
|
--app-error: #ef4444;
|
||||||
|
--app-info: #3b82f6;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--app-font-sans: system-ui, -apple-system, sans-serif;
|
||||||
|
--app-font-mono: ui-monospace, monospace;
|
||||||
|
|
||||||
|
/* Border radius */
|
||||||
|
--app-radius-sm: 0.25rem;
|
||||||
|
--app-radius-md: 0.375rem;
|
||||||
|
--app-radius-lg: 0.5rem;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--app-spacing-xs: 0.25rem;
|
||||||
|
--app-spacing-sm: 0.5rem;
|
||||||
|
--app-spacing-md: 1rem;
|
||||||
|
--app-spacing-lg: 1.5rem;
|
||||||
|
--app-spacing-xl: 2rem;
|
||||||
|
}
|
||||||
28
packages/webui/src/types/chat.ts
Normal file
28
packages/webui/src/types/chat.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat message role types
|
||||||
|
*/
|
||||||
|
export type MessageRole = 'user' | 'assistant' | 'system';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic chat message structure
|
||||||
|
*/
|
||||||
|
export interface ChatMessage {
|
||||||
|
role: MessageRole;
|
||||||
|
content: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plan entry for task tracking
|
||||||
|
*/
|
||||||
|
export interface PlanEntry {
|
||||||
|
content: string;
|
||||||
|
priority?: 'high' | 'medium' | 'low';
|
||||||
|
status: 'pending' | 'in_progress' | 'completed';
|
||||||
|
}
|
||||||
48
packages/webui/src/types/toolCall.ts
Normal file
48
packages/webui/src/types/toolCall.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool call status
|
||||||
|
*/
|
||||||
|
export type ToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool call location reference
|
||||||
|
*/
|
||||||
|
export interface ToolCallLocation {
|
||||||
|
path: string;
|
||||||
|
line?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool call content item
|
||||||
|
*/
|
||||||
|
export interface ToolCallContentItem {
|
||||||
|
type: 'content' | 'diff';
|
||||||
|
content?: {
|
||||||
|
type: string;
|
||||||
|
text?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
path?: string;
|
||||||
|
oldText?: string | null;
|
||||||
|
newText?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool call update data
|
||||||
|
*/
|
||||||
|
export interface ToolCallUpdate {
|
||||||
|
toolCallId: string;
|
||||||
|
kind?: string;
|
||||||
|
title?: string;
|
||||||
|
status?: ToolCallStatus;
|
||||||
|
rawInput?: unknown;
|
||||||
|
content?: ToolCallContentItem[];
|
||||||
|
locations?: ToolCallLocation[];
|
||||||
|
timestamp?: number;
|
||||||
|
}
|
||||||
11
packages/webui/tailwind.config.cjs
Normal file
11
packages/webui/tailwind.config.cjs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
presets: [require('./tailwind.preset.cjs')],
|
||||||
|
content: ['./src/**/*.{ts,tsx}'],
|
||||||
|
};
|
||||||
71
packages/webui/tailwind.preset.cjs
Normal file
71
packages/webui/tailwind.preset.cjs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @qwen-code/webui Tailwind CSS Preset
|
||||||
|
*
|
||||||
|
* This preset provides shared theme configuration for all Qwen Code products.
|
||||||
|
* Consumers should include this preset in their tailwind.config.js:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* module.exports = {
|
||||||
|
* presets: [require('@qwen-code/webui/tailwind.preset')],
|
||||||
|
* content: [
|
||||||
|
* './src/**\/*.{ts,tsx}',
|
||||||
|
* './node_modules/@qwen-code/webui/dist/**\/*.js'
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// Primary colors using CSS variables for runtime theming
|
||||||
|
'app-primary': 'var(--app-primary, #3b82f6)',
|
||||||
|
'app-primary-hover': 'var(--app-primary-hover, #2563eb)',
|
||||||
|
'app-primary-foreground': 'var(--app-primary-foreground, #ffffff)',
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
'app-background': 'var(--app-background, #ffffff)',
|
||||||
|
'app-background-secondary': 'var(--app-background-secondary, #f3f4f6)',
|
||||||
|
'app-background-tertiary': 'var(--app-background-tertiary, #e5e7eb)',
|
||||||
|
|
||||||
|
// Foreground/text colors
|
||||||
|
'app-foreground': 'var(--app-foreground, #111827)',
|
||||||
|
'app-foreground-secondary': 'var(--app-foreground-secondary, #6b7280)',
|
||||||
|
'app-foreground-muted': 'var(--app-foreground-muted, #9ca3af)',
|
||||||
|
|
||||||
|
// Border colors
|
||||||
|
'app-border': 'var(--app-border, #e5e7eb)',
|
||||||
|
'app-border-focus': 'var(--app-border-focus, #3b82f6)',
|
||||||
|
|
||||||
|
// Status colors
|
||||||
|
'app-success': 'var(--app-success, #10b981)',
|
||||||
|
'app-warning': 'var(--app-warning, #f59e0b)',
|
||||||
|
'app-error': 'var(--app-error, #ef4444)',
|
||||||
|
'app-info': 'var(--app-info, #3b82f6)',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['var(--app-font-sans, system-ui, sans-serif)'],
|
||||||
|
mono: ['var(--app-font-mono, ui-monospace, monospace)'],
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
'app-sm': 'var(--app-radius-sm, 0.25rem)',
|
||||||
|
'app-md': 'var(--app-radius-md, 0.375rem)',
|
||||||
|
'app-lg': 'var(--app-radius-lg, 0.5rem)',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
'app-xs': 'var(--app-spacing-xs, 0.25rem)',
|
||||||
|
'app-sm': 'var(--app-spacing-sm, 0.5rem)',
|
||||||
|
'app-md': 'var(--app-spacing-md, 1rem)',
|
||||||
|
'app-lg': 'var(--app-spacing-lg, 1.5rem)',
|
||||||
|
'app-xl': 'var(--app-spacing-xl, 2rem)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -14,11 +14,8 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "./dist",
|
|
||||||
"emitDeclarationOnly": true
|
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "**/*.stories.tsx"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
packages/webui/vite.config.ts
Normal file
53
packages/webui/vite.config.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import dts from 'vite-plugin-dts';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite configuration for @qwen-code/webui library
|
||||||
|
*
|
||||||
|
* Build outputs:
|
||||||
|
* - ESM: dist/index.js (primary format)
|
||||||
|
* - CJS: dist/index.cjs (compatibility)
|
||||||
|
* - TypeScript declarations: dist/index.d.ts
|
||||||
|
* - CSS: dist/styles.css (optional styles)
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
dts({
|
||||||
|
include: ['src'],
|
||||||
|
outDir: 'dist',
|
||||||
|
rollupTypes: true,
|
||||||
|
insertTypesEntry: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
name: 'QwenCodeWebUI',
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
||||||
|
output: {
|
||||||
|
globals: {
|
||||||
|
react: 'React',
|
||||||
|
'react-dom': 'ReactDOM',
|
||||||
|
'react/jsx-runtime': 'jsxRuntime',
|
||||||
|
},
|
||||||
|
assetFileNames: 'styles.[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourcemap: true,
|
||||||
|
minify: false,
|
||||||
|
cssCodeSplit: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue