asasvirtuais 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/actions/draw.ts +110 -0
- package/components/OAuthCard.tsx +346 -0
- package/components/icons.tsx +11 -0
- package/components/markdown.tsx +18 -0
- package/components/stack/list.tsx +21 -0
- package/components/stack/menu.tsx +40 -0
- package/components/stack/nav.tsx +39 -0
- package/components/table/fixed.tsx +59 -0
- package/components/table/key-value.tsx +19 -0
- package/components/ui/color-mode.tsx +108 -0
- package/components/ui/provider.tsx +15 -0
- package/components/ui/toaster.tsx +43 -0
- package/components/ui/tooltip.tsx +46 -0
- package/hooks/useBoolean.tsx +11 -0
- package/hooks/useForwardAs.tsx +29 -0
- package/hooks/useHash copy.tsx +27 -0
- package/hooks/useHash.tsx +27 -0
- package/hooks/useIsMobile.tsx +6 -0
- package/hooks/useOAuthTokens.ts +97 -0
- package/hooks/useOpenRouterModels.ts +80 -0
- package/lib/auth0.ts +11 -0
- package/lib/blob.ts +3 -0
- package/lib/client-token-storage.ts +216 -0
- package/lib/oauth-tokens.ts +85 -0
- package/lib/react/context.tsx +20 -0
- package/lib/react/index.ts +1 -0
- package/lib/tools.ts +375 -0
- package/next-env.d.ts +5 -0
- package/next.config.ts +23 -0
- package/package.json +72 -0
- package/packages/blob.ts +97 -0
- package/packages/chat/components/chat/feed/index.tsx +76 -0
- package/packages/chat/components/chat/feed/story.tsx +18 -0
- package/packages/chat/components/chat/index.tsx +16 -0
- package/packages/chat/components/chat/story.tsx +74 -0
- package/packages/chat/components/debug/index.tsx +54 -0
- package/packages/chat/components/header/index.tsx +14 -0
- package/packages/chat/components/header/menu/index.tsx +63 -0
- package/packages/chat/components/header/story.tsx +33 -0
- package/packages/chat/components/header/title/index.tsx +35 -0
- package/packages/chat/components/index.ts +13 -0
- package/packages/chat/components/input/index.tsx +17 -0
- package/packages/chat/components/input/menu/index.tsx +35 -0
- package/packages/chat/components/input/send.tsx +21 -0
- package/packages/chat/components/input/story.tsx +35 -0
- package/packages/chat/components/input/textarea/index.tsx +20 -0
- package/packages/chat/components/message/file.tsx +103 -0
- package/packages/chat/components/message/menu/index.tsx +26 -0
- package/packages/chat/components/message/story.tsx +49 -0
- package/packages/chat/components/messages/index.tsx +23 -0
- package/packages/chat/components/messages/story.tsx +11 -0
- package/packages/chat/components/ui/prose.tsx +263 -0
- package/packages/chat/edit-message.tsx +49 -0
- package/packages/chat/header.tsx +118 -0
- package/packages/chat/index.ts +14 -0
- package/packages/chat/input.tsx +89 -0
- package/packages/chat/message-menu.tsx +57 -0
- package/packages/chat/message.tsx +44 -0
- package/packages/chat/messages.tsx +44 -0
- package/packages/chat/model-selector.tsx +172 -0
- package/packages/chat/scenarios.tsx +68 -0
- package/packages/chat/settings.tsx +98 -0
- package/packages/chat/temperature-slider.tsx +67 -0
- package/packages/chat/tool-results.tsx +32 -0
- package/packages/crud/core.ts +75 -0
- package/packages/crud/fetcher.ts +64 -0
- package/packages/crud/index.ts +2 -0
- package/packages/crud/next.ts +128 -0
- package/packages/crud/react.tsx +365 -0
- package/packages/env.ts +8 -0
- package/packages/fields.tsx +157 -0
- package/packages/firebase.ts +13 -0
- package/packages/firestore.ts +51 -0
- package/packages/form.tsx +66 -0
- package/packages/next.ts +64 -0
- package/packages/openrouter.ts +4 -0
- package/packages/react/context.tsx +21 -0
- package/packages/react/crud.tsx +372 -0
- package/packages/react/hooks.ts +90 -0
- package/packages/react/store.tsx +20 -0
- package/packages/replit-db.ts +219 -0
- package/packages/wretch.ts +22 -0
- package/packages/yaml.ts +163 -0
- package/pnpm-workspace.yaml +4 -0
- package/server/db.ts +15 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Table, TableCell, TableColumnHeader, TableColumnProps, TableRow } from '@chakra-ui/react/table'
|
|
2
|
+
import { FunctionComponent, PropsWithChildren, ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
export type FixedTableProps = Table.RootProps & { guide: ReactNode }
|
|
5
|
+
|
|
6
|
+
export default function FixedTable( { children, guide, ...props } : FixedTableProps ) {
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Table.ScrollArea>
|
|
10
|
+
<Table.Root tableLayout='fixed' {...props}>
|
|
11
|
+
<Table.Header position='sticky' top={0} zIndex={2}>
|
|
12
|
+
{guide}
|
|
13
|
+
</Table.Header>
|
|
14
|
+
<Table.Body zIndex={1}>
|
|
15
|
+
{children}
|
|
16
|
+
</Table.Body>
|
|
17
|
+
<Table.Footer position='sticky' bottom={0} zIndex={2}>
|
|
18
|
+
{guide}
|
|
19
|
+
</Table.Footer>
|
|
20
|
+
</Table.Root>
|
|
21
|
+
</Table.ScrollArea>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type TitleSizes = {
|
|
26
|
+
[KEY in string]: {
|
|
27
|
+
label: string,
|
|
28
|
+
width: TableColumnProps['w'],
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type FixedTableComponents<T extends TitleSizes> = {
|
|
33
|
+
Components: {
|
|
34
|
+
[K in keyof T] : {
|
|
35
|
+
Header: FunctionComponent,
|
|
36
|
+
Data: FunctionComponent<PropsWithChildren>
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
HeadersRow: FunctionComponent,
|
|
40
|
+
}
|
|
41
|
+
export const makeFixedTableComponents = <T extends TitleSizes>(titleSizes: T): FixedTableComponents<T> => {
|
|
42
|
+
const Components = Object.fromEntries(
|
|
43
|
+
Object.entries(titleSizes).map(([key, {label, width}]) => [key, {
|
|
44
|
+
Header: () => <TableColumnHeader w={width}>{label}</TableColumnHeader>,
|
|
45
|
+
Data: ({children}: PropsWithChildren) => <TableCell w={width}>{children}</TableCell>,
|
|
46
|
+
}])
|
|
47
|
+
)
|
|
48
|
+
const HeadersRow = () => (
|
|
49
|
+
<TableRow>
|
|
50
|
+
{Object.entries(Components).map(([key, { Header }], i) => (
|
|
51
|
+
<Header key={key + i}/>
|
|
52
|
+
))}
|
|
53
|
+
</TableRow>
|
|
54
|
+
)
|
|
55
|
+
return {
|
|
56
|
+
Components,
|
|
57
|
+
HeadersRow,
|
|
58
|
+
} as unknown as FixedTableComponents<T>
|
|
59
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Table } from '@chakra-ui/react/table'
|
|
2
|
+
|
|
3
|
+
export type KeyValueTableProps = Table.RootProps & { values: { [K in string | number]: string | number } }
|
|
4
|
+
|
|
5
|
+
export default function KeyValueTable( { children, ...props } : KeyValueTableProps ) {
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<Table.Root variant='outline' {...props}>
|
|
9
|
+
<Table.Body zIndex={1}>
|
|
10
|
+
{Object.entries(props.values).map(([key, value]) => (
|
|
11
|
+
<Table.Row key={key}>
|
|
12
|
+
<Table.ColumnHeader>{key}</Table.ColumnHeader>
|
|
13
|
+
<Table.Cell>{value}</Table.Cell>
|
|
14
|
+
</Table.Row>
|
|
15
|
+
))}
|
|
16
|
+
</Table.Body>
|
|
17
|
+
</Table.Root>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type { IconButtonProps, SpanProps } from "@chakra-ui/react"
|
|
4
|
+
import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react"
|
|
5
|
+
import { ThemeProvider, useTheme } from "next-themes"
|
|
6
|
+
import type { ThemeProviderProps } from "next-themes"
|
|
7
|
+
import * as React from "react"
|
|
8
|
+
import { LuMoon, LuSun } from "react-icons/lu"
|
|
9
|
+
|
|
10
|
+
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
|
11
|
+
|
|
12
|
+
export function ColorModeProvider(props: ColorModeProviderProps) {
|
|
13
|
+
return (
|
|
14
|
+
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ColorMode = "light" | "dark"
|
|
19
|
+
|
|
20
|
+
export interface UseColorModeReturn {
|
|
21
|
+
colorMode: ColorMode
|
|
22
|
+
setColorMode: (colorMode: ColorMode) => void
|
|
23
|
+
toggleColorMode: () => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useColorMode(): UseColorModeReturn {
|
|
27
|
+
const { resolvedTheme, setTheme, forcedTheme } = useTheme()
|
|
28
|
+
const colorMode = forcedTheme || resolvedTheme
|
|
29
|
+
const toggleColorMode = () => {
|
|
30
|
+
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
colorMode: colorMode as ColorMode,
|
|
34
|
+
setColorMode: setTheme,
|
|
35
|
+
toggleColorMode,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useColorModeValue<T>(light: T, dark: T) {
|
|
40
|
+
const { colorMode } = useColorMode()
|
|
41
|
+
return colorMode === "dark" ? dark : light
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ColorModeIcon() {
|
|
45
|
+
const { colorMode } = useColorMode()
|
|
46
|
+
return colorMode === "dark" ? <LuMoon /> : <LuSun />
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
|
|
50
|
+
|
|
51
|
+
export const ColorModeButton = React.forwardRef<
|
|
52
|
+
HTMLButtonElement,
|
|
53
|
+
ColorModeButtonProps
|
|
54
|
+
>(function ColorModeButton(props, ref) {
|
|
55
|
+
const { toggleColorMode } = useColorMode()
|
|
56
|
+
return (
|
|
57
|
+
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
|
58
|
+
<IconButton
|
|
59
|
+
onClick={toggleColorMode}
|
|
60
|
+
variant="ghost"
|
|
61
|
+
aria-label="Toggle color mode"
|
|
62
|
+
size="sm"
|
|
63
|
+
ref={ref}
|
|
64
|
+
{...props}
|
|
65
|
+
css={{
|
|
66
|
+
_icon: {
|
|
67
|
+
width: "5",
|
|
68
|
+
height: "5",
|
|
69
|
+
},
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<ColorModeIcon />
|
|
73
|
+
</IconButton>
|
|
74
|
+
</ClientOnly>
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
|
|
79
|
+
function LightMode(props, ref) {
|
|
80
|
+
return (
|
|
81
|
+
<Span
|
|
82
|
+
color="fg"
|
|
83
|
+
display="contents"
|
|
84
|
+
className="chakra-theme light"
|
|
85
|
+
colorPalette="gray"
|
|
86
|
+
colorScheme="light"
|
|
87
|
+
ref={ref}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
|
|
95
|
+
function DarkMode(props, ref) {
|
|
96
|
+
return (
|
|
97
|
+
<Span
|
|
98
|
+
color="fg"
|
|
99
|
+
display="contents"
|
|
100
|
+
className="chakra-theme dark"
|
|
101
|
+
colorPalette="gray"
|
|
102
|
+
colorScheme="dark"
|
|
103
|
+
ref={ref}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
},
|
|
108
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
|
|
4
|
+
import {
|
|
5
|
+
ColorModeProvider,
|
|
6
|
+
type ColorModeProviderProps,
|
|
7
|
+
} from "./color-mode"
|
|
8
|
+
|
|
9
|
+
export function Provider(props: ColorModeProviderProps) {
|
|
10
|
+
return (
|
|
11
|
+
<ChakraProvider value={defaultSystem}>
|
|
12
|
+
<ColorModeProvider {...props} />
|
|
13
|
+
</ChakraProvider>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Toaster as ChakraToaster,
|
|
5
|
+
Portal,
|
|
6
|
+
Spinner,
|
|
7
|
+
Stack,
|
|
8
|
+
Toast,
|
|
9
|
+
createToaster,
|
|
10
|
+
} from "@chakra-ui/react"
|
|
11
|
+
|
|
12
|
+
export const toaster = createToaster({
|
|
13
|
+
placement: "bottom-end",
|
|
14
|
+
pauseOnPageIdle: true,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const Toaster = () => {
|
|
18
|
+
return (
|
|
19
|
+
<Portal>
|
|
20
|
+
<ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}>
|
|
21
|
+
{(toast) => (
|
|
22
|
+
<Toast.Root width={{ md: "sm" }}>
|
|
23
|
+
{toast.type === "loading" ? (
|
|
24
|
+
<Spinner size="sm" color="blue.solid" />
|
|
25
|
+
) : (
|
|
26
|
+
<Toast.Indicator />
|
|
27
|
+
)}
|
|
28
|
+
<Stack gap="1" flex="1" maxWidth="100%">
|
|
29
|
+
{toast.title && <Toast.Title>{toast.title}</Toast.Title>}
|
|
30
|
+
{toast.description && (
|
|
31
|
+
<Toast.Description>{toast.description}</Toast.Description>
|
|
32
|
+
)}
|
|
33
|
+
</Stack>
|
|
34
|
+
{toast.action && (
|
|
35
|
+
<Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
|
|
36
|
+
)}
|
|
37
|
+
{toast.closable && <Toast.CloseTrigger />}
|
|
38
|
+
</Toast.Root>
|
|
39
|
+
)}
|
|
40
|
+
</ChakraToaster>
|
|
41
|
+
</Portal>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
|
|
4
|
+
export interface TooltipProps extends ChakraTooltip.RootProps {
|
|
5
|
+
showArrow?: boolean
|
|
6
|
+
portalled?: boolean
|
|
7
|
+
portalRef?: React.RefObject<HTMLElement>
|
|
8
|
+
content: React.ReactNode
|
|
9
|
+
contentProps?: ChakraTooltip.ContentProps
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
14
|
+
function Tooltip(props, ref) {
|
|
15
|
+
const {
|
|
16
|
+
showArrow,
|
|
17
|
+
children,
|
|
18
|
+
disabled,
|
|
19
|
+
portalled = true,
|
|
20
|
+
content,
|
|
21
|
+
contentProps,
|
|
22
|
+
portalRef,
|
|
23
|
+
...rest
|
|
24
|
+
} = props
|
|
25
|
+
|
|
26
|
+
if (disabled) return children
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ChakraTooltip.Root {...rest}>
|
|
30
|
+
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
|
|
31
|
+
<Portal disabled={!portalled} container={portalRef}>
|
|
32
|
+
<ChakraTooltip.Positioner>
|
|
33
|
+
<ChakraTooltip.Content ref={ref} {...contentProps}>
|
|
34
|
+
{showArrow && (
|
|
35
|
+
<ChakraTooltip.Arrow>
|
|
36
|
+
<ChakraTooltip.ArrowTip />
|
|
37
|
+
</ChakraTooltip.Arrow>
|
|
38
|
+
)}
|
|
39
|
+
{content}
|
|
40
|
+
</ChakraTooltip.Content>
|
|
41
|
+
</ChakraTooltip.Positioner>
|
|
42
|
+
</Portal>
|
|
43
|
+
</ChakraTooltip.Root>
|
|
44
|
+
)
|
|
45
|
+
},
|
|
46
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useCallback, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
export default function useBoolean(initial: boolean = false) {
|
|
5
|
+
const [value, setValue] = useState(initial)
|
|
6
|
+
const on = () => setValue(true)
|
|
7
|
+
const off = () => setValue(false)
|
|
8
|
+
const toggle = () => setValue(prev => !prev)
|
|
9
|
+
const conditionalValue = useCallback( (a: any, b: any) => value ? a : b, [value] )
|
|
10
|
+
return [value, { on, off, toggle } as const, conditionalValue] as const
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Goal: combine List (which has context) with Stack (which is more geared towards CSS for Flex utility)
|
|
2
|
+
* Issue: <StackList as='menu'/> the as prop overwrites the as={Stack}
|
|
3
|
+
* Solution: The as prop has to be reapplied, so inside StackList as={<Stack as={as}}} />
|
|
4
|
+
* Issue: That prevents props from being passed to Stack
|
|
5
|
+
* Solution: use forwardRef as={forwardRef(({as, ...props}, ref) => <Stack ref={ref} as={as} {...props} /> )}
|
|
6
|
+
* Issue: This is clearly a case to apply recursion.
|
|
7
|
+
* Issue: as={forwardRef(...)} results in components being created dynamically, which might cause undesired rerenders
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
import { useMemo, forwardRef, ElementType } from 'react'
|
|
11
|
+
|
|
12
|
+
export default function useForwardAs<C extends ElementType, AS extends ElementType>(Component: C, AsComponent?: AS): C {
|
|
13
|
+
|
|
14
|
+
// @ts-expect-error
|
|
15
|
+
return useMemo(() => (
|
|
16
|
+
forwardRef((props, ref) => {
|
|
17
|
+
if (AsComponent)
|
|
18
|
+
if (typeof AsComponent === 'string')
|
|
19
|
+
// @ts-expect-error
|
|
20
|
+
return <Component ref={ref} {...props} as={AsComponent} />
|
|
21
|
+
else
|
|
22
|
+
// @ts-expect-error
|
|
23
|
+
return <AsComponent ref={ref} {...props} as={Component} />
|
|
24
|
+
else
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
return <Component ref={ref} {...props} />
|
|
27
|
+
})
|
|
28
|
+
), [AsComponent]) as C
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export default function useHash() {
|
|
6
|
+
|
|
7
|
+
const [hash, _setHash] = useState<string>('')
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
|
|
11
|
+
const handleHashChange = () => {
|
|
12
|
+
_setHash(window.location.hash.slice(1))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handleHashChange()
|
|
16
|
+
|
|
17
|
+
window.addEventListener('hashchange', handleHashChange)
|
|
18
|
+
|
|
19
|
+
return () => window.removeEventListener('hashchange', handleHashChange)
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const setHash = useCallback((hash: string) => {
|
|
23
|
+
window.location.hash = hash
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
return [hash, setHash] as const
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export default function useHash() {
|
|
6
|
+
|
|
7
|
+
const [hash, _setHash] = useState<string>('')
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
|
|
11
|
+
const handleHashChange = () => {
|
|
12
|
+
_setHash(window.location.hash.slice(1))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handleHashChange()
|
|
16
|
+
|
|
17
|
+
window.addEventListener('hashchange', handleHashChange)
|
|
18
|
+
|
|
19
|
+
return () => window.removeEventListener('hashchange', handleHashChange)
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const setHash = useCallback((hash: string) => {
|
|
23
|
+
window.location.hash = hash
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
return [hash, setHash] as const
|
|
27
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { clientTokenStorage } from '@/lib/client-token-storage'
|
|
5
|
+
|
|
6
|
+
interface TokenInfo {
|
|
7
|
+
platform: string
|
|
8
|
+
isConnected: boolean
|
|
9
|
+
expiresAt?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useOAuthTokens() {
|
|
13
|
+
const [tokens, setTokens] = useState<Record<string, TokenInfo>>({})
|
|
14
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
15
|
+
|
|
16
|
+
const loadTokens = useCallback(async () => {
|
|
17
|
+
try {
|
|
18
|
+
setIsLoading(true)
|
|
19
|
+
|
|
20
|
+
// First, fetch token status from server
|
|
21
|
+
const response = await fetch('/api/oauth/tokens')
|
|
22
|
+
if (response.ok) {
|
|
23
|
+
const data = await response.json()
|
|
24
|
+
const serverTokens: Record<string, TokenInfo> = {}
|
|
25
|
+
|
|
26
|
+
if (data.tokens) {
|
|
27
|
+
data.tokens.forEach((token: TokenInfo) => {
|
|
28
|
+
serverTokens[token.platform] = token
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setTokens(serverTokens)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Also check local storage for any tokens
|
|
36
|
+
const localTokens = await clientTokenStorage.getAllTokens()
|
|
37
|
+
if (localTokens.length > 0) {
|
|
38
|
+
setTokens(prev => {
|
|
39
|
+
const updated = { ...prev }
|
|
40
|
+
localTokens.forEach(token => {
|
|
41
|
+
updated[token.platform] = {
|
|
42
|
+
platform: token.platform,
|
|
43
|
+
isConnected: true,
|
|
44
|
+
expiresAt: token.expiresAt
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
return updated
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to load tokens:', error)
|
|
52
|
+
} finally {
|
|
53
|
+
setIsLoading(false)
|
|
54
|
+
}
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
loadTokens()
|
|
59
|
+
}, [loadTokens])
|
|
60
|
+
|
|
61
|
+
const refreshTokenStatus = useCallback(async (platform: string) => {
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(`/api/oauth/tokens?platform=${platform}`)
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
const data = await response.json()
|
|
66
|
+
setTokens(prev => ({
|
|
67
|
+
...prev,
|
|
68
|
+
[platform]: {
|
|
69
|
+
platform,
|
|
70
|
+
isConnected: data.isConnected,
|
|
71
|
+
expiresAt: data.expiresAt
|
|
72
|
+
}
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
// Also update local storage
|
|
76
|
+
if (data.accessToken) {
|
|
77
|
+
await clientTokenStorage.storeToken({
|
|
78
|
+
id: platform,
|
|
79
|
+
platform,
|
|
80
|
+
accessToken: data.accessToken,
|
|
81
|
+
expiresAt: data.expiresAt,
|
|
82
|
+
lastRefreshed: new Date().toISOString()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`Failed to refresh token status for ${platform}:`, error)
|
|
88
|
+
}
|
|
89
|
+
}, [])
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
tokens,
|
|
93
|
+
isLoading,
|
|
94
|
+
refreshTokenStatus,
|
|
95
|
+
reloadAllTokens: loadTokens
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useState, useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface OpenRouterModel {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
description?: string
|
|
8
|
+
context_length?: number
|
|
9
|
+
pricing?: {
|
|
10
|
+
prompt?: string
|
|
11
|
+
completion?: string
|
|
12
|
+
}
|
|
13
|
+
architecture?: {
|
|
14
|
+
modality?: string
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface UseOpenRouterModelsResult {
|
|
19
|
+
models: OpenRouterModel[]
|
|
20
|
+
loading: boolean
|
|
21
|
+
error: string | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useOpenRouterModels(): UseOpenRouterModelsResult {
|
|
25
|
+
const [models, setModels] = useState<OpenRouterModel[]>([])
|
|
26
|
+
const [loading, setLoading] = useState(true)
|
|
27
|
+
const [error, setError] = useState<string | null>(null)
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
async function fetchModels() {
|
|
31
|
+
try {
|
|
32
|
+
setLoading(true)
|
|
33
|
+
setError(null)
|
|
34
|
+
|
|
35
|
+
const response = await fetch('https://openrouter.ai/api/v1/models', {
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Failed to fetch models: ${response.statusText}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const data = await response.json()
|
|
46
|
+
|
|
47
|
+
// Filter and sort models for better UX
|
|
48
|
+
const filteredModels = data.data
|
|
49
|
+
?.filter((model: any) =>
|
|
50
|
+
model.id &&
|
|
51
|
+
model.name &&
|
|
52
|
+
!model.id.includes(':free') // Exclude free models for cleaner list
|
|
53
|
+
)
|
|
54
|
+
?.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
|
55
|
+
?.slice(0, 50) || [] // Limit to 50 models for performance
|
|
56
|
+
|
|
57
|
+
setModels(filteredModels)
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('Error fetching OpenRouter models:', err)
|
|
60
|
+
setError(err instanceof Error ? err.message : 'Unknown error occurred')
|
|
61
|
+
|
|
62
|
+
// Fallback to default models
|
|
63
|
+
setModels([
|
|
64
|
+
{ id: 'openrouter/auto', name: 'Auto (Recommended)' },
|
|
65
|
+
{ id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet' },
|
|
66
|
+
{ id: 'openai/gpt-4o', name: 'GPT-4o' },
|
|
67
|
+
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini' },
|
|
68
|
+
{ id: 'google/gemini-pro', name: 'Gemini Pro' },
|
|
69
|
+
{ id: 'meta-llama/llama-3.1-8b-instruct', name: 'Llama 3.1 8B' },
|
|
70
|
+
])
|
|
71
|
+
} finally {
|
|
72
|
+
setLoading(false)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fetchModels()
|
|
77
|
+
}, [])
|
|
78
|
+
|
|
79
|
+
return { models, loading, error }
|
|
80
|
+
}
|
package/lib/auth0.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Auth0Client } from '@auth0/nextjs-auth0/server'
|
|
2
|
+
import { redirect } from 'next/navigation'
|
|
3
|
+
|
|
4
|
+
export const auth0 = new Auth0Client()
|
|
5
|
+
|
|
6
|
+
export async function authenticate() {
|
|
7
|
+
const session = await auth0.getSession()
|
|
8
|
+
if (! session?.user)
|
|
9
|
+
return redirect('/auth/login')
|
|
10
|
+
return session.user
|
|
11
|
+
}
|
package/lib/blob.ts
ADDED