├── config └── index.ts ├── components ├── card │ ├── style.module.scss │ └── index.tsx ├── error │ ├── style.module.scss │ └── index.tsx ├── chat-input │ ├── style.module.scss │ └── index.tsx ├── form │ ├── style.module.scss │ └── index.tsx ├── chat-bubble │ ├── assets │ │ ├── left.svg │ │ ├── right.svg │ │ ├── avatar.svg │ │ └── heart_label.svg │ ├── style.module.scss │ └── index.tsx ├── word-count │ └── index.tsx ├── menu-item │ ├── style.module.scss │ └── index.tsx ├── welcome │ └── index.tsx ├── button │ ├── style.module.css │ └── index.tsx ├── loading │ ├── style.css │ └── index.tsx ├── input │ └── index.tsx ├── like-and-dislike │ ├── assets │ │ ├── like.svg │ │ ├── like_selected.svg │ │ ├── dislike.svg │ │ └── dislike_selected.svg │ ├── style.module.css │ └── index.tsx ├── x-power-by │ └── index.tsx ├── hint │ └── index.tsx └── select │ └── index.tsx ├── global.d.ts ├── .eslintrc.json ├── app ├── favicon.ico ├── components │ ├── style.module.css │ ├── loading.tsx │ ├── page.tsx │ └── main.tsx ├── globals.css ├── page.tsx ├── chat │ ├── error.tsx │ ├── loading.tsx │ ├── page.tsx │ └── main.tsx ├── layout.tsx └── api │ └── chat-messages │ └── route.ts ├── public ├── logo.png ├── vercel.svg └── next.svg ├── next.config.js ├── postcss.config.js ├── .vscode ├── settings.json └── launch.json ├── .husky └── pre-commit ├── .env.example ├── .prettierrc.js ├── service └── index.ts ├── i18n ├── index.ts ├── zh-CN │ └── index.json └── en │ └── index.json ├── .gitignore ├── tailwind.config.js ├── .editorconfig ├── middleware.ts ├── interface └── index.ts ├── tsconfig.json ├── package.json ├── README.md └── yarn.lock /config/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/card/style.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/error/style.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/chat-input/style.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'dify-client' 2 | declare module 'uuid' 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mierudayo/Dify-api-Connection/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mierudayo/Dify-api-Connection/HEAD/public/logo.png -------------------------------------------------------------------------------- /app/components/style.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | @apply flex min-h-screen p-8 flex-col gap-5 my-10; 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-gray-50; 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.next": true 4 | }, 5 | "files.associations": { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /components/form/style.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | @apply flex flex-col shadow-lg rounded-lg; 3 | } 4 | .form { 5 | @apply flex flex-col gap-3 p-4; 6 | } 7 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | export default async function Home() { 2 | return
Hello
3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo '🏗️👷 Styling your project before committing👷‍♂️🏗️' 5 | echo 'please be patient, this may take a while...' 6 | 7 | npx lint-staged 8 | -------------------------------------------------------------------------------- /app/chat/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import ErrorPage from '@/components/error' 3 | 4 | export default function Loading() { 5 | // You can add any UI inside Loading, including a Skeleton. 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # you can find this in https://cloud.dify.ai/ 2 | APP_ID=YOUR_APP_ID # This is your APP ID aka APP NAME will be used for generate user id under this scope 3 | API_SECRET=YOUR_API_SECRET # This is your API Secret 4 | 5 | -------------------------------------------------------------------------------- /components/chat-bubble/assets/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "yarn run dev" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /components/card/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | import styles from './style.module.css' 4 | 5 | interface CardProps {} 6 | 7 | const Card: FC = ({}) => { 8 | return
9 | } 10 | 11 | export default Card 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | semi: false, 3 | tabWidth: 2, 4 | printWidth: 80, 5 | singleQuote: true, 6 | jsxSingleQuote: true, 7 | trailingComma: 'none', 8 | arrowParens: 'always', 9 | endOfLine: 'auto', 10 | noBracketSpacing: true 11 | } 12 | 13 | module.exports = config 14 | -------------------------------------------------------------------------------- /app/chat/loading.tsx: -------------------------------------------------------------------------------- 1 | import LoadingIcon from '@/components/loading' 2 | 3 | export default function Loading() { 4 | // You can add any UI inside Loading, including a Skeleton. 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/components/loading.tsx: -------------------------------------------------------------------------------- 1 | import LoadingIcon from '@/components/loading' 2 | 3 | export default function Loading() { 4 | // You can add any UI inside Loading, including a Skeleton. 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/components/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Main from './main' 3 | 4 | export const metadata = { 5 | title: 'Components', 6 | description: 'Components' 7 | } 8 | 9 | const Home = async () => { 10 | await new Promise((resolve) => setTimeout(resolve, 1000)) 11 | return
12 | } 13 | 14 | export default Home 15 | -------------------------------------------------------------------------------- /components/chat-bubble/assets/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /service/index.ts: -------------------------------------------------------------------------------- 1 | import { ChatClient } from 'dify-client' 2 | import { cookies } from 'next/headers' 3 | import dotenv from 'dotenv' 4 | dotenv.config() 5 | 6 | export const getLocale = () => { 7 | const cookieStore = cookies() 8 | return cookieStore.get('locale')?.value || 'en' // default to english 9 | } 10 | export const client = new ChatClient(process.env.API_SECRET) 11 | -------------------------------------------------------------------------------- /components/chat-input/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | import styles from './style.module.scss' 4 | 5 | interface ChatInputProps { 6 | value: string 7 | onSubmit: () => void 8 | placeholder?: string 9 | } 10 | 11 | const ChatInput: FC = ({}) => { 12 | return
13 | } 14 | 15 | export default ChatInput 16 | -------------------------------------------------------------------------------- /components/error/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | 4 | interface ErrorPageProps {} 5 | 6 | const ErrorPage: FC = ({}) => { 7 | return ( 8 |
9 | We are sorry, but something went wrong. 10 |
11 | ) 12 | } 13 | 14 | export default ErrorPage 15 | -------------------------------------------------------------------------------- /components/word-count/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | 3 | interface WordCountProps { 4 | wordCount: number 5 | } 6 | 7 | const WordCount: FC = ({ wordCount }) => { 8 | return ( 9 |
10 | {wordCount} 11 |
12 | ) 13 | } 14 | 15 | export default WordCount 16 | -------------------------------------------------------------------------------- /i18n/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en/index.json' 2 | import zhCN from './zh-CN/index.json' 3 | 4 | export const translations = { 5 | en, 6 | 'zh-CN': zhCN 7 | } as any 8 | 9 | const t = 10 | (translations: any) => 11 | (locale: string = 'en') => 12 | (key: string) => { 13 | const keys = key.split('.') 14 | return keys.reduce((acc, k) => acc[k], translations[locale]) 15 | } 16 | 17 | const I18N = t(translations) 18 | export default I18N 19 | -------------------------------------------------------------------------------- /components/menu-item/style.module.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | @apply flex w-full items-center gap-2 text-sm font-medium h-9 rounded-lg justify-between; 3 | @apply text-gray-700 bg-white; 4 | @apply cursor-pointer; 5 | 6 | .action { 7 | @apply hidden; 8 | } 9 | &:hover { 10 | @apply text-blue-600 bg-gray-100; 11 | .action { 12 | @apply inline-flex; 13 | } 14 | } 15 | &.active { 16 | @apply text-blue-700 bg-blue-50; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import { Inter } from 'next/font/google' 3 | const inter = Inter({ subsets: ['latin'] }) 4 | 5 | export const metadata = { 6 | title: 'Create Next App', 7 | description: 'Generated by create next app' 8 | } 9 | 10 | export default function RootLayout({ children }: { children: React.ReactNode }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /i18n/zh-CN/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "name": "应用", 4 | "version": "版本", 5 | "chats": "聊天", 6 | "new_chat": "新聊天", 7 | "start_chat": "开始聊天", 8 | "try_to_ask": "试着问问", 9 | "opening_statement": "开场白", 10 | "welcome_message": "👏 欢迎使用 Dify.AI", 11 | "welcome_message_description": "Dify.AI 是一个基于人工智能的聊天机器人平台,它可以帮助您快速构建自己的聊天机器人。", 12 | "initial_prompt": "初始提示", 13 | "opts": { 14 | "add": "添加", 15 | "edit": "编辑", 16 | "delete": "删除", 17 | "save": "保存" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /components/chat-bubble/assets/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.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 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .env 38 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /components/welcome/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import cn from 'classnames' 3 | 4 | interface WelcomeProps { 5 | name: string 6 | description: string 7 | } 8 | 9 | const Welcome: FC = ({ name, description }) => { 10 | return ( 11 |
12 |
13 | {name} 14 |
15 |
{description}
16 |
17 | ) 18 | } 19 | 20 | export default Welcome 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js,tsx}] 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 2 17 | 18 | 19 | # Matches the exact files either package.json or .travis.yml 20 | [{package.json,.travis.yml}] 21 | indent_style = space 22 | indent_size = 2 -------------------------------------------------------------------------------- /app/api/chat-messages/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from 'next/server' 2 | import { cookies } from 'next/headers' 3 | import { client } from '@/service' 4 | 5 | export async function POST(request: NextRequest) { 6 | const { inputs, query, conversation_id } = await request.json() 7 | const cookieStore = cookies() 8 | const user = cookieStore.get('user')?.value || 'anonymous' 9 | const { data } = await client.createChatMessage( 10 | query, 11 | user, 12 | inputs, 13 | conversation_id 14 | true, 15 | ) 16 | return new Response(data) 17 | } 18 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import type { NextRequest } from 'next/server' 3 | import { v4 } from 'uuid' 4 | 5 | export function middleware(request: NextRequest) { 6 | const response = NextResponse.next() 7 | // Check and set the locale cookie if it doesn't exist 8 | if (!request.cookies.has('locale')) { 9 | response.cookies.set('locale', 'en') 10 | } 11 | // Check and set the user cookie if it doesn't exist 12 | if (!request.cookies.has('user')) { 13 | response.cookies.set('user', `${process.env.APP_ID}_${v4()}`) 14 | } 15 | return response 16 | } 17 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /i18n/en/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "name": "My App", 4 | "version": "Version", 5 | "chats": "Chats", 6 | "new_chat": "New Chat", 7 | "start_chat": "Start Chat", 8 | "try_to_ask": "Try to ask", 9 | "opening_statement": "Opening statement", 10 | "welcome_message": "👏 Welcome to Dify.AI", 11 | "welcome_message_description": "Dify.AI is a platform that allows you to create your own chatbots. ", 12 | "initial_prompt": "Initial prompt", 13 | "opts": { 14 | "add": "Add", 15 | "edit": "Edit", 16 | "delete": "Delete", 17 | "save": "Save" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /interface/index.ts: -------------------------------------------------------------------------------- 1 | export interface LocaleProps { 2 | locale: string 3 | } 4 | 5 | export interface AppProps { 6 | opening_statement: string 7 | suggested_questions: any[] 8 | suggested_questions_after_answer: { 9 | enabled: boolean 10 | } 11 | more_like_this: { 12 | enabled: boolean 13 | } 14 | user_input_form: any[] 15 | } 16 | 17 | export interface ConversationProps { 18 | id: string 19 | inputs: any 20 | introduction: string 21 | name: string 22 | status: string 23 | } 24 | 25 | export interface ConversationsProps { 26 | data: ConversationProps[] 27 | has_more: boolean 28 | limit: number 29 | } 30 | -------------------------------------------------------------------------------- /components/button/style.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | @apply inline-flex text-xs py-2 px-4 rounded-lg items-center; 3 | } 4 | 5 | .button.blue { 6 | @apply text-white bg-blue-600 hover:bg-blue-700; 7 | } 8 | 9 | .button.gray { 10 | @apply text-gray-500 bg-gray-200 hover:bg-gray-300; 11 | } 12 | 13 | .button.red { 14 | @apply text-white bg-red-700 hover:bg-red-800; 15 | } 16 | 17 | .button.white { 18 | @apply text-gray-500 bg-transparent outline outline-gray-300 hover:bg-gray-300; 19 | } 20 | 21 | .button.transparent { 22 | @apply border-solid border border-gray-200 text-gray-500 hover:bg-white hover:shadow-sm hover:border-gray-300; 23 | } 24 | -------------------------------------------------------------------------------- /components/loading/style.css: -------------------------------------------------------------------------------- 1 | .spin-animation path { 2 | animation: custom 1s linear infinite; 3 | } 4 | 5 | @keyframes custom { 6 | 0% { 7 | opacity: 0; 8 | } 9 | 10 | 25% { 11 | opacity: 0.1; 12 | } 13 | 14 | 50% { 15 | opacity: 0.2; 16 | } 17 | 18 | 75% { 19 | opacity: 0.5; 20 | } 21 | 22 | 100% { 23 | opacity: 1; 24 | } 25 | } 26 | 27 | .spin-animation path:nth-child(1) { 28 | animation-delay: 0.25s; 29 | } 30 | 31 | .spin-animation path:nth-child(2) { 32 | animation-delay: 0.5s; 33 | } 34 | 35 | .spin-animation path:nth-child(3) { 36 | animation-delay: 0.75s; 37 | } 38 | 39 | .spin-animation path:nth-child(4) { 40 | animation-delay: 1s; 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /components/input/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | import cn from 'classnames' 4 | 5 | interface InputProps { 6 | value: string 7 | className?: string 8 | placeholder?: string 9 | onChange: (e: React.ChangeEvent) => void 10 | } 11 | const Input: FC = ({ value, placeholder, className, onChange }) => { 12 | return ( 13 | 25 | ) 26 | } 27 | 28 | export default Input 29 | -------------------------------------------------------------------------------- /components/like-and-dislike/assets/like.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/like-and-dislike/assets/like_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/button/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react'; 2 | import cn from 'classnames'; 3 | import styles from './style.module.css'; 4 | 5 | interface ButtonProps { 6 | text?: string; 7 | type?: 'blue' | 'gray' | 'red' | 'white' | 'transparent'; 8 | className?: string; 9 | onClick?: () => void; 10 | children?: ReactNode; 11 | } 12 | 13 | const Button: FC = ({ 14 | text, 15 | children, 16 | className, 17 | type = 'blue', 18 | onClick, 19 | }: ButtonProps) => { 20 | const buttonClassNames = cn( 21 | styles.button, 22 | { 23 | [styles.blue]: type === 'blue', 24 | [styles.gray]: type === 'gray', 25 | [styles.red]: type === 'red', 26 | [styles.white]: type === 'white', 27 | [styles.transparent]: type === 'transparent', 28 | }, 29 | className 30 | ); 31 | 32 | return ( 33 | 36 | ); 37 | }; 38 | 39 | export default Button; -------------------------------------------------------------------------------- /app/chat/page.tsx: -------------------------------------------------------------------------------- 1 | import { getLocale, client } from '@/service' 2 | import { ConversationsProps } from '@/interface' 3 | import { cookies } from 'next/headers' 4 | import Main from './main' 5 | 6 | async function getAppInfo() { 7 | const { status, data: appInfo } = await client.getApplicationParameters() 8 | if (status !== 200) { 9 | // This will activate the closest `error.js` Error Boundary 10 | throw new Error('Failed to fetch data') 11 | } 12 | return appInfo 13 | } 14 | 15 | async function getConversations() { 16 | const { status, data } = await client.getConversations() 17 | if (status !== 200) { 18 | throw new Error('Failed to fetch data') 19 | } 20 | return data as ConversationsProps 21 | } 22 | 23 | const Home = async () => { 24 | const locale = await getLocale() 25 | const appInfo = await getAppInfo() 26 | const conversations = await getConversations() 27 | const cookieStore = cookies() 28 | const user = cookieStore.get('user')?.value || 'anonymous' 29 | return ( 30 |
31 |
37 |
38 | ) 39 | } 40 | 41 | export default Home 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dify-conversation", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "prepare": "husky install" 11 | }, 12 | "dependencies": { 13 | "@heroicons/react": "^2.0.18", 14 | "@types/node": "20.2.5", 15 | "@types/react": "18.2.9", 16 | "@types/react-dom": "18.2.4", 17 | "autoprefixer": "10.4.14", 18 | "classnames": "^2.3.2", 19 | "dayjs": "^1.11.8", 20 | "dify-client": "^2.0.0", 21 | "dotenv": "^16.1.4", 22 | "next": "13.4.4", 23 | "postcss": "8.4.24", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "sass": "^1.63.3", 27 | "tailwindcss": "3.3.2", 28 | "typescript": "5.1.3", 29 | "uuid": "^9.0.0" 30 | }, 31 | "devDependencies": { 32 | "eslint": "8.42.0", 33 | "eslint-config-next": "13.4.4", 34 | "husky": "^8.0.0", 35 | "lint-staged": "^13.2.2", 36 | "prettier": "^2.8.8" 37 | }, 38 | "lint-staged": { 39 | "**/*.{js,jsx,ts,tsx}": [ 40 | "eslint --fix", 41 | "prettier --config ./.prettierrc.js --write" 42 | ], 43 | "**/*.{css,scss,md,html,json}": [ 44 | "prettier --config ./.prettierrc.js --write" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /components/x-power-by/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import Image from 'next/image' 3 | import dayjs from 'dayjs' 4 | 5 | const XPowerBy = () => { 6 | return ( 7 | 8 | power by 9 | Dify.AI Logo 10 | 11 | ) 12 | } 13 | interface PowerByFooterProps { 14 | appName: string 15 | } 16 | 17 | export const XPowerByPrivacy = () => { 18 | return ( 19 |
20 | Please read the 21 | 22 | privacy policy 23 | 24 | provided by the app developer. 25 |
26 | ) 27 | } 28 | export const XPowerByFooter: FC = ({ appName }) => { 29 | return ( 30 |
31 | 32 | © {appName} {dayjs().year()} 33 | 34 |
35 | ) 36 | } 37 | 38 | export default XPowerBy 39 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/chat-bubble/assets/heart_label.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/like-and-dislike/style.module.css: -------------------------------------------------------------------------------- 1 | .action_container { 2 | @apply inline-flex items-center bg-white shadow-md rounded-lg; 3 | padding: 2px; 4 | height: 28px; 5 | } 6 | 7 | .action { 8 | @apply inline-flex items-center justify-center cursor-pointer rounded-lg; 9 | padding: 4px; 10 | } 11 | 12 | .action .like { 13 | @apply inline-flex items-center justify-center; 14 | width: 16px; 15 | height: 16px; 16 | background: url('./assets/like.svg') no-repeat center center; 17 | } 18 | 19 | .action:hover .like { 20 | background: url('./assets/like_selected.svg') no-repeat center center; 21 | } 22 | 23 | .action .dislike { 24 | @apply inline-flex items-center justify-center; 25 | width: 16px; 26 | height: 16px; 27 | background: url('./assets/dislike.svg') no-repeat center center; 28 | } 29 | 30 | .action:hover .dislike { 31 | background: url('./assets/dislike_selected.svg') no-repeat center center; 32 | } 33 | 34 | .action.action_like_active { 35 | @apply bg-blue-50; 36 | } 37 | 38 | .action.action_like_active .like { 39 | background: url('./assets/like_selected.svg') no-repeat center center; 40 | } 41 | 42 | .action.action_dislike_active { 43 | @apply bg-red-50; 44 | } 45 | 46 | .action.action_dislike_active .dislike { 47 | background: url('./assets/dislike_selected.svg') no-repeat center center; 48 | } 49 | -------------------------------------------------------------------------------- /components/like-and-dislike/assets/dislike.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/like-and-dislike/assets/dislike_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './style.css' 4 | interface ILoadingProps { 5 | type?: 'area' | 'app' 6 | } 7 | const Loading = ( 8 | { type = 'area' }: ILoadingProps = { type: 'area' } 9 | ) => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ) 28 | } 29 | export default Loading 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 🚧🚧🚧 WIP 🚧🚧🚧 3 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | npm run dev 11 | # or 12 | yarn dev 13 | # or 14 | pnpm dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /components/hint/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import cn from 'classnames' 3 | import Sparkles from '@heroicons/react/24/solid/SparklesIcon' 4 | import Pencil from '@heroicons/react/24/outline/PencilIcon' 5 | 6 | interface HintProps { 7 | hint: string 8 | hintDescription?: string 9 | onAction?: () => void 10 | } 11 | 12 | const Hint: FC = ({ hint, hintDescription, onAction }) => { 13 | return ( 14 |
21 |
22 | 23 | 24 | {hint} 25 | 26 | {onAction && ( 27 | 35 | 36 | Edit 37 | 38 | )} 39 |
40 | {hintDescription &&
{hintDescription}
} 41 |
42 | ) 43 | } 44 | 45 | export default Hint 46 | -------------------------------------------------------------------------------- /components/chat-bubble/style.module.scss: -------------------------------------------------------------------------------- 1 | .bubble { 2 | @apply flex flex-col; 3 | 4 | .content { 5 | @apply flex items-center; 6 | &:hover { 7 | .shape .actions { 8 | @apply block; 9 | } 10 | } 11 | .shape { 12 | @apply rounded-2xl px-4 py-3; 13 | .icon { 14 | @apply inline-flex w-4 h-4 mr-1; 15 | background: url(./assets/heart_label.svg) no-repeat no-repeat; 16 | background-size: contain; 17 | } 18 | .actions { 19 | @apply absolute h-8 hidden; 20 | top: -16px; 21 | right: -8px; 22 | } 23 | } 24 | &.mine { 25 | @apply flex-row-reverse; 26 | .shape { 27 | @apply rounded-tr-none bg-[#D1E9FF80] relative; 28 | &::before { 29 | content: ''; 30 | @apply absolute w-4 h-4; 31 | top: 0; 32 | right: -16px; 33 | background: url(./assets/right.svg) no-repeat; 34 | } 35 | } 36 | } 37 | &.other { 38 | @apply relative flex-row; 39 | .shape { 40 | @apply rounded-tl-none bg-gray-100 relative; 41 | &::before { 42 | top: 0; 43 | left: -8px; 44 | content: ''; 45 | @apply absolute w-4 h-4; 46 | background: url(./assets/left.svg) no-repeat; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | .avatar { 53 | @apply inline-flex items-center justify-center w-10 h-10 rounded-full bg-gray-200 shrink-0 mx-2 self-start; 54 | &.mine { 55 | background: url(./assets/avatar.svg) no-repeat; 56 | background-size: contain; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /components/chat-bubble/index.tsx: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import React, { FC } from 'react' 3 | import styles from './style.module.scss' 4 | import cn from 'classnames' 5 | import LikeAndDislike from '@/components/like-and-dislike' 6 | interface ChatBubbleProps { 7 | opening_statement?: string 8 | like_and_dislike?: 'like' | 'dislike' | 'none' 9 | content: string 10 | mine?: boolean 11 | time?: number 12 | tokens?: number 13 | } 14 | 15 | const ChatBubble: FC = ({ 16 | opening_statement, 17 | content, 18 | mine = false 19 | }) => { 20 | return ( 21 |
22 |
23 | {/* avatar */} 24 |
25 | {/* actions */} 26 |
27 | {!mine && !opening_statement && ( 28 |
29 | { 32 | console.log(action) 33 | }} 34 | /> 35 |
36 | )} 37 | {opening_statement && ( 38 |
39 | 40 | {opening_statement} 41 |
42 | )} 43 | {content} 44 |
45 |
46 |
47 | ) 48 | } 49 | 50 | export default ChatBubble 51 | -------------------------------------------------------------------------------- /components/like-and-dislike/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React from "react" 3 | import styles from './style.module.css' 4 | import cn from 'classnames' 5 | interface LikeAndDislikeProps { 6 | status: 'like' | 'dislike' | 'none' 7 | onAction?: (action: 'like' | 'dislike' | 'none') => void 8 | } 9 | 10 | const LikeAndDislike: React.FC = ({ 11 | onAction, 12 | status = 'none', 13 | }) => { 14 | return
17 | {status === 'none' && <> 18 | onAction && onAction('like')} 20 | className={cn( 21 | styles.action, 22 | 'hover:bg-blue-50 hover:outline-1 hover:outline-white', 23 | )}> 24 | 25 | 26 | onAction && onAction('dislike')} 28 | className={cn( 29 | styles.action, 30 | 'hover:bg-red-50 hover:outline-1 hover:outline-white', 31 | )}> 32 | 33 | 34 | } 35 | {status === 'like' && onAction && onAction('none')} 37 | className={cn( 38 | styles.action, 39 | styles.action_like_active, 40 | 'hover:bg-blue-50 hover:outline-1 hover:outline-white', 41 | )}> 42 | 43 | } 44 | {status === 'dislike' && onAction && onAction('none')} 46 | className={cn( 47 | styles.action, 48 | styles.action_dislike_active, 49 | 'hover:bg-red-50 hover:outline-1 hover:outline-white', 50 | )}> 51 | 52 | } 53 | 54 |
55 | } 56 | 57 | export default LikeAndDislike 58 | -------------------------------------------------------------------------------- /components/select/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC, useState } from 'react' 3 | import cn from 'classnames' 4 | import ChevronUp from '@heroicons/react/24/outline/ChevronUpIcon' 5 | import ChevronDown from '@heroicons/react/24/outline/ChevronDownIcon' 6 | 7 | interface SelectProps { 8 | value: string | number | undefined 9 | className?: string 10 | options: any[] 11 | onSelect: (item: any) => void 12 | } 13 | 14 | const Select: FC = ({ value, options, className, onSelect }) => { 15 | const [open, setOpen] = useState(false) 16 | const selectedOption = options.find((item) => item.value === value) 17 | return ( 18 |
19 |
{ 28 | setOpen(!open) 29 | }} 30 | > 31 | {selectedOption?.label || selectedOption?.name} 32 | 33 | {open ? ( 34 | 35 | ) : ( 36 | 37 | )} 38 | 39 |
40 | {open && ( 41 |
48 | {options.map((item, index) => ( 49 |
{ 62 | setOpen(false) 63 | onSelect(item.value) 64 | }} 65 | > 66 | {item.label || item.name} 67 |
68 | ))} 69 |
70 | )} 71 |
72 | ) 73 | } 74 | 75 | export default Select 76 | -------------------------------------------------------------------------------- /components/menu-item/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | import cn from 'classnames' 4 | import styles from './style.module.scss' 5 | import ChatBubble from '@heroicons/react/24/solid/ChatBubbleOvalLeftEllipsisIcon' 6 | import Dots from '@heroicons/react/24/solid/EllipsisHorizontalIcon' 7 | 8 | interface ActionProps { 9 | label: string 10 | value: string 11 | } 12 | interface MenuItemProps { 13 | active?: boolean 14 | text: string 15 | onClick: () => void 16 | actions?: ActionProps[] 17 | onActionClick?: (value: string) => void 18 | } 19 | 20 | const MenuItem: FC = ({ 21 | active = false, 22 | text, 23 | actions, 24 | onActionClick 25 | }) => { 26 | const [showActions, setShowActions] = React.useState(false) 27 | 28 | // TODO make showActions false when click outside and not on action button 29 | 30 | return ( 31 |
32 | 33 |
{text}
34 | {actions && actions.length > 0 && ( 35 |
{ 37 | setShowActions((s) => !s) 38 | }} 39 | className={cn( 40 | styles.action, 41 | 'inline-flex relative w-6 h-6 mr-1.5', 42 | 'rounded-md cursor-pointer items-center justify-center', 43 | 'bg-gray-100 hover:bg-gray-300 text-gray-700 shrink-0' 44 | // 'transition-colors duration-200 ' 45 | )} 46 | > 47 | 48 | {showActions && ( 49 |
56 |
57 | {actions?.map((action, index) => ( 58 |
{ 61 | evt.stopPropagation() 62 | setShowActions(false) 63 | onActionClick && onActionClick(action.value) 64 | }} 65 | className={cn( 66 | 'block px-4 py-4 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900', 67 | 'cursor-pointer' 68 | )} 69 | > 70 | {action.label} 71 |
72 | ))} 73 |
74 |
75 | )} 76 |
77 | )} 78 |
79 | ) 80 | } 81 | 82 | export default MenuItem 83 | -------------------------------------------------------------------------------- /components/form/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import cn from 'classnames'; 3 | import React, { FC, useState } from 'react'; 4 | import Button from '@/components/button'; 5 | import ChatBubble from '@heroicons/react/24/solid/ChatBubbleOvalLeftEllipsisIcon'; 6 | import Hint from '@/components/hint'; 7 | import Input from '@/components/input'; 8 | import Select from '@/components/select'; 9 | import styles from './style.module.scss'; 10 | 11 | export interface FormItemProps { 12 | label: string 13 | type: string 14 | variable: string 15 | required?: boolean 16 | options?: string[] 17 | max_length?: number 18 | default: string 19 | onChange?: (e: any) => void 20 | } 21 | 22 | interface FormProps { 23 | hint: string 24 | hintDescription?: string 25 | items: FormItemProps[] 26 | onSubmit?: (e: FormItemProps[]) => void 27 | onCancel?: () => void 28 | } 29 | export const FormItem: FC = ({ 30 | variable, 31 | type, 32 | label, 33 | options, 34 | default: defaultValue, 35 | onChange 36 | }) => { 37 | return ( 38 |
39 | 47 | {type === 'text-input' && ( 48 | { 52 | onChange && onChange({ variable, value: e.target.value }) 53 | }} 54 | /> 55 | )} 56 | {type === 'select' && options && ( 57 | { 77 | setValue(e.target.value) 78 | }} 79 | placeholder='Input something here...' 80 | className='w-full' 81 | /> 82 |
83 | 84 |
85 |

Select

86 |