mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2026-02-04 01:25:30 +08:00
Compare commits
3 Commits
chore/add-
...
feat/suppo
| Author | SHA1 | Date | |
|---|---|---|---|
| bf49c3c15d | |||
| 7305de467e | |||
| 4fa6b2c2bd |
@ -1,6 +0,0 @@
|
|||||||
# APP ID
|
|
||||||
NEXT_PUBLIC_APP_ID=
|
|
||||||
# APP API key
|
|
||||||
NEXT_PUBLIC_APP_KEY=
|
|
||||||
# API url prefix
|
|
||||||
NEXT_PUBLIC_API_URL=
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -28,7 +28,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env*.local
|
.env*.local
|
||||||
.env
|
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
@ -48,4 +47,4 @@ yarn.lock
|
|||||||
.yarnrc.yml
|
.yarnrc.yml
|
||||||
|
|
||||||
# pmpm
|
# pmpm
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
22
README.md
22
README.md
@ -2,22 +2,18 @@
|
|||||||
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).
|
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).
|
||||||
|
|
||||||
## Config App
|
## Config App
|
||||||
Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Setting the following content:
|
Config app in `config/index.ts`.Please config:
|
||||||
```
|
- APP_ID
|
||||||
# APP ID
|
- API_KEY
|
||||||
NEXT_PUBLIC_APP_ID=
|
|
||||||
# APP API key
|
|
||||||
NEXT_PUBLIC_APP_KEY=
|
|
||||||
```
|
|
||||||
|
|
||||||
Config more in `config/index.ts` file:
|
More config:
|
||||||
```js
|
```js
|
||||||
export const APP_INFO: AppInfo = {
|
export const APP_INFO: AppInfo = {
|
||||||
title: 'Chat APP',
|
"title": 'Chat APP',
|
||||||
description: '',
|
"description": '',
|
||||||
copyright: '',
|
"copyright": '',
|
||||||
privacy_policy: '',
|
"privacy_policy": '',
|
||||||
default_language: 'zh-Hans'
|
"default_language": 'zh-Hans'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isShowPrompt = true
|
export const isShowPrompt = true
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
import { type NextRequest } from 'next/server'
|
import { type NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
import { client, getInfo, setSession } from '@/app/api/utils/common'
|
||||||
|
|||||||
21
app/api/passport/route.ts
Normal file
21
app/api/passport/route.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { type NextRequest } from 'next/server'
|
||||||
|
import { client } from '@/app/api/utils/common'
|
||||||
|
import { API_KEY, API_URL, APP_ID } from '@/config'
|
||||||
|
|
||||||
|
// import { commonClient } from 'dify-client'
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${API_KEY}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
'X-App-Code': APP_ID
|
||||||
|
};
|
||||||
|
const res = await axios({
|
||||||
|
url: 'https://api.dify.ai/v1/passport',
|
||||||
|
headers,
|
||||||
|
responseType: "json",
|
||||||
|
})
|
||||||
|
console.log(res)
|
||||||
|
return new Response(res.data)
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { type NextRequest } from 'next/server'
|
import { type NextRequest } from 'next/server'
|
||||||
import { ChatClient } from 'dify-client'
|
import { ChatClient, DifyClient } from 'dify-client'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { API_KEY, API_URL, APP_ID } from '@/config'
|
import { API_KEY, API_URL, APP_ID } from '@/config'
|
||||||
|
|
||||||
@ -19,3 +19,4 @@ export const setSession = (sessionId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const client = new ChatClient(API_KEY, API_URL || undefined)
|
export const client = new ChatClient(API_KEY, API_URL || undefined)
|
||||||
|
export const commonClient = new ChatClient(API_KEY, API_URL || undefined)
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import { replaceVarWithValues, userInputsFormToPromptVariables } from '@/utils/prompt'
|
import { replaceVarWithValues, userInputsFormToPromptVariables } from '@/utils/prompt'
|
||||||
import AppUnavailable from '@/app/components/app-unavailable'
|
import AppUnavailable from '@/app/components/app-unavailable'
|
||||||
import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/config'
|
import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/config'
|
||||||
|
import { checkOrSetAccessToken } from '@/utils/access-token'
|
||||||
|
|
||||||
const Main: FC = () => {
|
const Main: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -63,8 +64,6 @@ const Main: FC = () => {
|
|||||||
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
|
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
|
||||||
const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false)
|
const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false)
|
||||||
const handleStartChat = (inputs: Record<string, any>) => {
|
const handleStartChat = (inputs: Record<string, any>) => {
|
||||||
createNewChat()
|
|
||||||
setConversationIdChangeBecauseOfNew(true)
|
|
||||||
setCurrInputs(inputs)
|
setCurrInputs(inputs)
|
||||||
setChatStarted()
|
setChatStarted()
|
||||||
// parse variables in introduction
|
// parse variables in introduction
|
||||||
@ -199,6 +198,8 @@ const Main: FC = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
(async () => {
|
(async () => {
|
||||||
|
await checkOrSetAccessToken()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [conversationData, appParams] = await Promise.all([fetchConversations(), fetchAppParams()])
|
const [conversationData, appParams] = await Promise.all([fetchConversations(), fetchAppParams()])
|
||||||
|
|
||||||
|
|||||||
@ -92,10 +92,10 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
{promptConfig.prompt_variables.map(item => (
|
{promptConfig.prompt_variables.map(item => (
|
||||||
<div className='tablet:flex items-start mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
|
<div className='tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
|
||||||
<label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
|
<label className={`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
|
||||||
{item.type === 'select'
|
{item.type === 'select'
|
||||||
&& (
|
? (
|
||||||
<Select
|
<Select
|
||||||
className='w-full'
|
className='w-full'
|
||||||
defaultValue={inputs?.[item.key]}
|
defaultValue={inputs?.[item.key]}
|
||||||
@ -104,24 +104,16 @@ const Welcome: FC<IWelcomeProps> = ({
|
|||||||
allowSearch={false}
|
allowSearch={false}
|
||||||
bgClassName='bg-gray-50'
|
bgClassName='bg-gray-50'
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<input
|
||||||
|
placeholder={item.name}
|
||||||
|
value={inputs?.[item.key] || ''}
|
||||||
|
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
|
||||||
|
className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
|
||||||
|
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === 'string' && (
|
|
||||||
<input
|
|
||||||
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
|
||||||
value={inputs?.[item.key] || ''}
|
|
||||||
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
|
|
||||||
className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
|
|
||||||
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{item.type === 'paragraph' && (
|
|
||||||
<textarea
|
|
||||||
className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50"
|
|
||||||
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
|
||||||
value={inputs?.[item.key] || ''}
|
|
||||||
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { AppInfo } from '@/types/app'
|
import type { AppInfo } from '@/types/app'
|
||||||
export const APP_ID = `${process.env.NEXT_PUBLIC_APP_ID}`
|
export const APP_ID = ''
|
||||||
export const API_KEY = `${process.env.NEXT_PUBLIC_APP_KEY}`
|
export const API_KEY = ''
|
||||||
export const API_URL = `${process.env.NEXT_PUBLIC_API_URL}`
|
export const API_URL = ''
|
||||||
export const APP_INFO: AppInfo = {
|
export const APP_INFO: AppInfo = {
|
||||||
title: 'Chat APP',
|
title: 'Chat APP',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { API_PREFIX } from '@/config'
|
import { API_PREFIX, APP_ID } from '@/config'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
const TIME_OUT = 100000
|
const TIME_OUT = 100000
|
||||||
@ -102,7 +102,16 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
|
|||||||
|
|
||||||
const baseFetch = (url: string, fetchOptions: any, { needAllResponseContent }: IOtherOptions) => {
|
const baseFetch = (url: string, fetchOptions: any, { needAllResponseContent }: IOtherOptions) => {
|
||||||
const options = Object.assign({}, baseOptions, fetchOptions)
|
const options = Object.assign({}, baseOptions, fetchOptions)
|
||||||
|
const sharedToken = APP_ID
|
||||||
|
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||||
|
let accessTokenJson = { [sharedToken]: '' }
|
||||||
|
try {
|
||||||
|
accessTokenJson = JSON.parse(accessToken)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
options.headers.set('Authorization', `Bearer ${accessTokenJson[sharedToken]}`)
|
||||||
const urlPrefix = API_PREFIX
|
const urlPrefix = API_PREFIX
|
||||||
|
|
||||||
let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||||
|
|||||||
@ -31,3 +31,7 @@ export const fetchAppParams = async () => {
|
|||||||
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
||||||
return post(url, { body })
|
return post(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchAccessToken = async (appId: string) => {
|
||||||
|
return get('/passport') as Promise<{ access_token: string }>
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ import type { Locale } from '@/i18n'
|
|||||||
export type PromptVariable = {
|
export type PromptVariable = {
|
||||||
key: string
|
key: string
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: 'string' | 'number' | 'select'
|
||||||
default?: string | number
|
default?: string | number
|
||||||
options?: string[]
|
options?: string[]
|
||||||
max_length?: number
|
max_length?: number
|
||||||
|
|||||||
19
utils/access-token.ts
Normal file
19
utils/access-token.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { fetchAccessToken } from '@/service'
|
||||||
|
import { APP_ID } from '@/config'
|
||||||
|
|
||||||
|
export const checkOrSetAccessToken = async () => {
|
||||||
|
const sharedToken = APP_ID
|
||||||
|
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||||
|
let accessTokenJson = { [sharedToken]: '' }
|
||||||
|
try {
|
||||||
|
accessTokenJson = JSON.parse(accessToken)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!accessTokenJson[sharedToken]) {
|
||||||
|
const res = await fetchAccessToken(sharedToken)
|
||||||
|
accessTokenJson[sharedToken] = res.access_token
|
||||||
|
localStorage.setItem('token', JSON.stringify(accessTokenJson))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,22 +16,14 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
|
|||||||
return []
|
return []
|
||||||
const promptVariables: PromptVariable[] = []
|
const promptVariables: PromptVariable[] = []
|
||||||
useInputs.forEach((item: any) => {
|
useInputs.forEach((item: any) => {
|
||||||
const isParagraph = !!item.paragraph
|
const type = item['text-input'] ? 'string' : 'select'
|
||||||
const [type, content] = (() => {
|
const content = type === 'string' ? item['text-input'] : item.select
|
||||||
if (isParagraph)
|
if (type === 'string') {
|
||||||
return ['paragraph', item.paragraph]
|
|
||||||
|
|
||||||
if (item['text-input'])
|
|
||||||
return ['string', item['text-input']]
|
|
||||||
|
|
||||||
return ['select', item.select]
|
|
||||||
})()
|
|
||||||
if (type === 'string' || type === 'paragraph') {
|
|
||||||
promptVariables.push({
|
promptVariables.push({
|
||||||
key: content.variable,
|
key: content.variable,
|
||||||
name: content.label,
|
name: content.label,
|
||||||
required: content.required,
|
required: content.required,
|
||||||
type,
|
type: 'string',
|
||||||
max_length: content.max_length,
|
max_length: content.max_length,
|
||||||
options: [],
|
options: [],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user