Compare commits

...

9 Commits

Author SHA1 Message Date
Joel
5e4689f758 Merge branch 'main' into feat/conversation-pin-op 2023-06-27 18:43:51 +08:00
Joel
3580c91be6 Merge branch 'main' into feat/conversation-pin-op 2023-06-27 18:05:28 +08:00
Joel
77f4ca164c chore: reset test paging 2023-06-27 18:04:13 +08:00
Joel
a5d73a8680 Merge branch 'main' into feat/conversation-pin-op 2023-06-27 17:42:00 +08:00
Joel
21330a4106 fix: new conversatino not set right name 2023-06-27 17:41:37 +08:00
Joel
c00e9608c0 fix: two list scroll ui 2023-06-27 17:27:27 +08:00
Joel
2c01660659 fix: missing vars bind 2023-06-27 17:04:19 +08:00
Joel
3df562d085 feat: i18n and success notify 2023-06-27 16:37:59 +08:00
Joel
04abd4b5c2 feat: converation pin and unpin almost done 2023-06-27 15:41:29 +08:00
8 changed files with 326 additions and 144 deletions

View File

@@ -1,12 +1,13 @@
import { useState } from 'react'
import type { ConversationItem } from '@/models/share'
import produce from 'immer'
import type { ConversationItem } from '@/models/share'
const storageConversationIdKey = 'conversationIdInfo'
type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
function useConversation() {
const [conversationList, setConversationList] = useState<ConversationItem[]>([])
const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
const [currConversationId, doSetCurrConversationId] = useState<string>('-1')
// when set conversation id, we do not have set appId
const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
@@ -29,9 +30,10 @@ function useConversation() {
// input can be updated by user
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
const resetNewConversationInputs = () => {
if (!newConversationInputs) return
setNewConversationInputs(produce(newConversationInputs, draft => {
Object.keys(draft).forEach(key => {
if (!newConversationInputs)
return
setNewConversationInputs(produce(newConversationInputs, (draft) => {
Object.keys(draft).forEach((key) => {
draft[key] = ''
})
}))
@@ -48,6 +50,8 @@ function useConversation() {
return {
conversationList,
setConversationList,
pinnedConversationList,
setPinnedConversationList,
currConversationId,
setCurrConversationId,
getConversationIdFromStorage,
@@ -59,8 +63,8 @@ function useConversation() {
setCurrInputs,
currConversationInfo,
setNewConversationInfo,
setExistConversationInfo
setExistConversationInfo,
}
}
export default useConversation;
export default useConversation

View File

@@ -14,7 +14,7 @@ import { ToastContext } from '@/app/components/base/toast'
import Sidebar from '@/app/components/share/chat/sidebar'
import ConfigSence from '@/app/components/share/chat/config-scence'
import Header from '@/app/components/share/header'
import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, stopChatMessageResponding, updateFeedback } from '@/service/share'
import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, pinConversation, sendChatMessage, stopChatMessageResponding, unpinConversation, updateFeedback } from '@/service/share'
import type { ConversationItem, SiteInfo } from '@/models/share'
import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat'
@@ -65,9 +65,12 @@ const Main: FC<IMainProps> = ({
/*
* conversation info
*/
const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([])
const {
conversationList,
setConversationList,
pinnedConversationList,
setPinnedConversationList,
currConversationId,
setCurrConversationId,
getConversationIdFromStorage,
@@ -81,11 +84,36 @@ const Main: FC<IMainProps> = ({
setNewConversationInfo,
setExistConversationInfo,
} = useConversation()
const [hasMore, setHasMore] = useState<boolean>(false)
const [hasMore, setHasMore] = useState<boolean>(true)
const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true)
const onMoreLoaded = ({ data: conversations, has_more }: any) => {
setHasMore(has_more)
setConversationList([...conversationList, ...conversations])
}
const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => {
setHasPinnedMore(has_more)
setPinnedConversationList([...pinnedConversationList, ...conversations])
}
const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0)
const noticeUpdateList = () => {
setConversationList([])
setHasMore(true)
setPinnedConversationList([])
setHasPinnedMore(true)
setControlUpdateConversationList(Date.now())
}
const handlePin = async (id: string) => {
await pinConversation(isInstalledApp, installedAppInfo?.id, id)
notify({ type: 'success', message: t('common.api.success') })
noticeUpdateList()
}
const handleUnpin = async (id: string) => {
await unpinConversation(isInstalledApp, installedAppInfo?.id, id)
notify({ type: 'success', message: t('common.api.success') })
noticeUpdateList()
}
const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
@@ -121,7 +149,7 @@ const Main: FC<IMainProps> = ({
let notSyncToStateIntroduction = ''
let notSyncToStateInputs: Record<string, any> | undefined | null = {}
if (!isNewConversation) {
const item = conversationList.find(item => item.id === currConversationId)
const item = allConversationList.find(item => item.id === currConversationId)
notSyncToStateInputs = item?.inputs || {}
setCurrInputs(notSyncToStateInputs)
notSyncToStateIntroduction = item?.introduction || ''
@@ -229,6 +257,10 @@ const Main: FC<IMainProps> = ({
return []
}
const fetchAllConversations = () => {
return fetchConversations(isInstalledApp, installedAppInfo?.id, undefined, undefined, 100)
}
const fetchInitData = () => {
return Promise.all([isInstalledApp
? {
@@ -240,7 +272,7 @@ const Main: FC<IMainProps> = ({
},
plan: 'basic',
}
: fetchAppInfo(), fetchConversations(isInstalledApp, installedAppInfo?.id), fetchAppParams(isInstalledApp, installedAppInfo?.id)])
: fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id)])
}
// init
@@ -255,10 +287,10 @@ const Main: FC<IMainProps> = ({
setIsPublicVersion(tempIsPublicVersion)
const prompt_template = ''
// handle current conversation id
const { data: conversations, has_more } = conversationData as { data: ConversationItem[]; has_more: boolean }
const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean }
const _conversationId = getConversationIdFromStorage(appId)
const isNotNewConversation = conversations.some(item => item.id === _conversationId)
setHasMore(has_more)
const isNotNewConversation = allConversations.some(item => item.id === _conversationId)
setAllConversationList(allConversations)
// fetch new conversation info
const { user_input_form, opening_statement: introduction, suggested_questions_after_answer }: any = appParams
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
@@ -276,7 +308,7 @@ const Main: FC<IMainProps> = ({
} as PromptConfig)
setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer)
setConversationList(conversations as ConversationItem[])
// setConversationList(conversations as ConversationItem[])
if (isNotNewConversation)
setCurrConversationId(_conversationId, appId, false)
@@ -403,12 +435,10 @@ const Main: FC<IMainProps> = ({
if (hasError)
return
let currChatList = conversationList
if (getConversationIdChangeBecauseOfNew()) {
const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppInfo?.id)
setHasMore(has_more)
setConversationList(conversations as ConversationItem[])
currChatList = conversations
const { data: allConversations }: any = await fetchAllConversations()
setAllConversationList(allConversations)
noticeUpdateList()
}
setConversationIdChangeBecauseOfNew(false)
resetNewConversationInputs()
@@ -451,14 +481,20 @@ const Main: FC<IMainProps> = ({
return (
<Sidebar
list={conversationList}
pinnedList={pinnedConversationList}
onMoreLoaded={onMoreLoaded}
onPinnedMoreLoaded={onPinnedMoreLoaded}
isNoMore={!hasMore}
isPinnedNoMore={!hasPinnedMore}
onCurrentIdChange={handleConversationIdChange}
currentId={currConversationId}
copyRight={siteInfo.copyright || siteInfo.title}
isInstalledApp={isInstalledApp}
installedAppId={installedAppInfo?.id}
siteInfo={siteInfo}
onPin={handlePin}
onUnpin={handleUnpin}
controlUpdateList={controlUpdateConversationList}
/>
)
}
@@ -482,9 +518,6 @@ const Main: FC<IMainProps> = ({
/>
)}
{/* {isNewConversation ? 'new' : 'exist'}
{JSON.stringify(newConversationInputs ? newConversationInputs : {})}
{JSON.stringify(existConversationInputs ? existConversationInputs : {})} */}
<div
className={cn(
'flex rounded-t-2xl bg-white overflow-hidden',

View File

@@ -1,32 +1,33 @@
import React, { useRef } from 'react'
import React, { useEffect, useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import {
ChatBubbleOvalLeftEllipsisIcon,
PencilSquareIcon,
} from '@heroicons/react/24/outline'
import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
import { useInfiniteScroll } from 'ahooks'
import cn from 'classnames'
import Button from '../../../base/button'
import List from './list'
import AppInfo from '@/app/components/share/chat/sidebar/app-info'
// import Card from './card'
import type { ConversationItem, SiteInfo } from '@/models/share'
import { fetchConversations } from '@/service/share'
function classNames(...classes: any[]) {
return classes.filter(Boolean).join(' ')
}
export type ISidebarProps = {
copyRight: string
currentId: string
onCurrentIdChange: (id: string) => void
list: ConversationItem[]
pinnedList: ConversationItem[]
isInstalledApp: boolean
installedAppId?: string
siteInfo: SiteInfo
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
isNoMore: boolean
isPinnedNoMore: boolean
onPin: (id: string) => void
onUnpin: (id: string) => void
controlUpdateList: number
}
const Sidebar: FC<ISidebarProps> = ({
@@ -34,37 +35,41 @@ const Sidebar: FC<ISidebarProps> = ({
currentId,
onCurrentIdChange,
list,
pinnedList,
isInstalledApp,
installedAppId,
siteInfo,
onMoreLoaded,
onPinnedMoreLoaded,
isNoMore,
isPinnedNoMore,
onPin,
onUnpin,
controlUpdateList,
}) => {
const { t } = useTranslation()
const listRef = useRef<HTMLDivElement>(null)
const [hasPinned, setHasPinned] = useState(false)
useInfiniteScroll(
async () => {
if (!isNoMore) {
const lastId = list[list.length - 1].id
const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId)
onMoreLoaded({ data: conversations, has_more })
}
return { list: [] }
},
{
target: listRef,
isNoMore: () => {
return isNoMore
},
reloadDeps: [isNoMore],
},
)
const checkHasPinned = async () => {
const { data }: any = await fetchConversations(isInstalledApp, installedAppId, undefined, true)
setHasPinned(data.length > 0)
}
useEffect(() => {
checkHasPinned()
}, [])
useEffect(() => {
if (controlUpdateList !== 0)
checkHasPinned()
}, [controlUpdateList])
const maxListHeight = isInstalledApp ? 'max-h-[30vh]' : 'max-h-[40vh]'
return (
<div
className={
classNames(
cn(
isInstalledApp ? 'tablet:h-[calc(100vh_-_74px)]' : 'tablet:h-[calc(100vh_-_3rem)]',
'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen',
)
@@ -85,40 +90,46 @@ const Sidebar: FC<ISidebarProps> = ({
<PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')}
</Button>
</div>
<nav
ref={listRef}
className="mt-4 flex-1 space-y-1 bg-white p-4 !pt-0 overflow-y-auto"
>
{list.map((item) => {
const isCurrent = item.id === currentId
const ItemIcon
= isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
return (
<div
onClick={() => onCurrentIdChange(item.id)}
key={item.id}
className={classNames(
isCurrent
? 'bg-primary-50 text-primary-600'
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-700',
'group flex items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
)}
>
<ItemIcon
className={classNames(
isCurrent
? 'text-primary-600'
: 'text-gray-400 group-hover:text-gray-500',
'mr-3 h-5 w-5 flex-shrink-0',
)}
aria-hidden="true"
/>
{item.name}
</div>
)
})}
</nav>
<div className='flex-grow h-0 overflow-y-auto overflow-x-hidden'>
{/* pinned list */}
{hasPinned && (
<div className='mt-4 px-4'>
<div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div>
<List
className={maxListHeight}
currentId={currentId}
onCurrentIdChange={onCurrentIdChange}
list={pinnedList}
isInstalledApp={isInstalledApp}
installedAppId={installedAppId}
onMoreLoaded={onPinnedMoreLoaded}
isNoMore={isPinnedNoMore}
isPinned={true}
onPinChanged={id => onUnpin(id)}
controlUpdate={controlUpdateList + 1}
/>
</div>
)}
{/* unpinned list */}
<div className='mt-4 px-4'>
{hasPinned && (
<div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div>
)}
<List
className={cn(hasPinned ? maxListHeight : 'flex-grow')}
currentId={currentId}
onCurrentIdChange={onCurrentIdChange}
list={list}
isInstalledApp={isInstalledApp}
installedAppId={installedAppId}
onMoreLoaded={onMoreLoaded}
isNoMore={isNoMore}
isPinned={false}
onPinChanged={id => onPin(id)}
controlUpdate={controlUpdateList + 1}
/>
</div>
</div>
<div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
<div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
</div>

View File

@@ -0,0 +1,113 @@
'use client'
import type { FC } from 'react'
import React, { useRef } from 'react'
import {
ChatBubbleOvalLeftEllipsisIcon,
} from '@heroicons/react/24/outline'
import { useInfiniteScroll } from 'ahooks'
import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
import cn from 'classnames'
import s from './style.module.css'
import type { ConversationItem } from '@/models/share'
import { fetchConversations } from '@/service/share'
import ItemOperation from '@/app/components/explore/item-operation'
export type IListProps = {
className: string
currentId: string
onCurrentIdChange: (id: string) => void
list: ConversationItem[]
isInstalledApp: boolean
installedAppId?: string
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
isNoMore: boolean
isPinned: boolean
onPinChanged: (id: string) => void
controlUpdate: number
}
const List: FC<IListProps> = ({
className,
currentId,
onCurrentIdChange,
list,
isInstalledApp,
installedAppId,
onMoreLoaded,
isNoMore,
isPinned,
onPinChanged,
controlUpdate,
}) => {
const listRef = useRef<HTMLDivElement>(null)
useInfiniteScroll(
async () => {
if (!isNoMore) {
const lastId = list[list.length - 1]?.id
const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
onMoreLoaded({ data: conversations, has_more })
}
return { list: [] }
},
{
target: listRef,
isNoMore: () => {
return isNoMore
},
reloadDeps: [isNoMore, controlUpdate],
},
)
return (
<nav
ref={listRef}
className={cn(className, 'shrink-0 space-y-1 bg-white pb-[60px] overflow-y-auto')}
>
{list.map((item) => {
const isCurrent = item.id === currentId
const ItemIcon
= isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
return (
<div
onClick={() => onCurrentIdChange(item.id)}
key={item.id}
className={cn(s.item,
isCurrent
? 'bg-primary-50 text-primary-600'
: 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
)}
>
<div className='flex items-center w-0 grow'>
<ItemIcon
className={cn(
isCurrent
? 'text-primary-600'
: 'text-gray-400 group-hover:text-gray-500',
'mr-3 h-5 w-5 flex-shrink-0',
)}
aria-hidden="true"
/>
<span>{item.name}</span>
</div>
{
!isCurrent && (
<div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
<ItemOperation
isPinned={isPinned}
togglePin={() => onPinChanged(item.id)}
isShowDelete={false}
onDelete={() => {}}
/>
</div>
)
}
</div>
)
})}
</nav>
)
}
export default React.memo(List)

View File

@@ -0,0 +1,7 @@
.opBtn {
visibility: hidden;
}
.item:hover .opBtn {
visibility: visible;
}

View File

@@ -1,45 +1,47 @@
const translation = {
common: {
welcome: "Welcome to use",
appUnavailable: "App is unavailable",
appUnkonwError: "App is unavailable"
welcome: 'Welcome to use',
appUnavailable: 'App is unavailable',
appUnkonwError: 'App is unavailable',
},
chat: {
newChat: "New chat",
newChatDefaultName: "New conversation",
powerBy: "Powered by",
prompt: "Prompt",
privatePromptConfigTitle: "Conversation settings",
publicPromptConfigTitle: "Initial Prompt",
configStatusDes: "Before start, you can modify conversation settings",
newChat: 'New chat',
pinnedTitle: 'Pinned',
unpinnedTitle: 'Chats',
newChatDefaultName: 'New conversation',
powerBy: 'Powered by',
prompt: 'Prompt',
privatePromptConfigTitle: 'Conversation settings',
publicPromptConfigTitle: 'Initial Prompt',
configStatusDes: 'Before start, you can modify conversation settings',
configDisabled:
"Previous session settings have been used for this session.",
startChat: "Start Chat",
'Previous session settings have been used for this session.',
startChat: 'Start Chat',
privacyPolicyLeft:
"Please read the ",
'Please read the ',
privacyPolicyMiddle:
"privacy policy",
'privacy policy',
privacyPolicyRight:
" provided by the app developer.",
' provided by the app developer.',
},
generation: {
tabs: {
create: "Create",
saved: "Saved",
create: 'Create',
saved: 'Saved',
},
savedNoData: {
title: "You haven't saved a result yet!",
title: 'You haven\'t saved a result yet!',
description: 'Start generating content, and find your saved results here.',
startCreateContent: 'Start create content'
startCreateContent: 'Start create content',
},
title: "AI Completion",
queryTitle: "Query content",
queryPlaceholder: "Write your query content...",
run: "RUN",
copy: "Copy",
resultTitle: "AI Completion",
noData: "AI will give you what you want here.",
title: 'AI Completion',
queryTitle: 'Query content',
queryPlaceholder: 'Write your query content...',
run: 'RUN',
copy: 'Copy',
resultTitle: 'AI Completion',
noData: 'AI will give you what you want here.',
},
};
}
export default translation;
export default translation

View File

@@ -1,41 +1,43 @@
const translation = {
common: {
welcome: "欢迎使用",
appUnavailable: "应用不可用",
appUnkonwError: "应用不可用",
welcome: '欢迎使用',
appUnavailable: '应用不可用',
appUnkonwError: '应用不可用',
},
chat: {
newChat: "新对话",
newChatDefaultName: "新的对话",
powerBy: "Powered by",
prompt: "提示词",
privatePromptConfigTitle: "对话设置",
publicPromptConfigTitle: "对话前提示词",
configStatusDes: "开始前,您可以修改对话设置",
configDisabled: "此次会话已使用上次会话表单",
startChat: "开始对话",
privacyPolicyLeft: "请阅读由该应用开发者提供的",
privacyPolicyMiddle: "隐私政策",
privacyPolicyRight: "。",
newChat: '新对话',
pinnedTitle: '已置顶',
unpinnedTitle: '对话列表',
newChatDefaultName: '新的对话',
powerBy: 'Powered by',
prompt: '提示词',
privatePromptConfigTitle: '对话设置',
publicPromptConfigTitle: '对话前提示词',
configStatusDes: '开始前,您可以修改对话设置',
configDisabled: '此次会话已使用上次会话表单',
startChat: '开始对话',
privacyPolicyLeft: '请阅读由该应用开发者提供的',
privacyPolicyMiddle: '隐私政策',
privacyPolicyRight: '。',
},
generation: {
tabs: {
create: "创建",
saved: "已保存",
create: '创建',
saved: '已保存',
},
savedNoData: {
title: "您还没有保存结果!",
title: '您还没有保存结果!',
description: '开始生成内容,您可以在这里找到保存的结果。',
startCreateContent: '开始生成内容'
startCreateContent: '开始生成内容',
},
title: "AI 智能书写",
queryTitle: "查询内容",
queryPlaceholder: "请输入文本内容",
run: "运行",
copy: "拷贝",
resultTitle: "AI 书写",
noData: "AI 会在这里给你惊喜。",
title: 'AI 智能书写',
queryTitle: '查询内容',
queryPlaceholder: '请输入文本内容',
run: '运行',
copy: '拷贝',
resultTitle: 'AI 书写',
noData: 'AI 会在这里给你惊喜。',
},
};
}
export default translation;
export default translation

View File

@@ -1,16 +1,18 @@
import type { IOnCompleted, IOnData, IOnError } from './base'
import {
del as consoleDel, get as consoleGet, post as consolePost,
delPublic as del, getPublic as get, postPublic as post, ssePost,
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
} from './base'
import type { Feedbacktype } from '@/app/components/app/chat'
function getAction(action: 'get' | 'post' | 'del', isInstalledApp: boolean) {
function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) {
switch (action) {
case 'get':
return isInstalledApp ? consoleGet : get
case 'post':
return isInstalledApp ? consolePost : post
case 'patch':
return isInstalledApp ? consolePatch : patch
case 'del':
return isInstalledApp ? consoleDel : del
}
@@ -55,8 +57,16 @@ export const fetchAppInfo = async () => {
return get('/site')
}
export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string) => {
return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { ...{ limit: 20 }, ...(last_id ? { last_id } : {}) } })
export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string, pinned?: boolean, limit?: number) => {
return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { ...{ limit: limit || 20 }, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}) } })
}
export const pinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
return getAction('patch', isInstalledApp)(getUrl(`conversations/${id}/pin`, isInstalledApp, installedAppId))
}
export const unpinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
return getAction('patch', isInstalledApp)(getUrl(`conversations/${id}/unpin`, isInstalledApp, installedAppId))
}
export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId = '') => {