diff --git a/.changes/shadcn-compatibility.md b/.changes/shadcn-compatibility.md new file mode 100644 index 0000000..2c6e948 --- /dev/null +++ b/.changes/shadcn-compatibility.md @@ -0,0 +1,5 @@ +--- +"@matechat/react": "patch:feat" +--- + +Improve Shadcn components compatibility. diff --git a/.changes/suggestion-to-func.md b/.changes/suggestion-to-func.md new file mode 100644 index 0000000..ffe7f4d --- /dev/null +++ b/.changes/suggestion-to-func.md @@ -0,0 +1,5 @@ +--- +"@matechat/react": "patch:feat" +--- + +Refactor `triggerOptions` to suggestion callback. diff --git a/package.json b/package.json index f740f88..099bbeb 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,44 @@ { + "name": "@matechat/react", + "version": "0.1.1", + "type": "module", + "description": "Front-end AI scenario solution UI library based on Huawei DevUI Design.", + "homepage": "https://github.com/DevCloudFE/matechat-react", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", "author": { "email": "fu050409@163.com", "name": "苏向夜" }, + "repository": { + "type": "git", + "url": "git+https://github.com/DevCloudFE/matechat-react.git" + }, "bugs": { "url": "https://gitcode.com/DevCloudFE/matechat-react/issues" }, + "scripts": { + "build": "vite build", + "dev": "tsx scripts/watch.ts", + "docs:build": "pnpm --filter @matechat/react-docs build", + "docs:dev": "tsx scripts/watch.ts --project docs", + "format": "biome format . --write", + "lint": "biome check .", + "lint:fix": "pnpm run lint --fix", + "playground:build": "pnpm --filter @matechat/react-playground build", + "prepublishOnly": "pnpm run build", + "preview": "pnpm --filter @matechat/react-playground dev", + "registry:build": "shadcn build --output docs/doc_build/r && tsx scripts/strip-tailwind-registry.ts docs/doc_build/r", + "test": "vitest", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^15.5.13" + }, "dependencies": { "@ai-sdk/openai": "^3.0.63", "@ai-sdk/react": "^3.0.179", @@ -19,7 +52,6 @@ "remark-math": "^6.0.0", "tailwind-merge": "^3.6.0" }, - "description": "Front-end AI scenario solution UI library based on Huawei DevUI Design.", "devDependencies": { "@biomejs/biome": "^2.4.15", "@testing-library/jest-dom": "^6.9.1", @@ -45,6 +77,10 @@ "vite-plugin-lib-inject-css": "^2.2.2", "vitest": "^4.1.6" }, + "publishConfig": { + "access": "public" + }, + "types": "./dist/*.d.ts", "exports": { ".": "./dist/index.js", "./*": "./dist/*.js" @@ -52,41 +88,5 @@ "files": [ "dist" ], - "homepage": "https://gitcode.com/DevCloudFE/matechat-react", - "license": "MIT", - "main": "./dist/index.js", - "module": "./dist/index.js", - "name": "@matechat/react", - "packageManager": "pnpm@10.14.0", - "peerDependencies": { - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^15.5.13" - }, - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/DevCloudFE/matechat-react.git" - }, - "scripts": { - "build": "vite build", - "dev": "tsx scripts/watch.ts", - "docs:build": "pnpm --filter @matechat/react-docs build", - "docs:dev": "tsx scripts/watch.ts --project docs", - "format": "biome format . --write", - "lint": "biome check .", - "lint:fix": "pnpm run lint --fix", - "playground:build": "pnpm --filter @matechat/react-playground build", - "prepublishOnly": "pnpm run build", - "preview": "pnpm --filter @matechat/react-playground dev", - "registry:build": "shadcn build --output docs/doc_build/r", - "test": "vitest", - "typecheck": "tsc --noEmit" - }, - "type": "module", - "types": "./dist/*.d.ts", - "version": "0.1.1" + "packageManager": "pnpm@10.14.0" } diff --git a/playground/src/App.tsx b/playground/src/App.tsx index 86a574c..72161a7 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -15,7 +15,7 @@ function App() { return (
@@ -57,7 +57,7 @@ function App() {
diff --git a/playground/src/communicate.tsx b/playground/src/communicate.tsx index 7acdc07..8e7736a 100644 --- a/playground/src/communicate.tsx +++ b/playground/src/communicate.tsx @@ -1,6 +1,6 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { useChat } from "@ai-sdk/react"; -import { BubbleList } from "@matechat/react"; +import { BubbleList } from "@matechat/react/bubble"; import { InputCount, Sender } from "@matechat/react/sender"; import { DirectChatTransport, ToolLoopAgent } from "ai"; import { useCallback, useMemo, useState } from "react"; @@ -39,7 +39,7 @@ function Communicate() { return (
-
+
} /> -
+
{t("attention")}
diff --git a/registry.json b/registry.json index 594c402..b72ad67 100644 --- a/registry.json +++ b/registry.json @@ -5,7 +5,7 @@ "items": [ { "name": "button", - "type": "registry:block", + "type": "registry:component", "title": "Button", "description": "MateChat Button", "files": [ @@ -17,7 +17,7 @@ }, { "name": "sender", - "type": "registry:block", + "type": "registry:component", "title": "Sender", "description": "MateChat Sender", "files": [ @@ -27,9 +27,25 @@ } ] }, + { + "name": "suggestion", + "type": "registry:component", + "title": "Suggestion", + "description": "MateChat Suggestion", + "files": [ + { + "path": "src/suggestion.tsx", + "type": "registry:component" + }, + { + "path": "src/list.tsx", + "type": "registry:component" + } + ] + }, { "name": "prompt", - "type": "registry:block", + "type": "registry:component", "title": "Prompt", "description": "MateChat Prompt", "files": [ @@ -39,6 +55,18 @@ } ] }, + { + "name": "file-upload", + "type": "registry:component", + "title": "File Upload", + "description": "MateChat File Upload", + "files": [ + { + "path": "src/file-upload.tsx", + "type": "registry:component" + } + ] + }, { "name": "bubble", "type": "registry:block", @@ -46,16 +74,16 @@ "description": "MateChat Bubble", "files": [ { - "path": "src/bubble/index.tsx", + "path": "src/bubble.tsx", "type": "registry:component" }, { - "path": "src/bubble/markdown.tsx", + "path": "src/markdown.tsx", "type": "registry:component" }, { - "path": "src/bubble/hooks.tsx", - "type": "registry:component" + "path": "src/hooks/use-theme.ts", + "type": "registry:hook" } ], "dependencies": [ diff --git a/scripts/strip-tailwind-registry.ts b/scripts/strip-tailwind-registry.ts new file mode 100644 index 0000000..c0b043b --- /dev/null +++ b/scripts/strip-tailwind-registry.ts @@ -0,0 +1,35 @@ +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const registryDir = process.argv[2]; +if (!registryDir) { + console.error( + "Usage: tsx scripts/strip-tailwind-registry.ts ", + ); + process.exit(1); +} + +const tailwindImportRE = /^import\s+["']\.\.?\/tailwind\.css["'];?\s*\n?/m; + +let cleaned = 0; +for (const file of readdirSync(registryDir)) { + if (!file.endsWith(".json") || file === "registry.json") continue; + const filePath = join(registryDir, file); + const raw = readFileSync(filePath, "utf-8"); + const data = JSON.parse(raw); + + if (!data.files) continue; + + for (const f of data.files) { + if (!f.content) continue; + const original = f.content; + f.content = f.content.replace(tailwindImportRE, ""); + if (f.content !== original) { + cleaned++; + } + } + + writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8"); +} + +console.log(`Cleaned ${cleaned} tailwind.css imports across ${registryDir}`); diff --git a/src/bubble/index.tsx b/src/bubble.tsx similarity index 98% rename from src/bubble/index.tsx rename to src/bubble.tsx index 4b94f26..4fb91ec 100644 --- a/src/bubble/index.tsx +++ b/src/bubble.tsx @@ -1,7 +1,5 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import "../tailwind.css"; - import type { UIMessage } from "ai"; +import { cva, type VariantProps } from "class-variance-authority"; import clsx from "clsx"; import type React from "react"; import { memo, useCallback, useEffect, useRef } from "react"; @@ -9,10 +7,12 @@ import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import { twMerge } from "tailwind-merge"; -import { BlockQuote, CodeBlock, Heading, Link } from "./markdown"; +import { BlockQuote, CodeBlock, Heading, Link } from "@/markdown"; + +import "./tailwind.css"; const bubbleVariants = cva( - "flex flex-col gap-1 justify-center rounded-lg dark:text-gray-200 text-gray-800 max-w-full whitespace-pre-wrap break-words", + "flex flex-col gap-1 justify-center rounded-lg dark:text-gray-200 text-gray-800 max-w-full whitespace-pre-wrap wrap-break-word", { variants: { size: { diff --git a/src/button.tsx b/src/button.tsx index f7f0f58..963ebe2 100644 --- a/src/button.tsx +++ b/src/button.tsx @@ -1,10 +1,10 @@ -import "./tailwind.css"; - import { cva, type VariantProps } from "class-variance-authority"; import clsx from "clsx"; import type * as React from "react"; import { twMerge } from "tailwind-merge"; +import "./tailwind.css"; + const buttonVariants = cva( "cursor-pointer inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors text-white dark:text-white/80", { diff --git a/src/file-upload.tsx b/src/file-upload.tsx index 9015282..5d01e56 100644 --- a/src/file-upload.tsx +++ b/src/file-upload.tsx @@ -2,7 +2,7 @@ import clsx from "clsx"; import type React from "react"; import { useCallback, useRef } from "react"; import { twMerge } from "tailwind-merge"; -import Appendix from "./icons/appendix.svg"; +import Appendix from "@/icons/appendix.svg"; export interface FileUploadProps extends React.ComponentProps<"button"> { onFilesSelect?: (files: File[]) => void; diff --git a/src/bubble/hooks.tsx b/src/hooks/use-theme.ts similarity index 95% rename from src/bubble/hooks.tsx rename to src/hooks/use-theme.ts index ef4ab07..dc2c9fb 100644 --- a/src/bubble/hooks.tsx +++ b/src/hooks/use-theme.ts @@ -20,7 +20,5 @@ export const useTheme = () => { }; }, []); - console.log(isDark); - return { isDark }; }; diff --git a/src/index.ts b/src/index.ts index d0939ad..d874275 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ export type { UseChatOptions } from "@ai-sdk/react"; export { useChat } from "@ai-sdk/react"; -export * from "./bubble"; -export * from "./button"; -export * from "./file-upload"; -export * from "./list"; -export * from "./prompt"; -export * from "./sender"; +export * from "@/bubble"; +export * from "@/button"; +export * from "@/file-upload"; +export * from "@/list"; +export * from "@/prompt"; +export * from "@/sender"; +export * from "@/suggestion"; diff --git a/src/bubble/markdown.tsx b/src/markdown.tsx similarity index 98% rename from src/bubble/markdown.tsx rename to src/markdown.tsx index 168e01b..345122b 100644 --- a/src/bubble/markdown.tsx +++ b/src/markdown.tsx @@ -5,7 +5,7 @@ import { oneLight, vscDarkPlus, } from "react-syntax-highlighter/dist/esm/styles/prism"; -import { useTheme } from "./hooks"; +import { useTheme } from "@/hooks/use-theme"; export interface HeadingProps extends React.ComponentProps<"h1"> { level: 1 | 2 | 3 | 4 | 5 | 6; diff --git a/src/prompt.tsx b/src/prompt.tsx index a24b53f..3136f38 100644 --- a/src/prompt.tsx +++ b/src/prompt.tsx @@ -1,10 +1,10 @@ -import "./tailwind.css"; - import { cva, type VariantProps } from "class-variance-authority"; import clsx from "clsx"; import type * as React from "react"; import { twMerge } from "tailwind-merge"; +import "./tailwind.css"; + const promptsVariants = cva("flex", { variants: { size: { diff --git a/src/sender.tsx b/src/sender.tsx index 832f446..033dd51 100644 --- a/src/sender.tsx +++ b/src/sender.tsx @@ -1,10 +1,8 @@ -import "./tailwind.css"; - import clsx from "clsx"; import { useCallback, useEffect, useRef, useState } from "react"; import { twMerge } from "tailwind-merge"; -import type { TriggerConfig } from "./suggestion"; -import Suggestion from "./suggestion"; + +import "./tailwind.css"; export interface InputCountProps extends React.ComponentProps<"span"> { count: number; @@ -60,7 +58,11 @@ export interface SenderProps extends React.ComponentProps<"div"> { onMessageChange?: (message: string) => void; onSend?: () => void; toolbar?: React.ReactNode; - triggerConfigs?: TriggerConfig[]; + suggestion?: (context: { + message: string; + textareaRef: React.RefObject; + onInject: (text: string, position: number) => void; + }) => React.ReactNode; } export function Sender({ @@ -71,7 +73,7 @@ export function Sender({ sendMessage, onSend, toolbar, - triggerConfigs, + suggestion, ...props }: SenderProps) { const textareaRef = useRef(null); @@ -150,12 +152,11 @@ export function Sender({ )} {...props} > - + {suggestion?.({ + message, + textareaRef, + onInject: handleTextInject, + })}