-
Notifications
You must be signed in to change notification settings - Fork 244
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #61 from Ray-D-Song/aitag
Aitag
- Loading branch information
Showing
21 changed files
with
982 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
packages/plugin/popup/components/FolderSelectWithCache.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { PlusIcon } from 'lucide-react' | ||
import { Button } from '@web-archive/shared/components/button' | ||
import { isNotNil } from '@web-archive/shared/utils' | ||
import { sendMessage } from 'webext-bridge/popup' | ||
import { useRequest } from 'ahooks' | ||
import { useState } from 'react' | ||
import FolderCombobox from './FolderCombobox' | ||
import NewFolderDialog from './NewFolderDialog' | ||
|
||
interface FolderSelectWithCacheProps { | ||
value: string | undefined | ||
onValueChange: (value: string | undefined) => void | ||
} | ||
|
||
async function getAllFolders() { | ||
const { folders } = await sendMessage('get-all-folders', {}) | ||
await new Promise(resolve => setTimeout(resolve, 2000)) | ||
return folders | ||
} | ||
|
||
export function getLastChooseFolderId() { | ||
return localStorage.getItem('lastChooseFolderId') || undefined | ||
} | ||
|
||
function FolderSelectWithCache({ value, onValueChange }: FolderSelectWithCacheProps) { | ||
const lastChooseFolderId = getLastChooseFolderId() | ||
const { data: folderList, refresh: refreshFolderList, mutate: setFolderList } = useRequest(getAllFolders, { | ||
cacheKey: 'folderList', | ||
setCache: (data) => { | ||
localStorage.setItem('folderList', JSON.stringify(data)) | ||
}, | ||
getCache: () => { | ||
const cache = localStorage.getItem('folderList') | ||
return cache ? JSON.parse(cache) : [] | ||
}, | ||
onSuccess: (data) => { | ||
if (isNotNil(value) && !data.some(folder => folder.id.toString() === value)) { | ||
onValueChange(undefined) | ||
lastChooseFolderId && localStorage.removeItem('lastChooseFolderId') | ||
} | ||
}, | ||
}) | ||
|
||
const [newFolderDialogVisible, setNewFolderDialogVisible] = useState(false) | ||
function handleNewFolderAdded(folder: { id: number, name: string }) { | ||
setFolderList(prevList => [ | ||
...(prevList ?? []), | ||
folder, | ||
]) | ||
onValueChange(folder.id.toString()) | ||
localStorage.setItem('lastChooseFolderId', folder.id.toString()) | ||
refreshFolderList() | ||
} | ||
|
||
function handleFolderSelect(newFolder: string) { | ||
localStorage.setItem('lastChooseFolderId', newFolder) | ||
onValueChange(newFolder) | ||
} | ||
|
||
return ( | ||
<div className="flex space-x-2"> | ||
<NewFolderDialog | ||
open={newFolderDialogVisible} | ||
afterSubmit={handleNewFolderAdded} | ||
setOpen={setNewFolderDialogVisible} | ||
> | ||
</NewFolderDialog> | ||
<div className="flex-1"> | ||
{/* | ||
use combobox instead of select due to Select(v2.1.2) will auto close when resize event is fired | ||
and popup in firefox will fire resize event after DOM mutations | ||
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Popups#:~:text=In%20Firefox%2C%20the%20size%20is%20calculated%20just%20before%20the%20popup%20is%20shown%2C%20and%20at%20most%2010%20times%20per%20second%20after%20DOM%20mutations. | ||
*/} | ||
<FolderCombobox | ||
value={value} | ||
onValueChange={handleFolderSelect} | ||
options={(folderList ?? []).map(folder => ({ | ||
value: folder.id.toString(), | ||
label: folder.name, | ||
}))} | ||
> | ||
</FolderCombobox> | ||
</div> | ||
<Button | ||
variant="secondary" | ||
className="h-10" | ||
size="icon" | ||
onClick={() => setNewFolderDialogVisible(true)} | ||
> | ||
<PlusIcon size={18}></PlusIcon> | ||
</Button> | ||
</div> | ||
) | ||
} | ||
|
||
export default FolderSelectWithCache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { useRef } from 'react' | ||
import type { AutoCompleteTagInputRef } from '@web-archive/shared/components/auto-complete-tag-input' | ||
import AutoCompleteTagInput from '@web-archive/shared/components/auto-complete-tag-input' | ||
import { Button } from '@web-archive/shared/components/button' | ||
import type { GenerateTagProps } from '@web-archive/shared/utils' | ||
import { generateTagByOpenAI, isNil } from '@web-archive/shared/utils' | ||
import { useRequest } from 'ahooks' | ||
import { AlertCircleIcon, Loader2Icon, SparklesIcon } from 'lucide-react' | ||
import { sendMessage } from 'webext-bridge/popup' | ||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@web-archive/shared/components/tooltip' | ||
import toast from 'react-hot-toast' | ||
|
||
async function getAllTags() { | ||
const { tags } = await sendMessage('get-all-tags', {}) | ||
return tags | ||
} | ||
|
||
async function getAITagConfig() { | ||
const { aiTagConfig } = await sendMessage('get-ai-tag-config', {}) | ||
return aiTagConfig | ||
} | ||
|
||
async function doGenerateTag(props: GenerateTagProps) { | ||
if (!props.model) { | ||
toast.error('Please configure in website settings first') | ||
throw new Error('Invalid AI tag config') | ||
} | ||
if (props.type === 'cloudflare') { | ||
const { tags } = await sendMessage('generate-tag', props) | ||
return tags | ||
} | ||
return await generateTagByOpenAI(props) | ||
} | ||
|
||
interface TagInputWithCacheProps { | ||
title: string | ||
description: string | ||
onValueChange: (value: string[]) => void | ||
} | ||
|
||
function TagInputWithCache({ onValueChange, title, description }: TagInputWithCacheProps) { | ||
const tagInputRef = useRef<AutoCompleteTagInputRef>(null) | ||
const { data: tagList } = useRequest(getAllTags, { | ||
cacheKey: 'tagList', | ||
setCache: (data) => { | ||
localStorage.setItem('tagList', JSON.stringify(data)) | ||
}, | ||
getCache: () => { | ||
const cache = localStorage.getItem('tagList') | ||
return cache ? JSON.parse(cache) : [] | ||
}, | ||
}) | ||
|
||
const { data: aiTagConfig } = useRequest(getAITagConfig, { | ||
cacheKey: 'aiTagConfig', | ||
setCache: (data) => { | ||
localStorage.setItem('aiTagConfig', JSON.stringify(data)) | ||
}, | ||
getCache: () => { | ||
const cache = localStorage.getItem('aiTagConfig') | ||
return cache ? JSON.parse(cache) : [] | ||
}, | ||
}) | ||
|
||
const { run: generateTagRun, loading: generateTagRunning, error: generateTagError } = useRequest( | ||
doGenerateTag, | ||
{ | ||
manual: true, | ||
onSuccess: (data) => { | ||
tagInputRef.current?.addTags(data) | ||
}, | ||
onError: (error) => { | ||
console.error(error) | ||
toast.error(error?.message) | ||
}, | ||
}, | ||
) | ||
|
||
return ( | ||
<div className="flex space-x-2"> | ||
<AutoCompleteTagInput | ||
ref={tagInputRef} | ||
tags={tagList ?? []} | ||
shouldLimitHeight | ||
onChange={({ bindTags }) => { | ||
onValueChange(bindTags) | ||
}} | ||
> | ||
</AutoCompleteTagInput> | ||
<TooltipProvider> | ||
<Tooltip> | ||
<TooltipTrigger asChild> | ||
<Button | ||
className="w-12 h-10 " | ||
variant="secondary" | ||
size="icon" | ||
disabled={generateTagRunning} | ||
onClick={() => { | ||
generateTagRun({ | ||
...aiTagConfig, | ||
title, | ||
pageDesc: description, | ||
}) | ||
}} | ||
> | ||
{ | ||
generateTagRunning | ||
? <Loader2Icon size={18} className="animate-spin"></Loader2Icon> | ||
: ( | ||
generateTagError | ||
? <AlertCircleIcon size={18} className="text-destructive"></AlertCircleIcon> | ||
: <SparklesIcon size={18} className="opacity-80"></SparklesIcon> | ||
) | ||
} | ||
</Button> | ||
</TooltipTrigger> | ||
{ | ||
generateTagError && ( | ||
<TooltipContent> | ||
{generateTagError.message} | ||
</TooltipContent> | ||
) | ||
} | ||
</Tooltip> | ||
</TooltipProvider> | ||
</div> | ||
) | ||
} | ||
|
||
export default TagInputWithCache |
Oops, something went wrong.