mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: create Model
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import type { AcceptableValue } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit, useVModel } from "@vueuse/core"
|
||||
import { ChevronDownIcon } from "lucide-vue-next"
|
||||
import { cn } from '#/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps<{ modelValue?: AcceptableValue | AcceptableValue[], class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": AcceptableValue
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emit, {
|
||||
passive: true,
|
||||
defaultValue: "",
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group/native-select relative w-fit has-[select:disabled]:opacity-50"
|
||||
data-slot="native-select-wrapper"
|
||||
>
|
||||
<select
|
||||
v-bind="{ ...$attrs, ...delegatedProps }"
|
||||
v-model="modelValue"
|
||||
data-slot="native-select"
|
||||
:class="cn(
|
||||
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</select>
|
||||
<ChevronDownIcon
|
||||
class="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
|
||||
aria-hidden="true"
|
||||
data-slot="native-select-icon"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- @fallthroughAttributes true -->
|
||||
<!-- @strictTemplates true -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from '#/lib/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<optgroup data-slot="native-select-optgroup" :class="cn('bg-popover text-popover-foreground', props.class)">
|
||||
<slot />
|
||||
</optgroup>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- @fallthroughAttributes true -->
|
||||
<!-- @strictTemplates true -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from '#/lib/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<option data-slot="native-select-option" :class="cn('bg-popover text-popover-foreground', props.class)">
|
||||
<slot />
|
||||
</option>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as NativeSelect } from "./NativeSelect.vue"
|
||||
export { default as NativeSelectOptGroup } from "./NativeSelectOptGroup.vue"
|
||||
export { default as NativeSelectOption } from "./NativeSelectOption.vue"
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as ScrollArea } from "./ScrollArea.vue"
|
||||
export { default as ScrollBar } from "./ScrollBar.vue"
|
||||
export { default as ScrollArea } from './ScrollArea.vue'
|
||||
export { default as ScrollBar } from './ScrollBar.vue'
|
||||
|
||||
@@ -16,6 +16,7 @@ export * from './components/input/index'
|
||||
export * from './components/input-group/index'
|
||||
export * from './components/kbd/index'
|
||||
export * from './components/label/index'
|
||||
export * from './components/native-select/index'
|
||||
export * from './components/radio-group/index'
|
||||
export * from './components/scroll-area/index'
|
||||
export * from './components/select/index'
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@memoh/ui": "workspace:*",
|
||||
"@pinia/colada": "^0.21.1",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"axios": "^1.13.2",
|
||||
"dotenv": "^17.2.3",
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<section class="ml-auto">
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="default">
|
||||
Open Dialog
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-106.25">
|
||||
<form @submit="addModel">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加Model</DialogTitle>
|
||||
<DialogDescription class="mb-4">
|
||||
使用不用厂商的大模型
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="grid ">
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="baseUrl"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel class="mb-2">
|
||||
Base Url
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="请输入Base Url"
|
||||
v-bind="componentField"
|
||||
autocomplete="baseurl"
|
||||
/>
|
||||
</FormControl>
|
||||
<blockquote class="h-5">
|
||||
<FormMessage />
|
||||
</blockquote>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="apiKey"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel class="mb-2">
|
||||
Api Key
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="请输入Api Key"
|
||||
autocomplete="apiKey"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<blockquote class="h-5">
|
||||
<FormMessage />
|
||||
</blockquote>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="clientType"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel class="mb-2">
|
||||
Client Type
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="请输入Api Key"
|
||||
autocomplete="clientType"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<blockquote class="h-5">
|
||||
<FormMessage />
|
||||
</blockquote>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="name"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel class="mb-2">
|
||||
Name
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="请输入Api Key"
|
||||
autocomplete="name"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<blockquote class="h-5">
|
||||
<FormMessage />
|
||||
</blockquote>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="role"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel class="mb-2">
|
||||
Role
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select v-bind="componentField">
|
||||
<SelectTrigger
|
||||
class="w-full"
|
||||
>
|
||||
<SelectValue
|
||||
placeholder="Select a fruit"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
value="chat"
|
||||
select
|
||||
>
|
||||
Chat
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<blockquote class="h-5">
|
||||
<FormMessage />
|
||||
</blockquote>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
<DialogFooter class="mt-4">
|
||||
<DialogClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">
|
||||
添加Model
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Input,
|
||||
Button,
|
||||
FormField,
|
||||
FormControl,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@memoh/ui'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { ref } from 'vue'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import z from 'zod'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
baseUrl: z.string().min(1),
|
||||
apiKey: z.string().min(1),
|
||||
clientType: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
role: z.string().min(1),
|
||||
}))
|
||||
|
||||
const form = useForm({
|
||||
validationSchema:formSchema
|
||||
})
|
||||
const addModel=form.handleSubmit(async (modelInfo) => {
|
||||
try {
|
||||
await request({
|
||||
url: '/model',
|
||||
data: {
|
||||
...modelInfo
|
||||
}
|
||||
})
|
||||
open.value = false
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
const open=ref(false)
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts" generic="TData, TValue">
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
useVueTable,
|
||||
} from '@tanstack/vue-table'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@memoh/ui'
|
||||
|
||||
const props = defineProps<{
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}>()
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
console.log(table.getHeaderGroups())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow
|
||||
v-for="headerGroup in table.getHeaderGroups()"
|
||||
:key="headerGroup.id"
|
||||
>
|
||||
<TableHead
|
||||
v-for="header in headerGroup.headers"
|
||||
:key="header.id"
|
||||
>
|
||||
<FlexRender
|
||||
v-if="!header.isPlaceholder"
|
||||
:render="header.column.columnDef.header"
|
||||
:props="header.getContext()"
|
||||
/>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow
|
||||
v-for="row in table.getRowModel().rows"
|
||||
:key="row.id"
|
||||
:data-state="row.getIsSelected() ? 'selected' : undefined"
|
||||
>
|
||||
<TableCell
|
||||
v-for="cell in row.getVisibleCells()"
|
||||
:key="cell.id"
|
||||
>
|
||||
<FlexRender
|
||||
:render="cell.column.columnDef.cell"
|
||||
:props="cell.getContext()"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
:colspan="columns.length"
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -107,10 +107,8 @@ import {
|
||||
CardContent,
|
||||
CardFooter,
|
||||
Input,
|
||||
|
||||
Button,
|
||||
FormControl,
|
||||
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
// import type { Payment } from '@/components/columns'
|
||||
import { ref, h } from 'vue'
|
||||
import CreateModel from '@/components/CreateModel/index.vue'
|
||||
|
||||
import DataTable from '@/components/DataTable/index.vue'
|
||||
|
||||
const modelData=ref([])
|
||||
|
||||
async function getData() {
|
||||
|
||||
return [
|
||||
{
|
||||
id: '728ed52f',
|
||||
amount: 100,
|
||||
status: 'pending',
|
||||
email: 'm@example.com',
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h1>模型管理</h1>
|
||||
</section>
|
||||
<div class="w-full py-10 mx-auto">
|
||||
<div class="flex mb-4">
|
||||
<CreateModel />
|
||||
</div>
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:data="modelData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -6,13 +6,8 @@ export default (function () {
|
||||
const axiosInstance = axios.create({
|
||||
baseURL:'http://localhost:7002/'
|
||||
})
|
||||
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
return config
|
||||
}, (error) => Promise.reject(error))
|
||||
|
||||
axiosInstance.interceptors.response.use((response) => {
|
||||
|
||||
return response
|
||||
}, (error) => {
|
||||
if (error?.status === 401) {
|
||||
@@ -23,6 +18,13 @@ export default (function () {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
return (params: AxiosRequestConfig,isToken=true) => {
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
if (isToken) {
|
||||
const token = localStorage.getItem('token')
|
||||
config.headers['Authorization'] =`Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
}, (error) => Promise.reject(error))
|
||||
|
||||
return axiosInstance(params)
|
||||
}
|
||||
|
||||
Generated
+3
@@ -246,6 +246,9 @@ importers:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
||||
'@tanstack/vue-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(vue@3.5.26(typescript@5.9.3))
|
||||
'@vee-validate/zod':
|
||||
specifier: ^4.15.1
|
||||
version: 4.15.1(vue@3.5.26(typescript@5.9.3))(zod@4.3.5)
|
||||
|
||||
Reference in New Issue
Block a user