optimize fm (#82)

* optimize fm

* chore: auto-fix linting and formatting issues

* fix: type

* chore: auto-fix linting and formatting issues

---------

Co-authored-by: uubulb <uubulb@users.noreply.github.com>
Co-authored-by: naiba <hi@nai.ba>
Co-authored-by: naiba <naiba@users.noreply.github.com>
This commit is contained in:
UUBulb
2024-12-29 22:19:50 +08:00
committed by GitHub
parent 97a5deb648
commit 01add8b160
12 changed files with 164 additions and 120 deletions

View File

@@ -7,27 +7,45 @@ export enum Oauth2RequestType {
BIND = 2, BIND = 2,
} }
export const getOauth2RedirectURL = async (provider: string, rType: Oauth2RequestType): Promise<ModelOauth2LoginResponse> => { export const getOauth2RedirectURL = async (
provider: string,
rType: Oauth2RequestType,
): Promise<ModelOauth2LoginResponse> => {
const sType = "type"
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.GET, `/api/v1/oauth2/${provider}`, { return fetcher<ModelOauth2LoginResponse>(FetcherMethod.GET, `/api/v1/oauth2/${provider}`, {
"type": rType, sType: rType,
}) })
} }
export const bindOauth2 = async (
export const bindOauth2 = async (provider: string, state: string, code: string): Promise<ModelOauth2LoginResponse> => { provider: string,
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/bind`, { state: string,
"state": state, code: string,
"code": code, ): Promise<ModelOauth2LoginResponse> => {
}) return fetcher<ModelOauth2LoginResponse>(
FetcherMethod.POST,
`/api/v1/oauth2/${provider}/bind`,
{
state: state,
code: code,
},
)
} }
export const unbindOauth2 = async (provider: string): Promise<ModelOauth2LoginResponse> => { export const unbindOauth2 = async (provider: string): Promise<ModelOauth2LoginResponse> => {
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/unbind`) return fetcher<ModelOauth2LoginResponse>(
FetcherMethod.POST,
`/api/v1/oauth2/${provider}/unbind`,
)
} }
export const oauth2callback = async (
export const oauth2callback = async (provider: string, state: string, code: string): Promise<void> => { provider: string,
state: string,
code: string,
): Promise<void> => {
return fetcher<void>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/callback`, { return fetcher<void>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/callback`, {
state, code state,
code,
}) })
} }

View File

@@ -41,6 +41,7 @@ import { HTMLAttributes, useEffect, useRef, useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { toast } from "sonner" import { toast } from "sonner"
import { Button } from "./ui/button"
import { TableCell, TableRow } from "./ui/table" import { TableCell, TableRow } from "./ui/table"
import { Filepath } from "./xui/filepath" import { Filepath } from "./xui/filepath"
import { IconButton } from "./xui/icon-button" import { IconButton } from "./xui/icon-button"
@@ -102,7 +103,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
header: () => <span>{t("Actions")}</span>, header: () => <span>{t("Actions")}</span>,
id: "download", id: "download",
cell: ({ row }) => { cell: ({ row }) => {
return ( return row.original.type == 0 ? (
<IconButton <IconButton
variant="ghost" variant="ghost"
icon="download" icon="download"
@@ -111,6 +112,8 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
downloadFile(row.original.name) downloadFile(row.original.name)
}} }}
/> />
) : (
<Button size="icon" variant="ghost" className="pointer-events-none" />
) )
}, },
}, },
@@ -157,6 +160,70 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
} }
} }
useEffect(() => {
const ws = new WebSocket(wsUrl)
wsRef.current = ws
ws.binaryType = "arraybuffer"
ws.onopen = () => {
listFile()
}
ws.onclose = (e) => {
console.log("WebSocket connection closed:", e)
}
ws.onerror = (e) => {
console.error(e)
toast("Websocket" + " " + t("Error"), {
description: t("Results.UnExpectedError"),
})
}
ws.onmessage = async (e: MessageEvent<ArrayBufferLike>) => {
try {
const identifier = new Uint8Array(e.data, 0, 4)
if (arraysEqual(identifier, FMIdentifier.error)) {
const errBytes = e.data.slice(4)
const errMsg = new TextDecoder("utf-8").decode(errBytes)
throw new Error(errMsg)
}
if (firstChunk.current) {
if (arraysEqual(identifier, FMIdentifier.file)) {
worker.postMessage({
operation: 1,
arrayBuffer: e.data,
fileName: currentBasename.current,
})
firstChunk.current = false
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
const { path, fmList } = await fm.parseFMList(e.data)
setPath(path)
setFMEntries(fmList)
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
// Upload completed
if (uOpen) setuOpen(false)
listFile()
} else {
throw new Error(t("Results.UnknownIdentifier"))
}
} else {
await waitForHandleReady()
worker.postMessage({
operation: 2,
arrayBuffer: e.data,
fileName: currentBasename.current,
})
}
} catch (error) {
console.error("Error processing received data:", error)
toast("FM" + " " + t("Error"), {
description: t("Results.UnExpectedError"),
})
setdOpen(false)
setuOpen(false)
}
}
}, [wsUrl])
useEffect(() => {
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => { worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
switch (event.data.type) { switch (event.data.type) {
case FMWorkerOpcode.Error: { case FMWorkerOpcode.Error: {
@@ -186,6 +253,18 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
} }
} }
const handleBeforeUnload = () => {
worker.postMessage({
operation: 3,
})
}
window.addEventListener("beforeunload", handleBeforeUnload)
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload)
}
}, [worker, dOpen])
const [currentPath, setPath] = useState("") const [currentPath, setPath] = useState("")
useEffect(() => { useEffect(() => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
@@ -193,69 +272,6 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
} }
}, [wsRef.current, currentPath]) }, [wsRef.current, currentPath])
useEffect(() => {
const ws = new WebSocket(wsUrl)
wsRef.current = ws
ws.binaryType = "arraybuffer"
ws.onopen = () => {
listFile()
}
ws.onclose = (e) => {
console.log("WebSocket connection closed:", e)
}
ws.onerror = (e) => {
console.error(e)
toast("Websocket" + " " + t("Error"), {
description: t("Results.UnExpectedError"),
})
}
ws.onmessage = async (e) => {
try {
const buf: ArrayBufferLike = e.data
if (firstChunk.current) {
const identifier = new Uint8Array(buf, 0, 4)
if (arraysEqual(identifier, FMIdentifier.file)) {
worker.postMessage({
operation: 1,
arrayBuffer: buf,
fileName: currentBasename.current,
})
firstChunk.current = false
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
const { path, fmList } = await fm.parseFMList(buf)
setPath(path)
setFMEntries(fmList)
} else if (arraysEqual(identifier, FMIdentifier.error)) {
const errBytes = buf.slice(4)
const errMsg = new TextDecoder("utf-8").decode(errBytes)
throw new Error(errMsg)
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
// Upload completed
if (uOpen) setuOpen(false)
listFile()
} else {
throw new Error(t("Results.UnknownIdentifier"))
}
} else {
await waitForHandleReady()
worker.postMessage({
operation: 2,
arrayBuffer: buf,
fileName: currentBasename.current,
})
}
} catch (error) {
console.error("Error processing received data:", error)
toast("FM" + " " + t("Error"), {
description: t("Results.UnExpectedError"),
})
if (dOpen) setdOpen(false)
if (uOpen) setuOpen(false)
}
}
}, [wsUrl])
const listFile = () => { const listFile = () => {
const prefix = new Int8Array([FMOpcode.List]) const prefix = new Int8Array([FMOpcode.List])
const pathMsg = new TextEncoder().encode(currentPath) const pathMsg = new TextEncoder().encode(currentPath)
@@ -317,7 +333,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
toast("FM" + " " + t("Error"), { toast("FM" + " " + t("Error"), {
description: error.message, description: error.message,
}) })
console.log("copy error: ", error) console.error("copy error: ", error)
} }
}} }}
> >

View File

@@ -8,7 +8,7 @@ import {
import { useAuth } from "@/hooks/useAuth" import { useAuth } from "@/hooks/useAuth"
import useSettings from "@/hooks/useSetting" import useSettings from "@/hooks/useSetting"
import { copyToClipboard } from "@/lib/utils" import { copyToClipboard } from "@/lib/utils"
import { ModelProfile, ModelConfig } from "@/types" import { ModelConfig, ModelProfile } from "@/types"
import i18next from "i18next" import i18next from "i18next"
import { Check, Clipboard } from "lucide-react" import { Check, Clipboard } from "lucide-react"
import { forwardRef, useState } from "react" import { forwardRef, useState } from "react"

View File

@@ -52,7 +52,7 @@ export const Filepath: React.FC<FilepathProps> = ({ path, setPath }) => {
setPath("/") setPath("/")
}} }}
> >
{"/"} /
</p> </p>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />

