├── python ├── generation │ ├── __init__.py │ ├── generate.py │ └── generation_template.py ├── data │ ├── cbt_resources │ │ ├── Abes-CCD.pdf │ │ ├── Abes-SB-CCD.pdf │ │ ├── Traditional-CCD.pdf │ │ ├── Abes-Case-Write-Up.pdf │ │ └── Strength-Based-CCD.pdf │ └── profiles.json ├── .env └── generate.sh ├── requirements.txt ├── title.png ├── public ├── favicon.ico ├── favicon-16x16.png ├── apple-touch-icon.png ├── vercel.svg ├── thirteen.svg └── next.svg ├── app ├── twitter-image.png ├── opengraph-image.png ├── new │ └── page.tsx ├── api │ ├── route.ts │ ├── data │ │ ├── session-instruction.tsx │ │ ├── emotions.tsx │ │ ├── patient-profiles.tsx │ │ ├── core-beliefs.tsx │ │ ├── diagram-fields.tsx │ │ └── patient-types.jsx │ ├── profile │ │ └── route.ts │ ├── type │ │ └── route.ts │ └── prompt │ │ └── route.ts ├── (chat) │ ├── layout.tsx │ ├── page.tsx │ └── chat │ │ └── [id] │ │ └── page.tsx ├── login │ ├── page.tsx │ ├── authenticate.ts │ └── actions.ts ├── signup │ ├── page.tsx │ ├── createUser.ts │ └── actions.ts ├── types │ └── page.tsx ├── share │ └── [id] │ │ └── page.tsx ├── globals.css ├── layout.tsx └── actions.ts ├── postcss.config.js ├── .env.example ├── next-env.d.ts ├── middleware.ts ├── components ├── markdown.tsx ├── sidebar-footer.tsx ├── ui │ ├── collapsible.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── input.tsx │ ├── separator.tsx │ ├── sonner.tsx │ ├── checkbox.tsx │ ├── tooltip.tsx │ ├── switch.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── dialog.tsx │ ├── codeblock.tsx │ ├── sheet.tsx │ ├── form.tsx │ ├── alert-dialog.tsx │ └── select.tsx ├── spinner.tsx ├── sidebar.tsx ├── providers.tsx ├── sidebar-toggle.tsx ├── tailwind-indicator.tsx ├── footer.tsx ├── sidebar-desktop.tsx ├── external-link.tsx ├── theme-toggle.tsx ├── sidebar-mobile.tsx ├── button-scroll-to-bottom.tsx ├── sidebar-items.tsx ├── chat-message-actions.tsx ├── login-button.tsx ├── sidebar-list.tsx ├── chat-history.tsx ├── chat-list.tsx ├── user-menu.tsx ├── stopwatch.tsx ├── clear-history.tsx ├── start-session.tsx ├── login-form.tsx ├── signup-form.tsx ├── chat-message.tsx ├── header.tsx ├── chat-share-dialog.tsx ├── prompt-form.tsx ├── message.tsx ├── sidebar-item.tsx ├── sidebar-actions.tsx ├── chat-panel.tsx ├── chat.tsx └── diagram-checkbox.tsx ├── components.json ├── next.config.js ├── .eslintrc.json ├── .gitignore ├── lib ├── hooks │ ├── use-enter-submit.tsx │ ├── use-local-storage.ts │ ├── use-streamable-text.ts │ ├── use-copy-to-clipboard.tsx │ ├── use-sidebar.tsx │ └── use-scroll-anchor.tsx ├── types.ts ├── utils │ └── kvDatabaseFunctions.ts ├── utils.ts └── chat │ └── actions.tsx ├── .github └── ISSUE_TEMPLATE │ ├── 🐞-bug-report.md │ └── ✨-feature-request.md ├── prettier.config.cjs ├── tsconfig.json ├── auth.config.ts ├── auth.ts ├── tailwind.config.ts ├── package.json └── README.md /python/generation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.2.5 2 | langchain-openai 3 | python-dotenv -------------------------------------------------------------------------------- /title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/title.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/app/twitter-image.png -------------------------------------------------------------------------------- /app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/app/opengraph-image.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /python/data/cbt_resources/Abes-CCD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/python/data/cbt_resources/Abes-CCD.pdf -------------------------------------------------------------------------------- /python/data/cbt_resources/Abes-SB-CCD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/python/data/cbt_resources/Abes-SB-CCD.pdf -------------------------------------------------------------------------------- /app/new/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation' 2 | 3 | export default async function NewPage() { 4 | redirect('/') 5 | } 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | KV_REST_API_READ_ONLY_TOKEN="" 2 | KV_REST_API_TOKEN="" 3 | KV_REST_API_URL="" 4 | KV_URL="" 5 | AUTH_SECRET="" 6 | OPENAI_API_KEY="" -------------------------------------------------------------------------------- /python/data/cbt_resources/Traditional-CCD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/python/data/cbt_resources/Traditional-CCD.pdf -------------------------------------------------------------------------------- /python/.env: -------------------------------------------------------------------------------- 1 | GENERATOR_MODEL="gpt-4" 2 | GENERATOR_MODEL_TEMP="1.0" 3 | OPENAI_API_KEY="" 4 | DATA_PATH="data/" 5 | OUT_PATH="output/" 6 | MAX_ATTEMPTS=5 -------------------------------------------------------------------------------- /python/data/cbt_resources/Abes-Case-Write-Up.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/python/data/cbt_resources/Abes-Case-Write-Up.pdf -------------------------------------------------------------------------------- /python/data/cbt_resources/Strength-Based-CCD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruiyiw/patient-psi/HEAD/python/data/cbt_resources/Strength-Based-CCD.pdf -------------------------------------------------------------------------------- /python/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 -m generation.generate --transcript-file "example_transcript.txt" --out-file "example_CCD_from_transcript.json" -------------------------------------------------------------------------------- /app/api/route.ts: -------------------------------------------------------------------------------- 1 | import { kv } from '@vercel/kv'; 2 | import { NextResponse } from 'next/server'; 3 | 4 | export async function GET() { 5 | const user = await kv.hgetall('user:me'); 6 | return NextResponse.json(user); 7 | } -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth' 2 | import { authConfig } from './auth.config' 3 | 4 | export default NextAuth(authConfig).auth 5 | 6 | export const config = { 7 | matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'] 8 | } 9 | -------------------------------------------------------------------------------- /components/markdown.tsx: -------------------------------------------------------------------------------- 1 | import { FC, memo } from 'react' 2 | import ReactMarkdown, { Options } from 'react-markdown' 3 | 4 | export const MemoizedReactMarkdown: FC = memo( 5 | ReactMarkdown, 6 | (prevProps, nextProps) => 7 | prevProps.children === nextProps.children && 8 | prevProps.className === nextProps.className 9 | ) 10 | -------------------------------------------------------------------------------- /components/sidebar-footer.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | 3 | export function SidebarFooter({ 4 | children, 5 | className, 6 | ...props 7 | }: React.ComponentProps<'div'>) { 8 | return ( 9 |
13 | {children} 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /app/(chat)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarDesktop } from '@/components/sidebar-desktop' 2 | 3 | interface ChatLayoutProps { 4 | children: React.ReactNode 5 | } 6 | 7 | export default async function ChatLayout({ children }: ChatLayoutProps) { 8 | return ( 9 |
10 | 11 | {children} 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: 'https', 7 | hostname: 'avatars.githubusercontent.com', 8 | port: '', 9 | pathname: '**' 10 | } 11 | ] 12 | }, 13 | serverRuntimeConfig: { 14 | logger: { 15 | level: 'info', 16 | }, 17 | serverPath: 'app/api', 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from '@/auth' 2 | import LoginForm from '@/components/login-form' 3 | import { Session } from '@/lib/types' 4 | import { redirect } from 'next/navigation' 5 | 6 | export default async function LoginPage() { 7 | const session = (await auth()) as Session 8 | 9 | if (session) { 10 | redirect('/') 11 | } 12 | 13 | return ( 14 |
15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /app/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from '@/auth' 2 | import SignupForm from '@/components/signup-form' 3 | import { Session } from '@/lib/types' 4 | import { redirect } from 'next/navigation' 5 | 6 | export default async function SignupPage() { 7 | const session = (await auth()) as Session 8 | 9 | if (session) { 10 | redirect('/') 11 | } 12 | 13 | return ( 14 |
15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /components/spinner.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export const spinner = ( 4 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /app/api/data/session-instruction.tsx: -------------------------------------------------------------------------------- 1 | export const sessionInstructions = { 2 | "ccd": "During your talk with the client, please fill in the following fields based on your understanding of the information the client revealed. Please prioritize what you observe during the conversation. ", 3 | "ccd-situation": "Please fill in the following fields based on the client's description of one specific situation and their reactions to it. If you think the client mentioned multiple situations, please select the situation that is most relevant to the client's mental state or behavior." 4 | } -------------------------------------------------------------------------------- /components/sidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | 5 | import { useSidebar } from '@/lib/hooks/use-sidebar' 6 | import { cn } from '@/lib/utils' 7 | 8 | export interface SidebarProps extends React.ComponentProps<'div'> {} 9 | 10 | export function Sidebar({ className, children }: SidebarProps) { 11 | const { isSidebarOpen, isLoading } = useSidebar() 12 | 13 | return ( 14 |
18 | {children} 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes' 5 | import { ThemeProviderProps } from 'next-themes/dist/types' 6 | import { SidebarProvider } from '@/lib/hooks/use-sidebar' 7 | import { TooltipProvider } from '@/components/ui/tooltip' 8 | 9 | export function Providers({ children, ...props }: ThemeProviderProps) { 10 | return ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "extends": [ 5 | "next/core-web-vitals", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss"], 10 | "rules": { 11 | "tailwindcss/no-custom-classname": "off", 12 | "tailwindcss/classnames-order": "off" 13 | }, 14 | "settings": { 15 | "tailwindcss": { 16 | "callees": ["cn", "cva"], 17 | "config": "tailwind.config.js" 18 | } 19 | }, 20 | "overrides": [ 21 | { 22 | "files": ["*.ts", "*.tsx"], 23 | "parser": "@typescript-eslint/parser" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /components/sidebar-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | 5 | import { useSidebar } from '@/lib/hooks/use-sidebar' 6 | import { Button } from '@/components/ui/button' 7 | import { IconSidebar } from '@/components/ui/icons' 8 | 9 | export function SidebarToggle() { 10 | const { toggleSidebar } = useSidebar() 11 | 12 | return ( 13 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === 'production') return null 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | .env* 36 | .vercel 37 | .vscode 38 | .env*.local 39 | *.json 40 | !profiles.json 41 | !.env.example 42 | !python/.env 43 | 44 | 45 | # cache 46 | __pycache__ -------------------------------------------------------------------------------- /lib/hooks/use-enter-submit.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, type RefObject } from 'react' 2 | 3 | export function useEnterSubmit(): { 4 | formRef: RefObject 5 | onKeyDown: (event: React.KeyboardEvent) => void 6 | } { 7 | const formRef = useRef(null) 8 | 9 | const handleKeyDown = ( 10 | event: React.KeyboardEvent 11 | ): void => { 12 | if ( 13 | event.key === 'Enter' && 14 | !event.shiftKey && 15 | !event.nativeEvent.isComposing 16 | ) { 17 | formRef.current?.requestSubmit() 18 | event.preventDefault() 19 | } 20 | } 21 | 22 | return { formRef, onKeyDown: handleKeyDown } 23 | } 24 | -------------------------------------------------------------------------------- /app/(chat)/page.tsx: -------------------------------------------------------------------------------- 1 | import { nanoid } from '@/lib/utils' 2 | import { Chat } from '@/components/chat' 3 | import { AI } from '@/lib/chat/actions' 4 | import { auth } from '@/auth' 5 | import { Session } from '@/lib/types' 6 | import { getMissingKeys } from '../actions' 7 | 8 | export const metadata = { 9 | title: 'Next.js AI Chatbot' 10 | } 11 | 12 | export default async function IndexPage() { 13 | const id = nanoid() 14 | const session = (await auth()) as Session 15 | const missingKeys = await getMissingKeys() 16 | 17 | return ( 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/🐞-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug report" 3 | about: Create a bug report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/✨-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature request" 3 | about: Add new features to the project 4 | title: "[FEAT]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /lib/hooks/use-local-storage.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export const useLocalStorage = ( 4 | key: string, 5 | initialValue: T 6 | ): [T, (value: T) => void] => { 7 | const [storedValue, setStoredValue] = useState(initialValue) 8 | 9 | useEffect(() => { 10 | // Retrieve from localStorage 11 | const item = window.localStorage.getItem(key) 12 | if (item) { 13 | setStoredValue(JSON.parse(item)) 14 | } 15 | }, [key]) 16 | 17 | const setValue = (value: T) => { 18 | // Save state 19 | setStoredValue(value) 20 | // Save to localStorage 21 | window.localStorage.setItem(key, JSON.stringify(value)) 22 | } 23 | return [storedValue, setValue] 24 | } 25 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | import { ExternalLink } from '@/components/external-link' 5 | 6 | export function FooterText({ className, ...props }: React.ComponentProps<'p'>) { 7 | return ( 8 |

15 | {/* Open source AI chatbot built with{' '} 16 | Next.js and{' '} 17 | 18 | Vercel AI SDK 19 | 20 | . */} 21 |

22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /components/sidebar-desktop.tsx: -------------------------------------------------------------------------------- 1 | import { Sidebar } from '@/components/sidebar' 2 | 3 | import { auth } from '@/auth' 4 | import { ChatHistory } from '@/components/chat-history' 5 | import { DiagramList } from './diagram-list' 6 | 7 | export async function SidebarDesktop() { 8 | const session = await auth() 9 | 10 | if (!session?.user?.id) { 11 | return null 12 | } 13 | 14 | return ( 15 |
16 | 17 | {/* @ts-ignore */} 18 | 19 | 20 |
21 | ) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /app/api/profile/route.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { NextRequest } from 'next/server'; 4 | import { NextResponse } from 'next/server'; 5 | import { getProfile } from '@/app/api/getDataFromKV'; // Import the getProfile function from your profile library 6 | 7 | export async function GET(request: NextRequest) { 8 | try { 9 | const profile = await getProfile(); 10 | if (profile) { 11 | return NextResponse.json({ profile: profile }); 12 | } else { 13 | return NextResponse.json({ error: 'Profile not found' }, { status: 404 }); 14 | } 15 | } catch (error) { 16 | console.error('Error retrieving profile:', error); 17 | return NextResponse.json({ error: 'Internal Server Error in GET' }, { status: 500 }); 18 | } 19 | } -------------------------------------------------------------------------------- /components/external-link.tsx: -------------------------------------------------------------------------------- 1 | export function ExternalLink({ 2 | href, 3 | children 4 | }: { 5 | href: string 6 | children: React.ReactNode 7 | }) { 8 | return ( 9 | 14 | {children} 15 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /lib/hooks/use-streamable-text.ts: -------------------------------------------------------------------------------- 1 | import { StreamableValue, readStreamableValue } from 'ai/rsc' 2 | import { useEffect, useState } from 'react' 3 | 4 | export const useStreamableText = ( 5 | content: string | StreamableValue 6 | ) => { 7 | const [rawContent, setRawContent] = useState( 8 | typeof content === 'string' ? content : '' 9 | ) 10 | 11 | useEffect(() => { 12 | ;(async () => { 13 | if (typeof content === 'object') { 14 | let value = '' 15 | for await (const delta of readStreamableValue(content)) { 16 | console.log(delta) 17 | if (typeof delta === 'string') { 18 | setRawContent((value = value + delta)) 19 | } 20 | } 21 | } 22 | })() 23 | }, [content]) 24 | 25 | return rawContent 26 | } 27 | -------------------------------------------------------------------------------- /app/types/page.tsx: -------------------------------------------------------------------------------- 1 | import { nanoid } from '@/lib/utils' 2 | import { Chat } from '@/components/chat' 3 | import { AI } from '@/lib/chat/actions' 4 | import { auth } from '@/auth' 5 | import { Session } from '@/lib/types' 6 | import { getMissingKeys } from '../actions' 7 | 8 | export interface ChatPageProps { 9 | params: { 10 | id: string; 11 | patient_type: string; 12 | }; 13 | } 14 | 15 | export default async function ChatPage({ params }: ChatPageProps) { 16 | const id = nanoid() 17 | const session = (await auth()) as Session 18 | const missingKeys = await getMissingKeys() 19 | 20 | return ( 21 | 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /lib/hooks/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({ 10 | timeout = 2000 11 | }: useCopyToClipboardProps) { 12 | const [isCopied, setIsCopied] = React.useState(false) 13 | 14 | const copyToClipboard = (value: string) => { 15 | if (typeof window === 'undefined' || !navigator.clipboard?.writeText) { 16 | return 17 | } 18 | 19 | if (!value) { 20 | return 21 | } 22 | 23 | navigator.clipboard.writeText(value).then(() => { 24 | setIsCopied(true) 25 | 26 | setTimeout(() => { 27 | setIsCopied(false) 28 | }, timeout) 29 | }) 30 | } 31 | 32 | return { isCopied, copyToClipboard } 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |