├── src ├── vite-env.d.ts ├── index.ts ├── assets │ ├── chatbox.gif │ ├── icons │ │ ├── pen.svg │ │ └── expand.svg │ └── parlant-logo-full.svg ├── theme.ts ├── utils │ ├── object.ts │ └── utils.ts ├── components │ ├── ui │ │ ├── Textarea.tsx │ │ ├── Markdown.tsx │ │ ├── Button.tsx │ │ └── Popover.tsx │ └── chat │ │ ├── footer │ │ └── ChatFooter.tsx │ │ ├── header │ │ └── ChatHeader.tsx │ │ ├── input │ │ └── ChatInput.tsx │ │ ├── message │ │ └── Message.tsx │ │ ├── message-list │ │ └── MessageList.tsx │ │ └── Chat.tsx └── App.tsx ├── public ├── parlant-logo.png └── icons │ └── send.svg ├── tsconfig.tsbuildinfo ├── .gitignore ├── index.html ├── tsconfig.json ├── eslint.config.js ├── biome.json ├── LICENSE ├── vite.config.ts ├── package.json └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Chatbox from '@/App'; 2 | 3 | export default Chatbox; 4 | -------------------------------------------------------------------------------- /src/assets/chatbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emcie-co/parlant-chat-react/HEAD/src/assets/chatbox.gif -------------------------------------------------------------------------------- /public/parlant-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emcie-co/parlant-chat-react/HEAD/public/parlant-logo.png -------------------------------------------------------------------------------- /src/theme.ts: -------------------------------------------------------------------------------- 1 | export const COLORS = { 2 | primaryText: '#151515', 3 | darkGrey: '#282828', 4 | accent: '#006E53', 5 | mutedText: '#A9A9A9', 6 | backgroundLight: '#F5F9F7', 7 | }; -------------------------------------------------------------------------------- /public/icons/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./src/app.tsx","./src/index.ts","./src/vite-env.d.ts","./src/components/chat/chat.tsx","./src/components/chat/message/message.tsx","./src/components/ui/button.tsx","./src/components/ui/shadow-wrapper.tsx","./src/components/ui/textarea.tsx","./src/utils/object.ts","./src/utils/utils.ts"],"version":"5.7.3"} -------------------------------------------------------------------------------- /src/utils/object.ts: -------------------------------------------------------------------------------- 1 | export function groupBy(array: T[], keyFn: (item: T) => string | number): Record { 2 | return array.reduce((result: Record, item: T) => { 3 | let key = keyFn(item); 4 | if (!key) key = key?.toString(); 5 | if (!result[key]) { 6 | result[key] = []; 7 | } 8 | result[key].push(item); 9 | return result; 10 | }, {}); 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # VS Code settings 27 | .vscode/settings.json 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDirs": ["."], 4 | // "module": "ES2022", 5 | // "moduleResolution": "Bundler", 6 | "target": "ES2015", 7 | "module": "ESNext", 8 | "moduleResolution": "Node", 9 | "strict": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "outDir": "dist", 13 | "allowJs": true, 14 | "declaration": true, 15 | "declarationMap": true, 16 | "emitDeclarationOnly": true, 17 | "skipLibCheck": true, 18 | "esModuleInterop": true, 19 | "allowSyntheticDefaultImports": true, 20 | "lib": ["DOM", "ESNext", "ES2022"], 21 | "baseUrl": ".", 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | }, 25 | "jsx": "react-jsx" 26 | }, 27 | "include": ["src/**/*"], 28 | "exclude": ["dist", "node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import reactHooks from 'eslint-plugin-react-hooks'; 3 | import reactRefresh from 'eslint-plugin-react-refresh'; 4 | import globals from 'globals'; 5 | 6 | export default [ 7 | {ignores: ['dist']}, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: {jsx: true}, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', {varsIgnorePattern: '^[A-Z_]'}], 27 | 'react-refresh/only-export-components': ['warn', {allowConstantExport: true}], 28 | }, 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true 10 | } 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "formatWithErrors": false, 15 | "indentStyle": "space", 16 | "indentWidth": 2, 17 | "lineWidth": 80 18 | }, 19 | "javascript": { 20 | "formatter": { 21 | "quoteStyle": "single" 22 | } 23 | }, 24 | "files": { 25 | "include": [ 26 | "src/**/*.{ts,tsx,js,jsx}" 27 | ], 28 | "ignore": [ 29 | "**/node_modules/**", 30 | "**/dist/**" 31 | ] 32 | }, 33 | "vcs": { 34 | "enabled": true, 35 | "clientKind": "git", 36 | "useIgnoreFile": true 37 | } 38 | } -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import {clsx, type ClassValue} from 'clsx'; 2 | 3 | export function cn(...inputs: ClassValue[]) { 4 | return clsx(inputs); 5 | } 6 | 7 | export function messageSound(reversed?: boolean) { 8 | const AudioCtx = window.AudioContext || (window as any)['webkitAudioContext']; 9 | const ctx = new AudioCtx(); 10 | 11 | const blip = (startTime: number, freq: number) => { 12 | const osc = ctx.createOscillator(); 13 | const gain = ctx.createGain(); 14 | osc.type = 'sine'; 15 | osc.frequency.setValueAtTime(freq, startTime); 16 | gain.gain.setValueAtTime(0.5, startTime); 17 | gain.gain.exponentialRampToValueAtTime(0.001, startTime + 0.15); 18 | osc.connect(gain); 19 | gain.connect(ctx.destination); 20 | osc.start(startTime); 21 | osc.stop(startTime + 0.15); 22 | }; 23 | 24 | const now = ctx.currentTime; 25 | if (reversed) { 26 | blip(now, 660); 27 | blip(now + 0.2, 880); 28 | return; 29 | } 30 | blip(now, 880); 31 | blip(now + 0.2, 660); 32 | } -------------------------------------------------------------------------------- /src/components/ui/Textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { createUseStyles } from 'react-jss'; 4 | import clsx from 'clsx'; 5 | 6 | const useStyles = createUseStyles({ 7 | textArea: { 8 | display: 'flex', 9 | minHeight: '80px', 10 | width: '100%', 11 | borderRadius: '6px', 12 | border: '1px solid', 13 | paddingInline: '0.75rem', 14 | paddingBlock: '0.5rem', 15 | fontSize: '0.875rem', 16 | fontFamily: 'Inter !important', 17 | outline: 'none', 18 | } 19 | }); 20 | 21 | export interface TextareaProps 22 | extends React.TextareaHTMLAttributes {} 23 | 24 | const Textarea = React.forwardRef( 25 | ({ className, ...props }, ref) => { 26 | const classes = useStyles(); 27 | return ( 28 |