View File

@@ -1,3 +1,4 @@
import { oauth2callback } from "@/api/oauth2"
import { getProfile, login as loginRequest } from "@/api/user" import { getProfile, login as loginRequest } from "@/api/user"
import { AuthContextProps } from "@/types" import { AuthContextProps } from "@/types"
import { createContext, useContext, useEffect, useMemo } from "react" import { createContext, useContext, useEffect, useMemo } from "react"
@@ -5,7 +6,6 @@ import { useNavigate } from "react-router-dom"
import { toast } from "sonner" import { toast } from "sonner"
import { useMainStore } from "./useMainStore" import { useMainStore } from "./useMainStore"
import { oauth2callback } from "@/api/oauth2"
const AuthContext = createContext<AuthContextProps>({ const AuthContext = createContext<AuthContextProps>({
profile: undefined, profile: undefined,

View File

@@ -3,6 +3,8 @@ import { GithubComNezhahqNezhaModelSettingResponseModelConfig } from "@/types"
import useSWR from "swr" import useSWR from "swr"
export default function useSetting() { export default function useSetting() {
return useSWR<GithubComNezhahqNezhaModelSettingResponseModelConfig>("/api/v1/setting", swrFetcher) return useSWR<GithubComNezhahqNezhaModelSettingResponseModelConfig>(
"/api/v1/setting",
swrFetcher,
)
} }

View File

@@ -38,9 +38,8 @@ onmessage = async function (event) {
throw new Error("accessHandle is undefined") throw new Error("accessHandle is undefined")
} }
const dataChunk = arrayBuffer accessHandle.write(arrayBuffer, { at: receivedLength })
accessHandle.write(dataChunk, { at: receivedLength }) receivedLength += arrayBuffer.byteLength
receivedLength += dataChunk.byteLength
if (receivedLength === expectedLength) { if (receivedLength === expectedLength) {
accessHandle.flush() accessHandle.flush()

View File

@@ -1,4 +1,4 @@
import { getOauth2RedirectURL, Oauth2RequestType } from "@/api/oauth2" import { Oauth2RequestType, getOauth2RedirectURL } from "@/api/oauth2"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
Form, Form,
@@ -103,9 +103,9 @@ function Login() {
</form> </form>
</Form> </Form>
<div className="mt-4"> <div className="mt-4">
{settingData?.config?.oauth2_providers?.map((p: string) => {settingData?.config?.oauth2_providers?.map((p: string) => (
<Button onClick={() => loginWith(p)}>{p}</Button> <Button onClick={() => loginWith(p)}>{p}</Button>
)} ))}
</div> </div>
</div> </div>
) )

View File

@@ -1,4 +1,4 @@
import { bindOauth2, getOauth2RedirectURL, Oauth2RequestType, unbindOauth2 } from "@/api/oauth2" import { Oauth2RequestType, bindOauth2, getOauth2RedirectURL, unbindOauth2 } from "@/api/oauth2"
import { getProfile } from "@/api/user" import { getProfile } from "@/api/user"
import { ProfileCard } from "@/components/profile" import { ProfileCard } from "@/components/profile"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
@@ -31,7 +31,8 @@ export default function ProfilePage() {
getProfile().then((profile) => { getProfile().then((profile) => {
setProfile(profile) setProfile(profile)
}) })
}).finally(() => { })
.finally(() => {
window.history.replaceState({}, document.title, window.location.pathname) window.history.replaceState({}, document.title, window.location.pathname)
}) })
} }
@@ -116,12 +117,20 @@ export default function ProfilePage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-lg font-semibold"> <CardContent className="text-lg font-semibold">
{settingData?.config?.oauth2_providers?.map((provider) => <div> {settingData?.config?.oauth2_providers?.map((provider) => (
{provider}: {profile.oauth2_bind?.[provider.toLowerCase()]} {profile.oauth2_bind?.[provider.toLowerCase()] ? <div>
<Button size="sm" onClick={() => unbindO2(provider)}>Unbind</Button> {provider}: {profile.oauth2_bind?.[provider.toLowerCase()]}{" "}
: {profile.oauth2_bind?.[provider.toLowerCase()] ? (
<Button size="sm" onClick={() => bindO2(provider)}>Bind</Button>} <Button size="sm" onClick={() => unbindO2(provider)}>
</div>)} Unbind
</Button>
) : (
<Button size="sm" onClick={() => bindO2(provider)}>
Bind
</Button>
)}
</div>
))}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>