Merge branch 'main' into refactor/channel-gateway

This commit is contained in:
Acbox
2026-02-07 17:29:14 +08:00
14 changed files with 407 additions and 379 deletions
+7 -1
View File
@@ -20,10 +20,16 @@ export const createAgent = ({
brave,
language = 'Same as the user input',
allowedActions = allActions,
identity,
channels = [],
mcpConnections = [],
currentChannel = 'Unknown Channel',
identity = {
botId: '',
sessionId: '',
containerId: '',
contactId: '',
contactName: '',
},
}: AgentParams, fetch: AuthFetcher) => {
const model = createModel(modelConfig)
+1 -1
View File
@@ -41,10 +41,10 @@ export interface AgentParams {
activeContextTime?: number
allowedActions?: AgentAction[]
brave?: BraveConfig
identity: IdentityContext
channels?: string[]
currentChannel?: string
mcpConnections?: MCPConnection[]
identity?: IdentityContext
}
export interface AgentInput {
+1 -1
View File
@@ -35,7 +35,7 @@ host = "localhost"
port = 5432
user = "postgres"
password = "1234"
database = "postgres"
database = "demo"
sslmode = "disable"
+9
View File
@@ -65,6 +65,15 @@ description = "Install CLI"
depends = ["//:pnpm-install"]
run = "cd packages/cli && npm install -g"
[tasks.build-cli]
description = "Build Go CLI binary and install to local bin"
run = """
mkdir -p ~/.local/bin
go build -trimpath -ldflags "-s -w" -o ~/.local/bin/container-cli ./cmd/cli
chmod +x ~/.local/bin/container-cli
echo " CLI binary installed to ~/.local/bin/container-cli"
"""
[tasks.mcp-image-up]
description = "Build MCP container image"
run = "scripts/mcp-image-up.sh"
@@ -0,0 +1,57 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { computed, onMounted, ref } from 'vue'
const props = withDefaults(
defineProps<{
words: string;
filter?: boolean;
duration?: number;
delay?: number;
class?: HTMLAttributes['class'];
}>(),
{ duration: 0.7, delay: 0, filter: true },
)
const scope = ref(null)
const wordsArray = computed(() => props.words.split(''))
const spanStyle = computed(() => ({
opacity: 0,
filter: props.filter ? 'blur(10px)' : 'none',
transition: `opacity ${props.duration}s, filter ${props.duration}s`,
}))
onMounted(() => {
if (scope.value) {
const spans = (scope.value as HTMLElement).querySelectorAll('span')
setTimeout(() => {
spans.forEach((span: HTMLElement, index: number) => {
setTimeout(() => {
span.style.opacity = '1'
span.style.filter = props.filter ? 'blur(0px)' : 'none'
}, index * 200)
})
}, props.delay)
}
})
</script>
<template>
<div
class="leading-snug tracking-wide"
:class="[props.class]"
>
<div ref="scope">
<span
v-for="(word, idx) in wordsArray"
:key="word + idx"
class="inline-block"
:style="spanStyle"
>
{{ word }}
</span>
</div>
</div>
</template>
@@ -0,0 +1 @@
export { default as TextGenerateEffect } from "./TextGenerateEffect.vue";
+1 -40
View File
@@ -6,57 +6,18 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
Toaster,
Separator
} from '@memoh/ui'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiTranslate, mdiBrightness6 } from '@mdi/js'
import { useColorMode } from '@vueuse/core'
// @ts-ignore
import 'vue-sonner/style.css'
const mode = useColorMode()
const modeToggleMap: Record<'dark' | 'light', 'dark' | 'light'> = {
dark: 'light',
light: 'dark'
}
const toggleMode = () => {
if (mode.value !== 'auto') {
mode.value = modeToggleMap[mode.value]
}
}
</script>
<template>
<section class="[&_input]:shadow-none!">
<div
class="fixed top-0 flex right-8 z-9999 [&:is(:has([data-state=open]))_.translate-icon]:opacity-100 align h-16 items-center"
>
<DropdownMenu>
<DropdownMenuTrigger class="ml-auto mr-4 cursor-pointer">
<svg-icon
type="mdi"
:path="mdiTranslate"
class="translate-icon opacity-30 hover:opacity-100"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="$i18n.locale = 'zh'">
中文
</DropdownMenuItem>
<DropdownMenuItem @click="$i18n.locale = 'en'">
English
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<svg-icon
type="mdi"
:path="mdiBrightness6"
class="translate-icon opacity-30 hover:opacity-100 cursor-pointer"
@click="toggleMode"
/>
</div>
<RouterView />
<Toaster position="top-center" />
</section>
+6 -30
View File
@@ -10,11 +10,6 @@
class="m-auto"
alt="logo.png"
>
<h4
class="scroll-m-20 text-xl font-semibold tracking-tight text-center text-muted-foreground title-container"
>
Memoh
</h4>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
@@ -53,19 +48,6 @@
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem class="flex justify-center exist-btn">
<Button
class="flex-[0.7] mb-10"
@click="exit"
>
{{ $t("login.exit") }}
</Button>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarRail />
</Sidebar>
</aside>
</template>
@@ -79,26 +61,25 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
CollapsibleTrigger,
Collapsible,
Button,
Toggle
} from '@memoh/ui'
import { computed } from 'vue'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiRobot, mdiChatOutline, mdiCogBox } from '@mdi/js'
import { useRouter } from 'vue-router'
import { useRouter,useRoute } from 'vue-router'
import { useUserStore } from '@/store/User.ts'
import i18n from '@/i18n'
import { ref } from 'vue'
const router = useRouter()
const route=useRoute()
const { t } = i18n.global
const curSlider = ref('chat')
const curSlider = ref()
const curSelectSlide = (cur: string) => computed(() => {
return curSlider.value === cur
return curSlider.value === cur||new RegExp(`^/main/${cur}$`).test(route.path)
})
const sidebarInfo = computed(() => [
{
@@ -131,9 +112,4 @@ const sidebarInfo = computed(() => [
// }
])
const { exitLogin } = useUserStore()
const exit = () => {
exitLogin()
router.replace({ name: 'Login' })
}
</script>
+11 -12
View File
@@ -8,16 +8,15 @@
<ChatList />
</ScrollArea>
</section>
<section class="flex-none relative mt-60">
<section class="mb-8 logo-title">
<img
src="../../../public/logo.png"
width="100"
class="m-auto"
alt="logo.png"
<section class="flex-none relative m-auto">
<section class="mb-20 logo-title">
<h4
class="scroll-m-20 text-3xl font-semibold tracking-tight text-center title-container"
style="font-family: 'Source Han Serif CN', 'Noto Serif SC', 'STSong', 'SimSun', serif;"
>
<h4 class="scroll-m-20 text-xl font-semibold tracking-tight text-center text-muted-foreground title-container">
Memoh
<TextGenerateEffect
words="您好!有什么能帮助您的?"
/>
</h4>
</section>
@@ -54,15 +53,15 @@
import {
ScrollArea,
Textarea,
Button
Button,
TextGenerateEffect
} from '@memoh/ui'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiSendOutline } from '@mdi/js'
import ChatList from '@/components/ChatList/index.vue'
import { provide, ref } from 'vue'
import { useChatList } from '@/store/ChatList'
import {storeToRefs} from 'pinia'
import { storeToRefs } from 'pinia'
const chatSay = ref('')
const curInputSay = ref('')
+31 -28
View File
@@ -2,38 +2,38 @@
<section class="w-screen h-screen flex *:m-auto bg-linear-to-t from-[#BFA4A0] to-[#7784AC] ">
<section class="w-full max-w-sm flex flex-col gap-10 ">
<section>
<img
src="../../../public/logo.png"
width="100"
alt="logo.png"
class="m-auto"
<h3
class="scroll-m-20 text-3xl tracking-wide font-semibold text-white text-center"
style="font-family: 'Source Han Serif CN', 'Noto Serif SC', 'STSong', 'SimSun', serif;"
>
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight text-white text-center">
Memoh
欢迎使用
</h3>
</section>
<form @submit="login">
<form
@submit="login"
>
<Card class="py-14">
<CardContent class="flex flex-col [&_input]:py-5">
<CardContent class="flex flex-col [&_input]:py-5 gap-4">
<FormField
v-slot="{ componentField }"
name="username"
>
<FormItem>
<FormLabel class="mb-2">
<Label
class="mb-2"
for="username"
>
{{ $t("login.username") }}
</FormLabel>
</Label>
<FormControl>
<Input
v-bind="componentField"
id="username"
type="text"
:placeholder="$t('prompt.enter', { msg: $t(`login.username`).toLocaleLowerCase() })"
v-bind="componentField"
autocomplete="username"
autocomplete="new-password"
/>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<FormField
@@ -41,20 +41,21 @@
name="password"
>
<FormItem>
<FormLabel class="mb-2">
<Label
class="mb-2"
for="password"
>
{{ $t('login.password') }}
</FormLabel>
<FormControl>
</Label>
<FormControl>
<Input
type="password"
id="password"
type="password"
:placeholder="$t('prompt.enter', { msg: $t(`login.password`).toLocaleLowerCase() })"
autocomplete="password"
autocomplete="new-password"
v-bind="componentField"
/>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<div class="flex">
@@ -66,6 +67,7 @@
</a>
</div>
</CardContent>
<CardFooter class="flex flex-col gap-4">
<Button
class="w-full"
@@ -98,8 +100,7 @@ import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Label,
Spinner
} from '@memoh/ui'
import { useRouter } from 'vue-router'
@@ -108,15 +109,17 @@ import { useForm } from 'vee-validate'
import * as z from 'zod'
import request from '@/utils/request'
import { useUserStore } from '@/store/User.ts'
import { ref } from 'vue'
import { ref } from 'vue'
import { toast } from 'vue-sonner'
const router = useRouter()
const formSchema = toTypedSchema(z.object({
username: z.string().min(1),
password: z.string().min(1),
}))
const form = useForm({
validationSchema: formSchema,
validationSchema: formSchema
})
const { login: LoginHandle } = useUserStore()
+5 -2
View File
@@ -52,10 +52,13 @@ watch(filterProvider, () => {
key: ['provider']
})
}, {
immediate: true
immediate:true
})
const curProvider = ref<Partial<ProviderInfo> & { id: string }>()
provide('curProvider', curProvider)
const selectProvider = (value: string) => computed(() => {
return curProvider.value?.name === value
})
@@ -198,4 +201,4 @@ const openStatus = reactive({
</SidebarProvider>
</div>
</div>
</template>
</template>
@@ -128,12 +128,10 @@
>
<ItemContent>
<ItemTitle>
{{ modelData.name }}
{{ modelData.name }}
</ItemTitle>
<ItemDescription class="gap-2 flex flex-wrap items-center mt-3 ">
<Badge
variant="outline"
>
<Badge variant="outline">
{{ modelData.type }}
</Badge>
</ItemDescription>
@@ -147,7 +145,7 @@
as: value as string === 'empty' ? '' : value as string,
model_id: modelData.model_id
})
}"
>
<SelectTrigger class="w-full">
@@ -244,7 +242,7 @@
<script setup lang="ts">
import {
Switch, Separator, Spinner, Input, Button,
Separator, Spinner, Input, Button,
FormControl,
FormField,
FormItem,
@@ -296,7 +294,7 @@ provide('openModel', toRef(openModel, 'state'))
provide('openModelTitle', toRef(openModel, 'title'))
provide('openModelState', toRef(openModel, 'curState'))
const deleteEnableAd = (value:ModelInfo) => {
const deleteEnableAd = (value: ModelInfo) => {
const copyModelData = { ...value }
if ('enable_as' in copyModelData) {
delete copyModelData['enable_as']
+227 -213
View File
@@ -1,177 +1,186 @@
<template>
<section>
<section class="max-w-187 m-auto">
<Card>
<form @submit="changeSetting">
<CardHeader>
<CardTitle class="text-2xl font-semibold tracking-tight">
Settings
</CardTitle>
<CardDescription>
Model Settings
</CardDescription>
</CardHeader>
<CardContent class="mt-4">
<FormField
v-slot="{ componentField }"
name="defaultChatModel"
>
<FormItem>
<FormLabel class="mb-2">
Chat Model
</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue :placeholder="$t('prompt.select',{msg:'Client Type'})" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem
v-for="(modelItem, index) in modelType.chat"
:key="modelItem.id"
:value="(modelItem.model.apiKey + index)"
>
{{ modelItem.model.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="defaultEmbeddingModel"
>
<FormItem>
<FormLabel class="mb-2">
Embedding Model
</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue :placeholder="$t('prompt.select',{msg:'Embedding Type'})" />
</SelectTrigger>
<SelectContent v-if="modelType.embedding.length > 0">
<SelectGroup>
<SelectItem
v-for="(modelItem, index) in modelType.embedding"
:key="modelItem.id"
:value="(modelItem.model.apiKey + index)"
>
{{ modelItem.model.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<form
@submit="changeSetting"
>
<section>
<section class="flex flex-col">
<h6 class=" mt-2 mb-2 align-middle">
<svg-icon
type="mdi"
:path="mdiRobotOutline"
class="inline mr-2 align-[-5px]"
/>
</h6>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</formcontrol>
</FormItem>
</FormField>
<Separator />
</section>
<section class="flex flex-col [&_:has(label)]:py-2">
<FormField
v-slot="{ componentField }"
name="defaultSummaryModel"
name="chat_model_id"
>
<FormItem>
<FormLabel class="mb-2">
<!-- defaultSummaryModel -->
Summary Model
</FormLabel>
<Label class="mb-2">
Chat Model ID
</Label>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue :placeholder="$t('prompt.select', { msg: 'Summary Type' })" />
</SelectTrigger>
<SelectContent v-if="modelType.embedding.length > 0">
<SelectGroup>
<SelectItem
v-for="(modelItem, index) in modelType.summary"
:key="modelItem.id"
:value="(modelItem.model.apiKey + index)"
>
{{ modelItem.model.name }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Input v-bind="componentField" />
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="embedding_model_id"
>
<FormItem>
<Label class="mb-2">
Embedding Model ID
</Label>
<FormControl>
<Input v-bind="componentField" />
</FormControl>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="memory_model_id"
>
<FormItem>
<Label class="mb-2">
Memory Model ID
</Label>
<FormControl>
<Input v-bind="componentField" />
</FormControl>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="max_context_load_time"
>
<FormItem class="**:[input]:max-w-40!">
<Label class="mb-2">
Timeout
</Label>
<FormControl>
<Input v-bind="componentField" />
</FormControl>
</FormItem>
</FormField>
</section>
</section>
<section class="mt-4">
<section class="flex flex-col">
<h6 class=" mt-2 mb-2 align-middle">
<svg-icon
type="mdi"
:path="mdiCog"
class="inline mr-2 align-[-5px]"
/>
</h6>
<Separator />
</section>
<section class="flex flex-col [&_:has(label)]:py-2">
<FormField
v-slot="{ componentField }"
name="language"
>
<FormItem>
<FormLabel class="mb-2">
Language
</FormLabel>
<FormItem class="**:[button]:min-w-40! flex justify-between items-ceneter">
<Label class="mb-2">
语言
</Label>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue
:placeholder="$t('prompt.select', { msg: 'Language' })"
/>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="ch">
<SelectItem
value="zh"
@click="$i18n.locale = 'zh'"
>
中文
</SelectItem>
<SelectItem value="en">
<SelectItem
value="en"
@click="$i18n.locale = 'en'"
>
English
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
<FormField
v-slot="{ componentField }"
name="maxContextLoadTime"
>
<FormItem>
<FormLabel class="mb-2">
<!-- defaultSummaryModel -->
Timeout
</FormLabel>
<FormControl>
<Input
:placeholder="$t('prompt.enter',{msg:'Timeout'})"
v-bind="componentField"
/>
</FormControl>
<blockquote class="h-5">
<FormMessage />
</blockquote>
</FormItem>
</FormField>
</CardContent>
<CardFooter class="flex">
<Button
class="ml-auto"
type="submit"
:disabled="diabeld"
>
Change
</Button>
</CardFooter>
</form>
</Card>
<Separator />
<section class="flex justify-between items-baseline">
<Label class="mb-4">
主题
</Label>
<Switch
:model-value="curDark()"
@update:model-value="() => {
toggleMode()
}
"
/>
</section>
</section>
</section>
<section class="mt-4 flex gap-3">
<Popover>
<template #default="{ close }">
<PopoverTrigger as-child>
<Button
class="ml-auto"
variant="outline"
>
{{ $t("login.exit") }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-80">
<div class="grid gap-4">
<p class="leading-7 not-first:mt-6 ">
确认退出登录?
</p>
<section class="flex gap-4">
<Button
variant="outline"
class="ml-auto"
@click="() => { close() }"
>
取消
</Button>
<Button @click="() => { exit(); close() }">
确定
</Button>
</section>
</div>
</PopoverContent>
</template>
</Popover>
<Button
:disabled="settingLoading"
type="submit"
>
<Spinner v-if="settingLoading" />
更改
</Button>
</section>
</form>
</section>
</section>
</template>
@@ -179,116 +188,121 @@
<script setup lang="ts">
import { useMutation, useQuery, useQueryCache } from '@pinia/colada'
import request from '@/utils/request'
import { watch, reactive, computed } from 'vue'
import { watch } from 'vue'
import { toTypedSchema } from '@vee-validate/zod'
import z from 'zod'
import { useForm, useFormValues } from 'vee-validate'
import { useForm } from 'vee-validate'
import {
Input,
Card,
CardDescription,
CardHeader,
CardTitle,
CardContent,
FormField,
FormItem,
FormLabel,
FormItem,
FormControl,
Button,
FormMessage,
Select,
SelectTrigger,
SelectContent,
SelectValue,
SelectGroup,
SelectItem,
CardFooter
Label,
Separator,
Switch,
Spinner,
Popover,
PopoverContent,
PopoverTrigger,
} from '@memoh/ui'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiRobotOutline, mdiCog } from '@mdi/js'
import { toast } from 'vue-sonner'
import i18n from '@/i18n'
import { useColorMode } from '@vueuse/core'
import { useRouter } from 'vue-router'
import { useUserStore } from '../../store/User'
const router=useRouter()
type ModelList = {
id: ModelTable['id'],
model: Omit<ModelTable, 'id' | 'defaultChatModel' | 'defaultEmbeddingModel' | 'defaultSummaryModel'>
};
const { exitLogin } = useUserStore()
const exit = () => {
exitLogin()
router.replace({ name: 'Login' })
}
const mode = useColorMode()
const modeToggleMap: Record<'dark' | 'light', 'dark' | 'light'> = {
dark: 'light',
light: 'dark'
}
const toggleMode = () => {
if (mode.value !== 'auto') {
mode.value = modeToggleMap[mode.value]
}
}
const curDark = () => {
return mode.value==='dark'?true:false
}
const modelType = reactive<{
chat: ModelList[],
embedding: ModelList[],
summary: ModelList[]
}>({
chat: [],
embedding: [],
summary: []
})
const { data: settingData } = useQuery({
key: ['Setting'],
query: async () => {
const modelData = await request({
url: '/model/',
method: 'get'
})
for (const modelItems of modelData.data.items) {
let type = modelItems.model.type as keyof typeof modelType
modelType[type].push(modelItems)
query: async () => request({
url: '/settings',
method: 'get'
}).then(fetchSetting => {
// if(f)
if (fetchSetting?.data?.language&&!i18n.global.availableLocales.includes(fetchSetting?.data?.language)) {
fetchSetting.data.language='zh'
}
return await request({
url: '/settings/',
method: 'get'
})
}
return fetchSetting?.data
})
})
const formSchema = toTypedSchema(z.object({
defaultChatModel: z.any(),
defaultEmbeddingModel: z.any(),
defaultSummaryModel: z.any(),
maxContextLoadTime: z.coerce.number().min(1500),
language: z.literal(['ch', 'en'])
chat_model_id: z.coerce.string(),
embedding_model_id: z.coerce.string(),
language: z.coerce.string(),
max_context_load_time: z.coerce.number().min(1000),
memory_model_id: z.coerce.string()
}))
const form = useForm({
validationSchema: formSchema
})
const currentSetting = useFormValues()
const diabeld = computed(() => {
return Object.keys(currentSetting.value).every((property) => {
const curKey = currentSetting.value[property]
const cacheKey = settingData.value?.data?.data?.[property]
if (curKey === cacheKey || Number(curKey) === Number(cacheKey)) {
return true
}
})
})
watch(settingData, () => {
form.setValues({
...(settingData.value?.data.data ?? {})
})
}, {
immediate: true
watch(settingData, () => {
form.setValues(settingData.value)
form.values=settingData.value
})
const cacheQuery=useQueryCache()
const { mutate: fetchSetting } = useMutation({
mutation: (data:typeof currentSetting.value) => request({
url: '/settings/',
data
const cacheQuery = useQueryCache()
const { mutate: fetchSetting,isLoading:settingLoading,status } = useMutation({
mutation: (data:typeof form.values) => request({
url: '/settings',
data,
method:'POST'
}),
onSettled: () => {
cacheQuery.invalidateQueries({
key:['Setting']
key: ['Setting']
})
}
})
watch(status, () => {
if (status.value === 'error') {
toast.error('保存失败')
}
})
const changeSetting = form.handleSubmit(async (value) => {
try {
await fetchSetting(value)
try {
await fetchSetting(value)
} catch {
return
}
return
}
})
</script>
+45 -44
View File
@@ -21,71 +21,72 @@
:root {
--radius: 0.65rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--background: oklch(98.7% 0.3% 290deg);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.546 0.245 262.881);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.274 0.006 286.033);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.21 0.006 285.885);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
--sidebar-ring: oklch(0.556 0 0);
}
@theme inline {