├── .gitignore
├── README.md
└── apps
├── functional_chat
├── .editorconfig
├── .env
├── .eslintrc.json
├── .lintstagedrc.mjs
├── .nvm
├── .prettierignore
├── .prettierrc
├── README.md
├── app
│ ├── (routes)
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ │ ├── chat
│ │ │ ├── ChatInferenceModule.tsx
│ │ │ ├── ChatScrollAnchor.tsx
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── chat-actions.tsx
│ │ │ ├── chat-avatar.tsx
│ │ │ ├── chat-input.tsx
│ │ │ ├── chat-message.tsx
│ │ │ ├── chat-messages.tsx
│ │ │ ├── chat.interface.ts
│ │ │ ├── empty-llm-state.tsx
│ │ │ ├── index.ts
│ │ │ ├── markdown.tsx
│ │ │ ├── toggle.tsx
│ │ │ └── use-copy-to-clipboard.tsx
│ │ ├── common
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── code.module.css
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── ui
│ │ │ ├── alert.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── form.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── select.tsx
│ │ │ └── textarea.tsx
│ ├── hooks
│ │ ├── use-at-bottom.ts
│ │ └── useAutosizeTextArea.ts
│ ├── lib
│ │ ├── cache.ts
│ │ └── utils.ts
│ └── styles
│ │ ├── globals.css
│ │ └── mdx.css
├── components.json
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│ └── api
│ │ ├── audio-transcription.ts
│ │ ├── chatCompletion.ts
│ │ ├── functionSpecs.ts
│ │ ├── functions
│ │ ├── flightPrices.ts
│ │ ├── generateImage.ts
│ │ ├── imageProcessing.ts
│ │ ├── newsSearch.ts
│ │ ├── popularDestinations.ts
│ │ ├── renderChart.ts
│ │ ├── stockQuote.ts
│ │ ├── weatherHistory.ts
│ │ └── webSearch.ts
│ │ └── upload-image.ts
├── public
│ ├── images
│ │ ├── LogoWithName.svg
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ └── favicon.ico
│ └── site.webmanifest
├── tailwind.config.js
└── tsconfig.json
├── transcription_chat
├── .eslintrc.json
├── .gitignore
├── README.md
├── app
│ ├── api
│ │ └── qa
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components.json
├── components
│ ├── DocumentPicker.tsx
│ ├── QASection.tsx
│ └── ui
│ │ ├── button.tsx
│ │ ├── carousel.tsx
│ │ └── input.tsx
├── lib
│ └── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│ ├── documents
│ │ ├── MPRA-POA.pdf
│ │ │ ├── 1.jpg
│ │ │ └── 2.jpg
│ │ ├── NY_State_DOH_1.pdf
│ │ │ ├── 1.jpg
│ │ │ ├── 10.jpg
│ │ │ ├── 11.jpg
│ │ │ ├── 12.jpg
│ │ │ ├── 2.jpg
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ ├── 5.jpg
│ │ │ ├── 6.jpg
│ │ │ ├── 7.jpg
│ │ │ ├── 8.jpg
│ │ │ └── 9.jpg
│ │ └── brown-fd.pdf
│ │ │ ├── 1.jpg
│ │ │ ├── 10.jpg
│ │ │ ├── 2.jpg
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ ├── 5.jpg
│ │ │ ├── 6.jpg
│ │ │ ├── 7.jpg
│ │ │ ├── 8.jpg
│ │ │ └── 9.jpg
│ ├── fireworks-logo.png
│ ├── mongodb-logo.png
│ ├── next.svg
│ └── vercel.svg
├── tailwind.config.ts
├── tsconfig.json
├── utils
│ └── api.ts
└── yarn.lock
└── transcription_demo
├── .env
├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── api
│ └── transcribe
│ │ └── route.ts
├── components
│ ├── DocumentViewer.tsx
│ ├── FileUpload.tsx
│ ├── Fragment.tsx
│ └── Page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
├── lib
│ └── transcribe.ts
├── page.tsx
└── types
│ └── index.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.js
├── postcss.config.mjs
├── public
└── fireworks-ai-wordmark-color-dark.svg
├── tailwind.config.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env.development.local
77 | .env.test.local
78 | .env.production.local
79 | .env.local
80 |
81 | # parcel-bundler cache (https://parceljs.org/)
82 | .cache
83 | .parcel-cache
84 |
85 | # Next.js build output
86 | .next
87 | out
88 |
89 | # Nuxt.js build / generate output
90 | .nuxt
91 | dist
92 |
93 | # Gatsby files
94 | .cache/
95 | # Comment in the public line in if your project uses Gatsby and not Next.js
96 | # https://nextjs.org/blog/next-9-1#public-directory-support
97 | # public
98 |
99 | # vuepress build output
100 | .vuepress/dist
101 |
102 | # vuepress v2.x temp and cache directory
103 | .temp
104 | .cache
105 |
106 | # Docusaurus cache and generated files
107 | .docusaurus
108 |
109 | # Serverless directories
110 | .serverless/
111 |
112 | # FuseBox cache
113 | .fusebox/
114 |
115 | # DynamoDB Local files
116 | .dynamodb/
117 |
118 | # TernJS port file
119 | .tern-port
120 |
121 | # Stores VSCode versions used for testing VSCode extensions
122 | .vscode-test
123 |
124 | # yarn v2
125 | .yarn/cache
126 | .yarn/unplugged
127 | .yarn/build-state.yml
128 | .yarn/install-state.gz
129 | .pnp.*
130 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fireworks Demo Apps
2 |
3 | This repository contains demo apps illustrating the capabilities of large language and image models. Individual apps are self-contained and easy to expand.
4 |
5 | Have fun!
6 |
7 | ## List of apps ( :construction: more apps coming soon)
8 |
9 | - [Functional chat](https://github.com/fw-ai/forge/tree/main/apps/functional_chat) - an LLM powered chat with function calling capabilities.
10 |
11 | ## External apps leveraging Fireworks models
12 |
13 | - [VexaSearch](https://github.com/n4ze3m/vexasearch/tree/main) - a simple AI-powered search application designed to determine the actions to perform based on a function call.
14 |
--------------------------------------------------------------------------------
/apps/functional_chat/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | trim_trailing_whitespace = true
9 |
10 | [*.{ts,js,json,yml}]
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/apps/functional_chat/.env:
--------------------------------------------------------------------------------
1 | # Global settings
2 | DEFAULT_CACHE_TTL_SEC=0
3 |
4 | # Functions
5 | ACTIVE_FUNCTIONS="generateImage,renderChart,stockQuote,newsSearch,imageProcessing"
6 |
7 | # Chat model
8 | # FIREWORKS_CHAT_MODEL="accounts/fireworks/models/firefunction-v1"
9 | FIREWORKS_CHAT_MODEL="accounts/fireworks/models/firefunction-v2"
10 | FIREWORKS_IMAGE_GEN_MODEL="accounts/fireworks/models/stable-diffusion-xl-1024-v1-0"
11 | FIREWORKS_VISION_MODEL="accounts/fireworks/models/llama-v3p2-11b-vision-instruct"
12 |
13 | # API keys
14 | FIREWORKS_API_KEY="set the key in .env.local"
15 | BING_SEARCH_KEY="set the key in .env.local"
16 | ALPHAVANTAGE_KEY="set the key in .env.local"
17 | TRAVELPAYOUTS_KEY="set the key in .env.local"
18 | RAPIDAPI_KEY="set the key in .env.local"
19 | VISUALCROSSING_KEY="set the key in .env.local"
20 |
--------------------------------------------------------------------------------
/apps/functional_chat/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "project": [
5 | "./tsconfig.json"
6 | ]
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "extends": [
12 | "plugin:@next/next/recommended"
13 | ],
14 | "settings": {
15 | "next": {
16 | "rootDir": "."
17 | }
18 | },
19 | "rules": {
20 | "import/unambiguous": "off",
21 | "import/no-default-export": "off",
22 | "promise/prefer-await-to-then": "off",
23 | "padding-line-between-statements": "off",
24 | "no-param-reassign": "off",
25 | "no-empty": "off",
26 | "class-methods-use-this": "off",
27 | "unicorn/no-null": "off",
28 | "import/extensions": "off",
29 | "import/no-unassigned-import": "off",
30 | "no-implicit-coercion": "off",
31 | "line-comment-position": "off",
32 | "no-inline-comments": "off",
33 | "camelcase": "off",
34 | "@typescript-eslint/prefer-nullish-coalescing": "off",
35 | "@typescript-eslint/no-unsafe-argument": "off",
36 | "@typescript-eslint/no-empty-interface": "warn",
37 | "@typescript-eslint/strict-boolean-expressions": "off",
38 | "@typescript-eslint/consistent-type-definitions": "off",
39 | "import/no-extraneous-dependencies": "off",
40 | "@typescript-eslint/no-floating-promises": "off",
41 | "@typescript-eslint/no-unnecessary-type-arguments": "off",
42 | "@typescript-eslint/no-unnecessary-condition": "off",
43 | "@typescript-eslint/no-explicit-any": "off",
44 | "@typescript-eslint/no-unsafe-assignment": "off",
45 | "@typescript-eslint/no-use-before-define": "off",
46 | "@typescript-eslint/no-unsafe-member-access": "off",
47 | "@typescript-eslint/no-unsafe-call": "off",
48 | "@typescript-eslint/no-unsafe-return": "off",
49 | "@typescript-eslint/non-nullable-type-assertion-style": "off",
50 | "@typescript-eslint/no-misused-promises": [
51 | "error",
52 | {
53 | "checksVoidReturn": false
54 | }
55 | ]
56 | }
57 | }
--------------------------------------------------------------------------------
/apps/functional_chat/.lintstagedrc.mjs:
--------------------------------------------------------------------------------
1 | const joinFileNames = (files) => files.map((name) => `"${name}"`).join(' ');
2 |
3 | export default {
4 | 'ts/**/*.ts?(x)': () => 'yarn ts:check:all',
5 | 'ts/**/*.{js,jsx,ts,tsx}': (files) => `yarn ts:lint:fix ${joinFileNames(files)}`,
6 | };
7 |
--------------------------------------------------------------------------------
/apps/functional_chat/.nvm:
--------------------------------------------------------------------------------
1 | v18.17.0
--------------------------------------------------------------------------------
/apps/functional_chat/.prettierignore:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------------------------------------------------------------------------
2 | # Keep this section in sync with .gitignore/.prettierignore
3 | #-------------------------------------------------------------------------------------------------------------------
4 |
5 | ## Python
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | env/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | out/
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | .hypothesis/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 |
62 | # Flask stuff:
63 | instance/
64 | .webassets-cache
65 |
66 | # Scrapy stuff:
67 | .scrapy
68 |
69 | # Sphinx documentation
70 | docs/_build/
71 |
72 | # PyBuilder
73 | target/
74 |
75 | # Jupyter Notebook
76 | .ipynb_checkpoints
77 |
78 | # pyenv
79 | .python-version
80 |
81 | # celery beat schedule file
82 | celerybeat-schedule
83 |
84 | # SageMath parsed files
85 | *.sage.py
86 |
87 | # virtualenv
88 | .venv
89 | venv/
90 | ENV/
91 |
92 | # Spyder project settings
93 | .spyderproject
94 | .spyproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # mkdocs documentation
100 | /site
101 |
102 | # mypy
103 | .mypy_cache/
104 |
105 | # yarn no zero-installs
106 | node_modules/
107 | .pnp.*
108 | .yarn/*
109 | !.yarn/patches
110 | !.yarn/plugins
111 | !.yarn/releases
112 | !.yarn/sdks
113 | !.yarn/versions
114 |
115 | # macOS
116 | .DS_Store
117 |
118 | # ESLint
119 | .eslintcache
120 |
121 | # Typescript
122 | tsconfig.tsbuildinfo
123 |
124 | # NextJS
125 | .next
126 | ts/web/.vscode
127 |
128 | # Firebase cache
129 | .firebase/
130 | .packages/
131 |
132 | # vsix files
133 | *.vsix
134 |
135 | # Vercel
136 | .vercel
137 |
138 | #-------------------------------------------------------------------------------------------------------------------
139 | # Prettier-specific overrides
140 | #-------------------------------------------------------------------------------------------------------------------
141 |
142 | # Yarn files
143 | .yarn
144 | yarn.lock
145 | .yarnrc.yml
146 |
147 | # Generated
148 | ts/protos/
149 |
150 | # Directories not to be formatted
151 | fireworks-protos/
152 | flint-instrumentation/
153 | flint-lsp-router/
154 | flint-protos/
155 | worker-vscode-extension/
156 |
--------------------------------------------------------------------------------
/apps/functional_chat/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "singleQuote": true,
4 | "trailingComma": "all"
5 | }
6 |
--------------------------------------------------------------------------------
/apps/functional_chat/README.md:
--------------------------------------------------------------------------------
1 | # Functional Chat Demo App ( :rocket: [Try it!](http://functional-chat.fireworks.ai))
2 |
3 | https://github.com/fw-ai/forge/assets/5000576/ce83463e-e898-46a9-9e84-d13d4a12d474
4 |
5 | ## Quick start
6 |
7 | If you need to install Node.js, follow [these instructions](https://github.com/fw-ai/forge/tree/main/apps/functional_chat#configuring-the-environment).
8 |
9 | Install the dependencies:
10 | ```bash
11 | npm install
12 | ```
13 |
14 | Either edit `.env` or create a new file called `.env.local` (recommended) with api keys to the services you want to call.
15 |
16 | Run the server in dev mode:
17 | ```bash
18 | npm run dev
19 | ```
20 |
21 | ## Capabilities
22 |
23 | A simple chat with function calling. Functions can perform tasks that typically involve calling external services. An LLM interprets user messages and decides which function to call and with what parameter values. The model is capable of multi-turn conversations combining the output from multiple functions, refining the calls based on additional instructions and making new calls with parameter values extracted from other function output.
24 |
25 | ## Included functions
26 |
27 | The demo app includes the following functions (new functions are easy to add - see the next section):
28 | - image generation with an SDXL model,
29 | - getting stock quotes curtesy of [alphavantage](https://www.alphavantage.co/),
30 | - charting datasets with chart.js curtesy of [quickchart](https://quickchart.io).
31 |
32 | ## Adding a new function
33 |
34 | To add a new function to the chat follow these steps:
35 | 1. Create a file under [functions](https://github.com/fw-ai/forge/tree/main/apps/functional_chat/pages/api/functions) directory. By convention, the file name follows the function name,
36 | 2. Implement a `handler` that supports `spec` and `call` query actions. `spec` should return the description of the function parameters in [`json_schema`](https://json-schema.org/) format. `call` performs the function call - often times invoking an external API - and returns the responses in a descriptive format (typically JSON). There are several function spec and call [examples](https://github.com/fw-ai/forge/tree/main/apps/functional_chat/pages/api/functions) that you can copy and modify based on your needs.
37 | 3. Include the name of the newly added function in the [`ACTIVE_FUNCTIONS`](https://github.com/fw-ai/forge/blob/main/apps/functional_chat/.env) environment variable.
38 | 4. Redeploy your app and have fun!
39 |
40 | ## Configuring the environment
41 |
42 | If you are starting from scratch, here are the steps to follow to set up a Node.js on your host:
43 |
44 | 1. [Install](https://github.com/nvm-sh/nvm#installing-and-updating) Node Version Manager (nvm)
45 |
46 | 2. Install the latest Node.js:
47 | ```bash
48 | nvm install stable
49 | ```
50 |
51 | 3. Optional: to make this version your default version, run the following:
52 | ```bash
53 | nvm alias default stable
54 | ```
55 |
56 | 4. Optional: To check what version of Node.js that you're running, run the following:
57 | ```bash
58 | node -v
59 | ```
60 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/(routes)/layout.tsx:
--------------------------------------------------------------------------------
1 | import '~/styles/globals.css';
2 |
3 | export interface RootLayoutProps {
4 | readonly children: React.ReactNode;
5 | }
6 | export default async function RootLayout({ children }: RootLayoutProps) {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | {children}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/(routes)/page.tsx:
--------------------------------------------------------------------------------
1 | import { ChatInferenceModule } from '~/components/chat/ChatInferenceModule';
2 | /* eslint camelcase: 0 */
3 | import { cn } from '~/lib/utils';
4 |
5 | // eslint-disable-next-line complexity
6 | export default async function Page() {
7 | return (
8 | <>
9 |
18 | >
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/ChatScrollAnchor.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 |
5 | import { useInView } from 'react-intersection-observer';
6 | import { useAtBottom } from '~/hooks/use-at-bottom';
7 |
8 | interface ChatScrollAnchorProps {
9 | trackVisibility?: boolean;
10 | }
11 |
12 | export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
13 | const isAtBottom = useAtBottom();
14 | const { ref, entry, inView } = useInView({
15 | trackVisibility,
16 | delay: 100,
17 | rootMargin: '0px 0px -150px 0px',
18 | });
19 |
20 | React.useEffect(() => {
21 | if (isAtBottom && trackVisibility && !inView) {
22 | entry?.target.scrollIntoView({
23 | block: 'start',
24 | });
25 | }
26 | }, [inView, entry, isAtBottom, trackVisibility]);
27 |
28 | return ;
29 | }
30 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/CodeBlock.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, {
4 | FC,
5 | memo,
6 | useState,
7 | } from 'react';
8 |
9 | import {
10 | CheckIcon,
11 | CopyIcon,
12 | EyeClosedIcon,
13 | EyeOpenIcon,
14 | } from '@radix-ui/react-icons';
15 |
16 | import { Button } from '../ui/button';
17 | import { useCopyToClipboard } from './use-copy-to-clipboard';
18 |
19 | import SyntaxHighlighter from 'react-syntax-highlighter';
20 | import { atelierDuneLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
21 |
22 | interface Props {
23 | language: string;
24 | value: string;
25 | copyValue?: string;
26 | apiKeyToHide?: string;
27 | header?: boolean;
28 | showLineNumbers?: boolean;
29 | }
30 |
31 | type languageMap = Record;
32 |
33 | export const programmingLanguages: languageMap = {
34 | javascript: '.js',
35 | python: '.py',
36 | java: '.java',
37 | c: '.c',
38 | cpp: '.cpp',
39 | 'c++': '.cpp',
40 | 'c#': '.cs',
41 | ruby: '.rb',
42 | php: '.php',
43 | swift: '.swift',
44 | 'objective-c': '.m',
45 | kotlin: '.kt',
46 | typescript: '.ts',
47 | go: '.go',
48 | perl: '.pl',
49 | rust: '.rs',
50 | scala: '.scala',
51 | haskell: '.hs',
52 | lua: '.lua',
53 | shell: '.sh',
54 | sql: '.sql',
55 | html: '.html',
56 | css: '.css',
57 | // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
58 | };
59 |
60 | export const generateRandomString = (length: number, lowercase = false) => {
61 | // excluding similar looking characters like Z, 2, I, 1, O, 0
62 | const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789';
63 | let result = '';
64 | // eslint-disable-next-line no-plusplus
65 | for (let i = 0; i < length; i++) {
66 | result += chars.charAt(Math.floor(Math.random() * chars.length));
67 | }
68 | return lowercase ? result.toLowerCase() : result;
69 | };
70 |
71 | const CodeBlock: FC = memo(({ language, value, copyValue, apiKeyToHide, header = true, showLineNumbers = true }) => {
72 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 });
73 | const [showAPIKey, setShowAPIKey] = useState(false);
74 | const onCopy = () => {
75 | if (isCopied) return;
76 | copyToClipboard(copyValue || value);
77 | };
78 |
79 | const showHideAPIKey = () => {
80 | setShowAPIKey(!showAPIKey);
81 | };
82 |
83 | return (
84 |
85 | {header ? (
86 |
87 |
{language}
88 |
89 | {apiKeyToHide ? (
90 |
94 | ) : null}
95 |
99 |
100 |
101 | ) : null}
102 |
125 | {apiKeyToHide && !showAPIKey ? value.replace(apiKeyToHide, '*'.repeat(apiKeyToHide.length)) : value}
126 |
127 |
128 | );
129 | });
130 | CodeBlock.displayName = 'CodeBlock';
131 |
132 | export { CodeBlock };
133 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat-actions.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | PauseIcon,
3 | ReloadIcon,
4 | } from '@radix-ui/react-icons';
5 |
6 | import { Button } from '../ui/button';
7 | import { ChatHandler } from './chat.interface';
8 |
9 | export default function ChatActions(
10 | props: Pick & {
11 | showReload?: boolean;
12 | showStop?: boolean;
13 | },
14 | ) {
15 | return (
16 |
17 | {props.showStop && (
18 |
22 | )}
23 | {props.showReload && (
24 |
28 | )}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { FaRobot } from "react-icons/fa";
2 | import { FaUser } from "react-icons/fa";
3 |
4 | export default function ChatAvatar({ role }: { role: string }) {
5 | if (role === "user") {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat-input.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useRef,
3 | useState,
4 | } from 'react';
5 |
6 | import { useAutosizeTextArea } from '~/hooks/useAutosizeTextArea';
7 |
8 | import { PaperPlaneIcon } from '@radix-ui/react-icons';
9 |
10 | import { Button } from '../ui/button';
11 | import { Textarea } from '../ui/textarea';
12 |
13 | export default function ChatInput(props: {
14 | onSubmit: (text: string, base64Files?: string[]) => void;
15 | multiModal?: boolean;
16 | isLoading?: boolean;
17 | onFileUpload?: (file: File) => void;
18 | onFileError?: (error: string) => void;
19 | }) {
20 | const [imageUrl, setImageUrl] = useState(null);
21 | const [value, setValue] = useState('');
22 | const textAreaRef = useRef(null);
23 |
24 | useAutosizeTextArea(textAreaRef.current, value);
25 |
26 | const handleChange = (evt: React.ChangeEvent) => {
27 | const val = evt.target?.value;
28 |
29 | setValue(val);
30 | };
31 |
32 | const onSubmit = (e: React.FormEvent) => {
33 | e.preventDefault();
34 | const message = e.currentTarget.elements.namedItem('message') as HTMLInputElement;
35 | if (imageUrl) {
36 | props.onSubmit(message.value, [imageUrl]);
37 | setImageUrl(null);
38 | setValue('');
39 | return;
40 | }
41 | props.onSubmit(message.value);
42 | setTimeout(() => {
43 | setValue('');
44 | }, 10);
45 | };
46 |
47 | const handleKeyDown = (event: React.KeyboardEvent) => {
48 | // ignore if the user is using IME
49 | if (event.nativeEvent.isComposing || event.keyCode === 229) {
50 | return;
51 | }
52 | // Prevents adding a new line
53 |
54 | if (
55 | ((event.which || event.keyCode) === 13 || event.key === 'Enter') &&
56 | !event.shiftKey &&
57 | !event.altKey &&
58 | !event.ctrlKey &&
59 | !event.metaKey
60 | ) {
61 | event.currentTarget.form?.requestSubmit();
62 | } else if (event.key === 'Enter') {
63 | // Adds a new line
64 | setValue(`${value}`);
65 | } else {
66 | setValue(event.currentTarget.value);
67 | }
68 | };
69 |
70 | return (
71 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat-message.tsx:
--------------------------------------------------------------------------------
1 | import { ChatMessage as ChatMessageInterface } from '~/components/common/types';
2 | import { cn } from '~/lib/utils';
3 |
4 | import { CheckIcon, CopyIcon } from '@radix-ui/react-icons';
5 |
6 | import { Button } from '../ui/button';
7 | import { v4 as uuidv4 } from 'uuid';
8 | import ChatAvatar from './chat-avatar';
9 | import Markdown from './markdown';
10 | import { useCopyToClipboard } from './use-copy-to-clipboard';
11 | import Toggle from './toggle';
12 | import { stringifyObject } from '../common/utils';
13 |
14 | export default function ChatMessage(chatMessage: ChatMessageInterface) {
15 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 });
16 | const functionCalls = chatMessage.metadata?.functionCalls;
17 | var functionCallContent;
18 | if (functionCalls && functionCalls.length > 0) {
19 | type FunctionCall = {
20 | name: string;
21 | arguments: { [key: string]: any };
22 | };
23 |
24 | const parsedFunctionCalls: FunctionCall[] = functionCalls.map(functionCall => ({
25 | name: functionCall.name,
26 | arguments: JSON.parse(functionCall.arguments) // Ensure this parsing is safe
27 | }));
28 |
29 | functionCallContent = '```javascript\n' + stringifyObject(parsedFunctionCalls) + '\n```';
30 | }
31 | return (
32 |
39 |
40 |
41 |
42 | {chatMessage.role === 'assistant' && chatMessage.metadata?.loading === true ? (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ) : null}
51 | {functionCallContent && (
52 |
53 |
57 |
58 | )}
59 |
63 | {chatMessage?.metadata?.averageTokenTime ? (
64 |
65 |
66 | {chatMessage.metadata.firstTokenTime?.toFixed(0) || '-- '}ms initial latency{' | '}
67 | {chatMessage.metadata.averageTokenTime.toFixed(2)} tokens/s
68 |
69 |
70 | ) : null}
71 |
72 |
80 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat-messages.tsx:
--------------------------------------------------------------------------------
1 | import { ChatMessage as ChatMessageInterface } from '../common/types';
2 | import ChatActions from './chat-actions';
3 | import ChatMessage from './chat-message';
4 |
5 | export default function ChatMessages(props: {
6 | messages: ChatMessageInterface[];
7 | isLoading: boolean;
8 | reload?: () => void;
9 | stop?: () => void;
10 | children?: React.ReactNode;
11 | }) {
12 | const messageLength = props.messages.length;
13 | const lastMessage = props.messages[messageLength - 1];
14 |
15 | const isLastMessageFromAssistant = messageLength > 0 && lastMessage?.role !== 'user';
16 | const showReload = props.reload && !props.isLoading && isLastMessageFromAssistant;
17 | const showStop = props.stop && props.isLoading;
18 |
19 | return (
20 |
21 |
22 | {props.messages.filter(m => m.metadata?.hide !== true).map((m) => (
23 |
24 | ))}
25 | {props.children}
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/chat.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Message {
2 | id: string;
3 | content: string;
4 | role: string;
5 | }
6 |
7 | export interface ChatHandler {
8 | messages: Message[];
9 | input: string;
10 | isLoading: boolean;
11 | handleSubmit: (
12 | e: React.FormEvent,
13 | ops?: {
14 | data?: any;
15 | },
16 | ) => void;
17 | handleInputChange: (e: React.ChangeEvent) => void;
18 | reload?: () => void;
19 | stop?: () => void;
20 | onFileUpload?: (file: File) => Promise;
21 | onFileError?: (errMsg: string) => void;
22 | }
23 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/empty-llm-state.tsx:
--------------------------------------------------------------------------------
1 | import { ExternalLinkIcon, ExclamationTriangleIcon } from "@radix-ui/react-icons";
2 |
3 | export function EmptyLLMOutput() {
4 | return (
5 |
6 |
7 |
8 | Demo Chat with Function Calling Capabilities
9 |
10 |
11 |
12 | This demo has been preconfigured with the following functions:
13 |
14 | - generate an image from a text description,
15 | - render chart from numeric data,
16 | - obtain last day's price of a given stock,
17 | - get recent news articles related to a query.
18 |
19 |
20 |
21 | Click on "show available functions" link above to see the function schema.
22 |
23 |
24 | Note that the model was not optimized for nested function calling.
25 |
26 |
27 | It's easy to build an app with custom functions! The instructions are
here .
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/index.ts:
--------------------------------------------------------------------------------
1 | export { type ChatHandler, type Message } from './chat.interface';
2 |
3 | export { default as ChatInput } from './chat-input';
4 | export { default as ChatMessages } from './chat-messages';
5 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/markdown.tsx:
--------------------------------------------------------------------------------
1 | import { FC, memo } from 'react';
2 |
3 | import ReactMarkdown, { Options } from 'react-markdown';
4 | import remarkGfm from 'remark-gfm';
5 |
6 | import { CodeBlock } from './CodeBlock';
7 |
8 | const MemoizedReactMarkdown: FC = memo(
9 | ReactMarkdown,
10 | (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className,
11 | );
12 |
13 | export default function Markdown({ content }: { content: string }) {
14 | return (
15 | {children};
21 | },
22 | code({ inline, className, children, ...props }) {
23 | if (children && Array.isArray(children) && children.length > 0) {
24 | if (children[0] === '▍') {
25 | return ▍;
26 | }
27 | children[0] = (children[0] as string).replace('`▍`', '▍');
28 | }
29 | if (inline) {
30 | return (
31 |
32 | {children}
33 |
34 | );
35 | }
36 | const match = /language-(?\w+)/u.exec(className || '');
37 |
38 | return (
39 |
48 | );
49 | },
50 | }}
51 | >
52 | {content}
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/toggle.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default function Toggle(props: {
4 | showText: string,
5 | hideText: string,
6 | children: React.ReactNode;
7 | }) {
8 | const [isVisible, setIsVisible] = useState(false);
9 |
10 | const toggleVisibility = () => {
11 | setIsVisible(!isVisible);
12 | };
13 |
14 | return (
15 |
16 |
21 | {isVisible &&
22 | {props.children}
23 |
}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/chat/use-copy-to-clipboard.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 |
5 | export interface useCopyToClipboardProps {
6 | timeout?: number;
7 | }
8 |
9 | export function useCopyToClipboard({ timeout = 2000 }: useCopyToClipboardProps) {
10 | const [isCopied, setIsCopied] = React.useState(false);
11 |
12 | const copyToClipboard = (value: string) => {
13 | if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
14 | return;
15 | }
16 |
17 | if (!value) {
18 | return;
19 | }
20 |
21 | navigator.clipboard
22 | .writeText(value)
23 | .then(() => {
24 | setIsCopied(true);
25 |
26 | setTimeout(() => {
27 | setIsCopied(false);
28 | }, timeout);
29 | return null;
30 | })
31 | .catch(() => {
32 | // eslint-disable-next-line no-console
33 | console.error('Failed to copy to clipboard.');
34 | });
35 | };
36 |
37 | return { isCopied, copyToClipboard };
38 | }
39 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/common/CodeBlock.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useState } from 'react';
3 |
4 | import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter';
5 | import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
6 | import bash from 'react-syntax-highlighter/dist/esm/languages/hljs/bash';
7 | import python from 'react-syntax-highlighter/dist/esm/languages/hljs/python';
8 | import typescript from 'react-syntax-highlighter/dist/esm/languages/hljs/typescript';
9 |
10 | import { CopyIcon } from '@radix-ui/react-icons';
11 |
12 | SyntaxHighlighter.registerLanguage('typescript', typescript);
13 | SyntaxHighlighter.registerLanguage('python', python);
14 | SyntaxHighlighter.registerLanguage('bash', bash);
15 |
16 | interface CodeBlockProps {
17 | language: 'python' | 'typescript' | 'bash';
18 | value: string;
19 | }
20 |
21 | const CodeBlock = ({ language, value }: CodeBlockProps) => {
22 | const [copyTip, setCopyTip] = useState('Copy code');
23 | const handleCopy = async (text: string) => {
24 | navigator.clipboard.writeText(text);
25 | setCopyTip('Copied');
26 | await new Promise((resolve) => setTimeout(resolve, 500));
27 | setCopyTip(`Copy code`);
28 | };
29 | return (
30 |
31 | {/* */}
32 |
41 | {String(value).replace(/\n$/u, '')}
42 |
43 |
44 |
58 |
59 | );
60 | };
61 |
62 | export default CodeBlock;
63 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/common/code.module.css:
--------------------------------------------------------------------------------
1 | .keyword {
2 | color: blue;
3 | }
4 |
5 | .comment {
6 | color: green;
7 | }
8 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/common/types.ts:
--------------------------------------------------------------------------------
1 | // TODO: tried to move it into fireworks/lib/types, had a build of build errros.
2 | export interface Completion {
3 | text: string;
4 | numTokens: number;
5 | }
6 |
7 | export interface FunctionCall {
8 | name: string;
9 | arguments: string;
10 | }
11 |
12 | export interface ToolCall {
13 | id: string;
14 | type: string;
15 | function: FunctionCall;
16 | }
17 |
18 | export interface ChatMessage {
19 | id: string;
20 | role: string;
21 | content: string;
22 | toolCalls?: ToolCall[];
23 | metadata?: {
24 | firstTokenTime?: number;
25 | totalTokens?: number;
26 | averageTokenTime?: number;
27 | perplexity?: any;
28 | used?: boolean;
29 | hide?: boolean;
30 | loading?: boolean;
31 | functionCalls?: FunctionCall[];
32 | functionResponse?: string;
33 | };
34 | }
35 |
36 | export interface DeltaMessage {
37 | role: string;
38 | content?: string;
39 | }
40 |
41 | export interface ChatCompletionResponseStreamChoice {
42 | index: number;
43 | delta: DeltaMessage;
44 | finish_reason?: string;
45 | logprobs?: LogProbs;
46 | }
47 |
48 | export interface ChatCompletionStreamResponse {
49 | id: string;
50 | object: string;
51 | created: number;
52 | model: string;
53 | choices: ChatCompletionResponseStreamChoice[];
54 | }
55 |
56 | export interface LogProbs {
57 | tokens: string[];
58 | }
59 |
60 | export interface Choice {
61 | text: string;
62 | index: number;
63 | finish_reason: string;
64 | logprobs?: LogProbs;
65 | }
66 |
67 | export interface CompletionResponse {
68 | id: string;
69 | object: string;
70 | created: number;
71 | model: string;
72 | choices: Choice[];
73 | }
74 |
75 | export interface ChatState {
76 | messages: ChatMessage[];
77 | temperature: number;
78 | max_tokens: number;
79 | top_p: number;
80 | top_k: number;
81 | presence_penalty: number;
82 | frequency_penalty: number;
83 | context_length_exceeded_behavior: 'truncate' | 'error';
84 | }
85 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/common/utils.ts:
--------------------------------------------------------------------------------
1 | import { ChatMessage, ChatState, FunctionCall } from './types';
2 | import { v4 as uuidv4 } from 'uuid';
3 |
4 | export function stringifyObject(obj: any, indentLevel: number = 0): string {
5 | const indent = ' '.repeat(indentLevel * 4); // 4 spaces per indent level
6 | const subIndent = ' '.repeat((indentLevel + 1) * 4);
7 |
8 | if (Array.isArray(obj)) {
9 | return '[\n' + obj.map(item => subIndent + stringifyObject(item, indentLevel + 1)).join(',\n') + '\n' + indent + ']';
10 | } else if (typeof obj === 'object' && obj !== null) {
11 | return '{\n' + Object.entries(obj).map(([key, value]) =>
12 | `${subIndent}${key}: ${stringifyObject(value, indentLevel + 1)}`).join(',\n') + '\n' + indent + '}';
13 | } else {
14 | return JSON.stringify(obj);
15 | }
16 | }
17 |
18 | export async function chatCompletion(requestBody: ChatState, messages: ChatMessage[]): Promise {
19 | const response = await fetch('/api/chatCompletion', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json'
23 | },
24 | body: JSON.stringify({ requestBody, messages })
25 | });
26 | return response.json();
27 | }
28 |
29 | export async function callFunction(name: string, args: string): Promise {
30 | console.log(`DEBUG: call functions ${name} args ${args}`);
31 | const response = await fetch(`/api/functions/${name}?action=call&args=${encodeURIComponent(args)}`);
32 | if (!response.ok) {
33 | const errorDetails = await response.text();
34 | throw new Error(`Function call failed: ${response.status} ${response.statusText} - ${errorDetails}`);
35 | }
36 | const data = await response.json();
37 | return JSON.stringify(data);
38 | }
39 |
40 | export async function generateImage(name: string, args: string): Promise {
41 | const response = await fetch(`/api/functions/${name}?action=call&args=${encodeURIComponent(args)}`);
42 | if (!response.ok) {
43 | const errorDetails = await response.text();
44 | throw new Error(`Function call failed: ${response.status} ${response.statusText} - ${errorDetails}`);
45 | }
46 | // Assuming the server returns a direct link to the image
47 | const imageBlob = await response.blob();
48 | const imageUrl = URL.createObjectURL(imageBlob);
49 |
50 | return JSON.stringify({ image_url: imageUrl });
51 | };
52 |
53 | export async function callFunctions(message: ChatMessage): Promise {
54 | if (message.toolCalls === undefined) {
55 | return null;
56 | }
57 |
58 | const promises = message.toolCalls.map(async (toolCall) => {
59 | const callId = toolCall?.id;
60 | const func = toolCall?.function;
61 |
62 | if (callId === undefined || func === undefined || func.name === undefined || func.arguments === undefined) {
63 | return null;
64 | }
65 |
66 | let content: string;
67 | switch (func.name) {
68 | // TODO: figure out a better way to handle this
69 | case 'renderChart':
70 | case 'generateImage':
71 | content = await generateImage(func.name, func.arguments);
72 | break;
73 | default:
74 | content = await callFunction(func.name, func.arguments);
75 | break;
76 | }
77 |
78 | return content;
79 | });
80 |
81 | const results = await Promise.all(promises);
82 | const combinedContent = results.filter(content => content !== null).join('\n');
83 |
84 | return {
85 | content: combinedContent,
86 | id: uuidv4(),
87 | role: 'tool',
88 | metadata: {
89 | totalTokens: 0,
90 | firstTokenTime: 0,
91 | averageTokenTime: 0,
92 | perplexity: null,
93 | hide: true
94 | },
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "~/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | cva,
5 | type VariantProps,
6 | } from 'class-variance-authority';
7 | import { cn } from '~/lib/utils.ts';
8 |
9 | import { Slot } from '@radix-ui/react-slot';
10 |
11 | const buttonVariants = cva(
12 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
13 | {
14 | variants: {
15 | variant: {
16 | default: 'text-primary-foreground',
17 | destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
18 | outline: 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
19 | secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
20 | ghost: 'hover:bg-accent hover:text-accent-foreground',
21 | link: 'text-primary underline-offset-4 hover:underline',
22 | },
23 | size: {
24 | default: 'h-9 px-4 py-2',
25 | sm: 'h-8 rounded-md px-3 text-xs',
26 | lg: 'h-10 rounded-md px-8',
27 | icon: 'h-9 w-9',
28 | },
29 | },
30 | defaultVariants: {
31 | variant: 'default',
32 | size: 'default',
33 | },
34 | },
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : 'button';
46 | return ;
47 | },
48 | );
49 | Button.displayName = 'Button';
50 |
51 | export { Button, buttonVariants };
52 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "~/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/consistent-type-assertions */
2 | import * as React from 'react';
3 |
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from 'react-hook-form';
12 | import { Label } from '~/components/ui/label';
13 | import { cn } from '~/lib/utils';
14 |
15 | import * as LabelPrimitive from '@radix-ui/react-label';
16 | import { Slot } from '@radix-ui/react-slot';
17 |
18 | const Form = FormProvider;
19 |
20 | type FormFieldContextValue<
21 | TFieldValues extends FieldValues = FieldValues,
22 | TName extends FieldPath = FieldPath,
23 | > = {
24 | name: TName;
25 | };
26 |
27 | const FormFieldContext = React.createContext({} as FormFieldContextValue);
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath,
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext);
44 | const itemContext = React.useContext(FormItemContext);
45 | const { getFieldState, formState } = useFormContext();
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState);
48 |
49 | if (!fieldContext) {
50 | throw new Error('useFormField should be used within ');
51 | }
52 |
53 | const { id } = itemContext;
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | };
63 | };
64 |
65 | type FormItemContextValue = {
66 | id: string;
67 | };
68 |
69 | const FormItemContext = React.createContext({} as FormItemContextValue);
70 |
71 | const FormItem = React.forwardRef>(
72 | ({ className, ...props }, ref) => {
73 | const id = React.useId();
74 |
75 | return (
76 |
77 |
78 |
79 | );
80 | },
81 | );
82 | FormItem.displayName = 'FormItem';
83 |
84 | const FormLabel = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => {
88 | const { error, formItemId } = useFormField();
89 |
90 | return ;
91 | });
92 | FormLabel.displayName = 'FormLabel';
93 |
94 | const FormControl = React.forwardRef, React.ComponentPropsWithoutRef>(
95 | ({ ...props }, ref) => {
96 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
97 |
98 | return (
99 |
106 | );
107 | },
108 | );
109 | FormControl.displayName = 'FormControl';
110 |
111 | const FormDescription = React.forwardRef>(
112 | ({ className, ...props }, ref) => {
113 | const { formDescriptionId } = useFormField();
114 |
115 | return (
116 |
117 | );
118 | },
119 | );
120 | FormDescription.displayName = 'FormDescription';
121 |
122 | const FormMessage = React.forwardRef>(
123 | ({ className, children, ...props }, ref) => {
124 | const { error, formMessageId } = useFormField();
125 | const body = error ? String(error?.message) : children;
126 |
127 | if (!body) {
128 | return null;
129 | }
130 |
131 | return (
132 |
138 | {body}
139 |
140 | );
141 | },
142 | );
143 | FormMessage.displayName = 'FormMessage';
144 |
145 | export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
146 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "~/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes { }
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "~/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "~/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "~/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes { }
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/hooks/use-at-bottom.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export function useAtBottom(offset = 0) {
4 | const [isAtBottom, setIsAtBottom] = React.useState(false);
5 |
6 | React.useEffect(() => {
7 | const handleScroll = () => {
8 | setIsAtBottom(window.innerHeight + window.scrollY >= document.body.offsetHeight - offset);
9 | };
10 |
11 | window.addEventListener('scroll', handleScroll, { passive: true });
12 | handleScroll();
13 |
14 | return () => {
15 | window.removeEventListener('scroll', handleScroll);
16 | };
17 | }, [offset]);
18 |
19 | return isAtBottom;
20 | }
21 |
--------------------------------------------------------------------------------
/apps/functional_chat/app/hooks/useAutosizeTextArea.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | // Updates the height of a