mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: basic api server
This commit is contained in:
@@ -4,10 +4,24 @@
|
||||
"scripts": {
|
||||
"dev": "bun run --env-file=../../.env --watch src/index.ts",
|
||||
"build": "bun build src/index.ts --outfile dist/index.js --target bun --minify",
|
||||
"start": "bun run dist/index.js"
|
||||
"start": "bun run dist/index.js",
|
||||
"test": "vitest"
|
||||
},
|
||||
"exports": {
|
||||
"./client": "./src/client.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"elysia": "latest"
|
||||
"@elysiajs/cors": "^1.4.1",
|
||||
"@elysiajs/cron": "^1.4.1",
|
||||
"@elysiajs/eden": "^1.4.6",
|
||||
"@memohome/agent": "workspace:*",
|
||||
"@memohome/db": "workspace:*",
|
||||
"@memohome/memory": "workspace:*",
|
||||
"@memohome/shared": "workspace:*",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"elysia": "latest",
|
||||
"node-cron": "^4.2.1",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { app } from './index'
|
||||
import { treaty } from '@elysiajs/eden'
|
||||
|
||||
export type ApiClient = typeof app
|
||||
|
||||
export const createClient = (
|
||||
baseUrl: string = process.env.API_BASE_URL ?? 'http://localhost:7002'
|
||||
) => {
|
||||
return treaty<ApiClient>(baseUrl)
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
import { Elysia } from 'elysia'
|
||||
import { corsMiddleware } from './middlewares'
|
||||
import { agentModule, modelModule, settingsModule } from './modules'
|
||||
import { memoryModule } from './modules/memory'
|
||||
|
||||
const port = process.env.API_SERVER_PORT || 7002
|
||||
|
||||
const app = new Elysia().get('/', () => 'Hello Elysia').listen(port)
|
||||
export const app = new Elysia()
|
||||
.use(corsMiddleware)
|
||||
.use(agentModule)
|
||||
.use(memoryModule)
|
||||
.use(modelModule)
|
||||
.use(settingsModule)
|
||||
.listen(port)
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import cors from '@elysiajs/cors'
|
||||
|
||||
export const corsMiddleware = cors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
exposeHeaders: ['Content-Type', 'Authorization'],
|
||||
credentials: true,
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from './cors'
|
||||
@@ -0,0 +1,5 @@
|
||||
import Elysia from 'elysia'
|
||||
|
||||
export const agentModule = new Elysia({
|
||||
prefix: '/agent',
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './agent'
|
||||
export * from './model'
|
||||
export * from './settings'
|
||||
@@ -0,0 +1,40 @@
|
||||
import Elysia from 'elysia'
|
||||
import { messageModule } from './message'
|
||||
import { AddMemoryModel, SearchMemoryModel } from './model'
|
||||
import { addMemory, searchMemory } from './service'
|
||||
import { MemoryUnit } from '@memohome/memory'
|
||||
|
||||
export const memoryModule = new Elysia({
|
||||
prefix: '/memory',
|
||||
})
|
||||
.use(messageModule)
|
||||
// Add memory
|
||||
.post('/', async ({ body }) => {
|
||||
try {
|
||||
const result = await addMemory(body as unknown as MemoryUnit)
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to add memory',
|
||||
}
|
||||
}
|
||||
}, AddMemoryModel)
|
||||
// Search memory
|
||||
.get('/search', async ({ query }) => {
|
||||
try {
|
||||
const results = await searchMemory(query.query, query.userId)
|
||||
return {
|
||||
success: true,
|
||||
data: results,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to search memory',
|
||||
}
|
||||
}
|
||||
}, SearchMemoryModel)
|
||||
@@ -0,0 +1,22 @@
|
||||
import Elysia from 'elysia'
|
||||
import { GetMemoryMessageFilterModel, GetMemoryMessageModel } from './model'
|
||||
import { getMemoryMessages, getMemoryMessagesFilter } from './service'
|
||||
|
||||
export const messageModule = new Elysia({
|
||||
prefix: '/message',
|
||||
})
|
||||
.get('/', async ({ query }) => {
|
||||
const units = await getMemoryMessages(query)
|
||||
return {
|
||||
success: true,
|
||||
units,
|
||||
}
|
||||
}, GetMemoryMessageModel)
|
||||
.get('/filter', async ({ query }) => {
|
||||
const units = await getMemoryMessagesFilter(query)
|
||||
|
||||
return {
|
||||
units,
|
||||
success: true,
|
||||
}
|
||||
}, GetMemoryMessageFilterModel)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const GetMemoryMessageModel = {
|
||||
query: z.object({
|
||||
limit: z.string().transform(Number).default(10),
|
||||
page: z.string().transform(Number).default(1),
|
||||
userId: z.string(),
|
||||
}),
|
||||
}
|
||||
|
||||
export const GetMemoryMessageFilterModel = {
|
||||
query: z.object({
|
||||
from: z.date(),
|
||||
to: z.date(),
|
||||
userId: z.string(),
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { db } from '@memohome/db'
|
||||
import { history } from '@memohome/db/schema'
|
||||
import { eq, desc, and, gte, lte, asc } from 'drizzle-orm'
|
||||
|
||||
export const getMemoryMessages = async (
|
||||
query: {
|
||||
limit: number
|
||||
page: number
|
||||
userId: string
|
||||
}
|
||||
) => {
|
||||
const { limit, page, userId } = query
|
||||
const results = await db
|
||||
.select()
|
||||
.from(history)
|
||||
.where(eq(history.user, userId))
|
||||
.orderBy(desc(history.timestamp))
|
||||
.limit(limit)
|
||||
.offset((page - 1) * limit)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export const getMemoryMessagesFilter = async (
|
||||
query: {
|
||||
from: Date
|
||||
to: Date
|
||||
userId: string
|
||||
}
|
||||
) => {
|
||||
const { from, to, userId } = query
|
||||
const results = await db
|
||||
.select()
|
||||
.from(history)
|
||||
.where(and(
|
||||
gte(history.timestamp, from),
|
||||
lte(history.timestamp, to),
|
||||
eq(history.user, userId),
|
||||
))
|
||||
.orderBy(asc(history.timestamp))
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
// MemoryUnit schema
|
||||
const MemoryUnitSchema = z.object({
|
||||
messages: z.array(z.object()),
|
||||
timestamp: z.coerce.date(),
|
||||
user: z.string(),
|
||||
})
|
||||
|
||||
export const AddMemoryModel = {
|
||||
body: MemoryUnitSchema,
|
||||
}
|
||||
|
||||
export const SearchMemoryModel = {
|
||||
query: z.object({
|
||||
query: z.string().min(1, 'Search query is required'),
|
||||
userId: z.string().min(1, 'User ID is required'),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createMemory, MemoryUnit } from '@memohome/memory'
|
||||
import { getEmbeddingModel, getSummaryModel } from '@/modules/model/service'
|
||||
import { ChatModel, EmbeddingModel } from '@memohome/shared'
|
||||
|
||||
export const addMemory = async (memoryUnit: MemoryUnit) => {
|
||||
const [embeddingModel, summaryModel] = await Promise.all([
|
||||
getEmbeddingModel(memoryUnit.user),
|
||||
getSummaryModel(memoryUnit.user),
|
||||
])
|
||||
if (!embeddingModel || !summaryModel) {
|
||||
throw new Error('Embedding or summary model not found')
|
||||
}
|
||||
const { addMemory } = createMemory({
|
||||
summaryModel: summaryModel.model as ChatModel,
|
||||
embeddingModel: embeddingModel.model as EmbeddingModel,
|
||||
})
|
||||
await addMemory(memoryUnit)
|
||||
return memoryUnit
|
||||
}
|
||||
|
||||
export const searchMemory = async (query: string, userId: string) => {
|
||||
const [embeddingModel, summaryModel] = await Promise.all([
|
||||
getEmbeddingModel(userId),
|
||||
getSummaryModel(userId),
|
||||
])
|
||||
if (!embeddingModel || !summaryModel) {
|
||||
throw new Error('Embedding or summary model not found')
|
||||
}
|
||||
const { searchMemory } = createMemory({
|
||||
summaryModel: summaryModel.model as ChatModel,
|
||||
embeddingModel: embeddingModel.model as EmbeddingModel,
|
||||
})
|
||||
const results = await searchMemory(query, userId)
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import Elysia from 'elysia'
|
||||
import {
|
||||
CreateModelModel,
|
||||
UpdateModelModel,
|
||||
GetModelByIdModel,
|
||||
DeleteModelModel,
|
||||
GetDefaultModelModel,
|
||||
} from './model'
|
||||
import {
|
||||
getModels,
|
||||
getModelById,
|
||||
createModel,
|
||||
updateModel,
|
||||
deleteModel,
|
||||
getChatModel,
|
||||
getSummaryModel,
|
||||
getEmbeddingModel,
|
||||
} from './service'
|
||||
import { Model } from '@memohome/shared'
|
||||
|
||||
export const modelModule = new Elysia({
|
||||
prefix: '/model',
|
||||
})
|
||||
// Get all models
|
||||
.get('/', async () => {
|
||||
try {
|
||||
const models = await getModels()
|
||||
return {
|
||||
success: true,
|
||||
data: models,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch models',
|
||||
}
|
||||
}
|
||||
})
|
||||
// Get model by ID
|
||||
.get('/:id', async ({ params }) => {
|
||||
try {
|
||||
const { id } = params
|
||||
const model = await getModelById(id)
|
||||
if (!model) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Model not found',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: model,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch model',
|
||||
}
|
||||
}
|
||||
}, GetModelByIdModel)
|
||||
// Create new model
|
||||
.post('/', async ({ body }) => {
|
||||
try {
|
||||
const newModel = await createModel(body as Model)
|
||||
return {
|
||||
success: true,
|
||||
data: newModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create model',
|
||||
}
|
||||
}
|
||||
}, CreateModelModel)
|
||||
// Update model
|
||||
.put('/:id', async ({ params, body }) => {
|
||||
try {
|
||||
const { id } = params
|
||||
const updatedModel = await updateModel(id, body as Model)
|
||||
if (!updatedModel) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Model not found',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: updatedModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update model',
|
||||
}
|
||||
}
|
||||
}, UpdateModelModel)
|
||||
// Delete model
|
||||
.delete('/:id', async ({ params }) => {
|
||||
try {
|
||||
const { id } = params
|
||||
const deletedModel = await deleteModel(id)
|
||||
if (!deletedModel) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Model not found',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: deletedModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete model',
|
||||
}
|
||||
}
|
||||
}, DeleteModelModel)
|
||||
// Get default chat model
|
||||
.get('/chat/default', async ({ query }) => {
|
||||
try {
|
||||
const { userId } = query
|
||||
const chatModel = await getChatModel(userId)
|
||||
if (!chatModel) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Default chat model not found or not set',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: chatModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch default chat model',
|
||||
}
|
||||
}
|
||||
}, GetDefaultModelModel)
|
||||
// Get default summary model
|
||||
.get('/summary/default', async ({ query }) => {
|
||||
try {
|
||||
const { userId } = query
|
||||
const summaryModel = await getSummaryModel(userId)
|
||||
if (!summaryModel) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Default summary model not found or not set',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: summaryModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch default summary model',
|
||||
}
|
||||
}
|
||||
}, GetDefaultModelModel)
|
||||
// Get default embedding model
|
||||
.get('/embedding/default', async ({ query }) => {
|
||||
try {
|
||||
const { userId } = query
|
||||
const embeddingModel = await getEmbeddingModel(userId)
|
||||
if (!embeddingModel) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Default embedding model not found or not set',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: embeddingModel,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch default embedding model',
|
||||
}
|
||||
}
|
||||
}, GetDefaultModelModel)
|
||||
@@ -0,0 +1,55 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const BaseModelSchema = z.object({
|
||||
modelId: z.string().min(1, 'Model ID is required'),
|
||||
baseUrl: z.string(),
|
||||
apiKey: z.string().min(1, 'API key is required'),
|
||||
clientType: z.string(),
|
||||
name: z.string().optional(),
|
||||
})
|
||||
|
||||
// Chat model schema (type is optional and defaults to 'chat')
|
||||
const ChatModelSchema = BaseModelSchema.extend({
|
||||
type: z.enum(['chat']).optional().default('chat'),
|
||||
})
|
||||
|
||||
// Embedding model schema (type must be 'embedding' and dimensions is required)
|
||||
const EmbeddingModelSchema = BaseModelSchema.extend({
|
||||
type: z.literal('embedding'),
|
||||
dimensions: z.number().int().positive('Dimensions must be a positive integer'),
|
||||
})
|
||||
|
||||
// Union of both model types
|
||||
const ModelSchema = z.union([ChatModelSchema, EmbeddingModelSchema])
|
||||
|
||||
// Export the inferred type from the schema
|
||||
export type ModelInput = z.infer<typeof ModelSchema>
|
||||
|
||||
export const CreateModelModel = {
|
||||
body: ModelSchema,
|
||||
}
|
||||
|
||||
export const UpdateModelModel = {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
body: ModelSchema,
|
||||
}
|
||||
|
||||
export const GetModelByIdModel = {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
}
|
||||
|
||||
export const DeleteModelModel = {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
}
|
||||
|
||||
export const GetDefaultModelModel = {
|
||||
query: z.object({
|
||||
userId: z.string().min(1, 'User ID is required'),
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { db } from '@memohome/db'
|
||||
import { model } from '@memohome/db/schema'
|
||||
import { Model } from '@memohome/shared'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { getSettings } from '@/modules/settings/service'
|
||||
|
||||
export const getModels = async () => {
|
||||
const models = await db.select().from(model)
|
||||
return models
|
||||
}
|
||||
|
||||
export const getModelById = async (id: string) => {
|
||||
const [result] = await db.select().from(model).where(eq(model.id, id))
|
||||
return result
|
||||
}
|
||||
|
||||
export const createModel = async (data: Model) => {
|
||||
const [newModel] = await db
|
||||
.insert(model)
|
||||
.values({ model: data })
|
||||
.returning()
|
||||
return newModel
|
||||
}
|
||||
|
||||
export const updateModel = async (id: string, data: Model) => {
|
||||
const [updatedModel] = await db
|
||||
.update(model)
|
||||
.set({ model: data })
|
||||
.where(eq(model.id, id))
|
||||
.returning()
|
||||
return updatedModel
|
||||
}
|
||||
|
||||
export const deleteModel = async (id: string) => {
|
||||
const [deletedModel] = await db
|
||||
.delete(model)
|
||||
.where(eq(model.id, id))
|
||||
.returning()
|
||||
return deletedModel
|
||||
}
|
||||
|
||||
export const getChatModel = async (userId: string) => {
|
||||
const userSettings = await getSettings(userId)
|
||||
if (!userSettings?.defaultChatModel) {
|
||||
return null
|
||||
}
|
||||
return await getModelById(userSettings.defaultChatModel)
|
||||
}
|
||||
|
||||
export const getSummaryModel = async (userId: string) => {
|
||||
const userSettings = await getSettings(userId)
|
||||
if (!userSettings?.defaultSummaryModel) {
|
||||
return null
|
||||
}
|
||||
return await getModelById(userSettings.defaultSummaryModel)
|
||||
}
|
||||
|
||||
export const getEmbeddingModel = async (userId: string) => {
|
||||
const userSettings = await getSettings(userId)
|
||||
if (!userSettings?.defaultEmbeddingModel) {
|
||||
return null
|
||||
}
|
||||
return await getModelById(userSettings.defaultEmbeddingModel)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import Elysia from 'elysia'
|
||||
import {
|
||||
GetSettingsModel,
|
||||
CreateSettingsModel,
|
||||
UpdateSettingsModel,
|
||||
} from './model'
|
||||
import {
|
||||
getSettings,
|
||||
createSettings,
|
||||
updateSettings,
|
||||
upsertSettings,
|
||||
} from './service'
|
||||
|
||||
export const settingsModule = new Elysia({
|
||||
prefix: '/settings',
|
||||
})
|
||||
// Get user settings
|
||||
.get('/:userId', async ({ params }) => {
|
||||
try {
|
||||
const { userId } = params
|
||||
const userSettings = await getSettings(userId)
|
||||
if (!userSettings) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Settings not found',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: userSettings,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch settings',
|
||||
}
|
||||
}
|
||||
}, GetSettingsModel)
|
||||
// Create new settings
|
||||
.post('/', async ({ body }) => {
|
||||
try {
|
||||
const newSettings = await createSettings(body)
|
||||
return {
|
||||
success: true,
|
||||
data: newSettings,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create settings',
|
||||
}
|
||||
}
|
||||
}, CreateSettingsModel)
|
||||
// Update settings
|
||||
.put('/:userId', async ({ params, body }) => {
|
||||
try {
|
||||
const { userId } = params
|
||||
const updatedSettings = await updateSettings(userId, body)
|
||||
if (!updatedSettings) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Settings not found',
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: updatedSettings,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update settings',
|
||||
}
|
||||
}
|
||||
}, UpdateSettingsModel)
|
||||
// Upsert settings (create or update)
|
||||
.patch('/', async ({ body }) => {
|
||||
try {
|
||||
const result = await upsertSettings(body)
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to upsert settings',
|
||||
}
|
||||
}
|
||||
}, CreateSettingsModel)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const SettingsSchema = z.object({
|
||||
userId: z.string().min(1, 'User ID is required'),
|
||||
defaultChatModel: z.string().uuid().nullable().optional(),
|
||||
defaultEmbeddingModel: z.string().uuid().nullable().optional(),
|
||||
defaultSummaryModel: z.string().uuid().nullable().optional(),
|
||||
})
|
||||
|
||||
export type SettingsInput = z.infer<typeof SettingsSchema>
|
||||
|
||||
export const GetSettingsModel = {
|
||||
params: z.object({
|
||||
userId: z.string(),
|
||||
}),
|
||||
}
|
||||
|
||||
export const CreateSettingsModel = {
|
||||
body: SettingsSchema,
|
||||
}
|
||||
|
||||
export const UpdateSettingsModel = {
|
||||
params: z.object({
|
||||
userId: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
defaultChatModel: z.string().uuid().nullable().optional(),
|
||||
defaultEmbeddingModel: z.string().uuid().nullable().optional(),
|
||||
defaultSummaryModel: z.string().uuid().nullable().optional(),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { db } from '@memohome/db'
|
||||
import { settings } from '@memohome/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { SettingsInput } from './model'
|
||||
|
||||
export const getSettings = async (userId: string) => {
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.userId, userId))
|
||||
return result
|
||||
}
|
||||
|
||||
export const createSettings = async (data: SettingsInput) => {
|
||||
const [newSettings] = await db
|
||||
.insert(settings)
|
||||
.values({
|
||||
userId: data.userId,
|
||||
defaultChatModel: data.defaultChatModel || null,
|
||||
defaultEmbeddingModel: data.defaultEmbeddingModel || null,
|
||||
defaultSummaryModel: data.defaultSummaryModel || null,
|
||||
})
|
||||
.returning()
|
||||
return newSettings
|
||||
}
|
||||
|
||||
export const updateSettings = async (
|
||||
userId: string,
|
||||
data: Partial<Omit<SettingsInput, 'userId'>>
|
||||
) => {
|
||||
const [updatedSettings] = await db
|
||||
.update(settings)
|
||||
.set({
|
||||
defaultChatModel: data.defaultChatModel,
|
||||
defaultEmbeddingModel: data.defaultEmbeddingModel,
|
||||
defaultSummaryModel: data.defaultSummaryModel,
|
||||
})
|
||||
.where(eq(settings.userId, userId))
|
||||
.returning()
|
||||
return updatedSettings
|
||||
}
|
||||
|
||||
export const upsertSettings = async (data: SettingsInput) => {
|
||||
const [result] = await db
|
||||
.insert(settings)
|
||||
.values({
|
||||
userId: data.userId,
|
||||
defaultChatModel: data.defaultChatModel || null,
|
||||
defaultEmbeddingModel: data.defaultEmbeddingModel || null,
|
||||
defaultSummaryModel: data.defaultSummaryModel || null,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: settings.userId,
|
||||
set: {
|
||||
defaultChatModel: data.defaultChatModel,
|
||||
defaultEmbeddingModel: data.defaultEmbeddingModel,
|
||||
defaultSummaryModel: data.defaultSummaryModel,
|
||||
},
|
||||
})
|
||||
.returning()
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Tests
|
||||
|
||||
这个目录包含了使用 Elysia Eden Client 编写的 API 测试。
|
||||
|
||||
## 测试文件
|
||||
|
||||
- `setup.ts` - 测试设置文件,配置测试服务器和 eden client
|
||||
- `memory.test.ts` - Memory API 测试
|
||||
- `memory-message.test.ts` - Memory Message API 测试
|
||||
- `model.test.ts` - Model API 测试
|
||||
- `settings.test.ts` - Settings API 测试
|
||||
|
||||
## 运行测试
|
||||
|
||||
从项目根目录运行:
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
或者只运行 API 包的测试:
|
||||
|
||||
```bash
|
||||
cd packages/api
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## 测试说明
|
||||
|
||||
测试使用 vitest 作为测试框架,并使用 Elysia Eden Client (treaty) 来测试 API 端点。
|
||||
|
||||
测试服务器会在测试开始前启动(端口 7003),测试结束后自动关闭。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保数据库已配置并运行(某些测试可能需要数据库连接)
|
||||
- 测试使用独立的测试端口(7003)以避免与开发服务器冲突
|
||||
- 某些测试可能需要先创建数据才能测试查询功能
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getTestClient } from './setup'
|
||||
|
||||
describe('Memory Message API', () => {
|
||||
const client = getTestClient()
|
||||
|
||||
describe('GET /memory/message', () => {
|
||||
it('should get memory messages successfully', async () => {
|
||||
const response = await client.memory.message.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
limit: 10,
|
||||
page: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.units).toBeDefined()
|
||||
})
|
||||
|
||||
it('should use default limit and page when not provided', async () => {
|
||||
const response = await client.memory.message.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
limit: 10,
|
||||
page: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error for missing userId', async () => {
|
||||
const response = await client.memory.message.get({
|
||||
// @ts-expect-error - Testing invalid input
|
||||
query: {
|
||||
limit: 10,
|
||||
page: 1,
|
||||
// missing userId
|
||||
},
|
||||
})
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /memory/message/filter', () => {
|
||||
it('should filter memory messages successfully', async () => {
|
||||
const response = await client.memory.message.filter.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
from: new Date('2024-01-01') as unknown as string,
|
||||
to: new Date('2024-12-31') as unknown as string,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.units).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return error for missing required fields', async () => {
|
||||
const response = await client.memory.message.filter.get({
|
||||
// @ts-expect-error - Testing invalid input
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
// missing from and to
|
||||
},
|
||||
})
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getTestClient } from './setup'
|
||||
|
||||
describe('Memory API', () => {
|
||||
const client = getTestClient()
|
||||
|
||||
describe('POST /memory', () => {
|
||||
it('should add memory successfully', async () => {
|
||||
const memoryData = {
|
||||
messages: [
|
||||
{ role: 'user', content: 'Hello, this is a test message' },
|
||||
{ role: 'assistant', content: 'Hello! How can I help you?' },
|
||||
],
|
||||
timestamp: new Date(),
|
||||
user: 'test-user-123',
|
||||
}
|
||||
|
||||
const response = await client.memory.post(memoryData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
console.log(response.data?.error)
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return error for invalid memory data', async () => {
|
||||
const invalidData = {
|
||||
messages: [],
|
||||
// missing timestamp and user
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.memory.post(invalidData as any)
|
||||
|
||||
// Elysia 会返回 400 或 422 对于验证错误
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /memory/search', () => {
|
||||
it('should search memory successfully', async () => {
|
||||
const response = await client.memory.search.get({
|
||||
query: {
|
||||
query: 'test search',
|
||||
userId: 'test-user-123',
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(Array.isArray(response.data?.data)).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error for missing query', async () => {
|
||||
const response = await client.memory.search.get({
|
||||
// @ts-expect-error - Testing invalid input
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
// missing query
|
||||
},
|
||||
})
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
|
||||
it('should return error for missing userId', async () => {
|
||||
const response = await client.memory.search.get({
|
||||
// @ts-expect-error - Testing invalid input
|
||||
query: {
|
||||
query: 'test search',
|
||||
// missing userId
|
||||
},
|
||||
})
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { getTestClient } from './setup'
|
||||
|
||||
describe('Model API', () => {
|
||||
const client = getTestClient()
|
||||
let createdModelId: string | null = null
|
||||
|
||||
describe('GET /model', () => {
|
||||
it('should get all models successfully', async () => {
|
||||
const response = await client.model.get()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(Array.isArray(response.data?.data)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /model', () => {
|
||||
it('should create a chat model successfully', async () => {
|
||||
const modelData = {
|
||||
modelId: 'test-chat-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
name: 'Test Chat Model',
|
||||
type: 'chat' as const,
|
||||
}
|
||||
|
||||
const response = await client.model.post(modelData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
|
||||
if (response.data?.data?.id) {
|
||||
createdModelId = response.data.data.id
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an embedding model successfully', async () => {
|
||||
const modelData = {
|
||||
modelId: 'test-embedding-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
name: 'Test Embedding Model',
|
||||
type: 'embedding' as const,
|
||||
dimensions: 1536,
|
||||
}
|
||||
|
||||
const response = await client.model.post(modelData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return error for invalid model data', async () => {
|
||||
const invalidData = {
|
||||
// missing required fields
|
||||
name: 'Invalid Model',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.model.post(invalidData as any)
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
|
||||
it('should return error for embedding model without dimensions', async () => {
|
||||
const invalidData = {
|
||||
modelId: 'test-embedding-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
type: 'embedding' as const,
|
||||
// missing dimensions
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.model.post(invalidData as any)
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /model/:id', () => {
|
||||
it('should get model by id successfully', async () => {
|
||||
if (!createdModelId) {
|
||||
// 先创建一个模型
|
||||
const createResponse = await client.model.post({
|
||||
modelId: 'test-get-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
type: 'chat' as const,
|
||||
})
|
||||
|
||||
if (createResponse.data?.data?.id) {
|
||||
createdModelId = createResponse.data.data.id
|
||||
}
|
||||
}
|
||||
|
||||
if (createdModelId) {
|
||||
const response = await client.model({
|
||||
id: createdModelId,
|
||||
}).get()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('should return error for non-existent model', async () => {
|
||||
const response = await client.model({
|
||||
id: 'non-existent-id',
|
||||
}).get()
|
||||
|
||||
expect(response.status).toBe(200) // API 返回 200 但 success: false
|
||||
expect(response.data?.success).toBe(false)
|
||||
expect(response.data?.error).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /model/:id', () => {
|
||||
it('should update model successfully', async () => {
|
||||
if (!createdModelId) {
|
||||
const createResponse = await client.model.post({
|
||||
modelId: 'test-update-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
type: 'chat' as const,
|
||||
})
|
||||
|
||||
if (createResponse.data?.data?.id) {
|
||||
createdModelId = createResponse.data.data.id
|
||||
}
|
||||
}
|
||||
|
||||
if (createdModelId) {
|
||||
const updateData = {
|
||||
modelId: 'test-updated-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'updated-api-key',
|
||||
clientType: 'openai',
|
||||
name: 'Updated Model',
|
||||
type: 'chat' as const,
|
||||
}
|
||||
|
||||
const response = await client.model[createdModelId].put(updateData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE /model/:id', () => {
|
||||
it('should delete model successfully', async () => {
|
||||
// 先创建一个模型用于删除
|
||||
const createResponse = await client.model.post({
|
||||
modelId: 'test-delete-model',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-api-key',
|
||||
clientType: 'openai',
|
||||
type: 'chat' as const,
|
||||
})
|
||||
|
||||
const modelId = createResponse.data?.data?.id
|
||||
if (modelId) {
|
||||
const response = await client.model[modelId].delete()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /model/chat/default', () => {
|
||||
it('should get default chat model successfully', async () => {
|
||||
const response = await client.model.chat.default.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
// 可能返回 success: false 如果没有设置默认模型
|
||||
expect(response.data?.success !== undefined).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error for missing userId', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.model.chat.default.get({ query: {} } as any)
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /model/summary/default', () => {
|
||||
it('should get default summary model successfully', async () => {
|
||||
const response = await client.model.summary.default.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success !== undefined).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /model/embedding/default', () => {
|
||||
it('should get default embedding model successfully', async () => {
|
||||
const response = await client.model.embedding.default.get({
|
||||
query: {
|
||||
userId: 'test-user-123',
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success !== undefined).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { getTestClient } from './setup'
|
||||
|
||||
describe('Settings API', () => {
|
||||
const client = getTestClient()
|
||||
const testUserId = 'test-user-settings-123'
|
||||
|
||||
describe('GET /settings/:userId', () => {
|
||||
it('should get settings successfully', async () => {
|
||||
const response = await client.settings({
|
||||
userId: testUserId,
|
||||
}).get()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
// 可能返回 success: false 如果设置不存在
|
||||
expect(response.data?.success !== undefined).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /settings', () => {
|
||||
it('should create settings successfully', async () => {
|
||||
const settingsData = {
|
||||
userId: testUserId,
|
||||
defaultChatModel: null,
|
||||
defaultEmbeddingModel: null,
|
||||
defaultSummaryModel: null,
|
||||
}
|
||||
|
||||
const response = await client.settings.post(settingsData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('should create settings with model IDs', async () => {
|
||||
const settingsData = {
|
||||
userId: `${testUserId}-with-models`,
|
||||
defaultChatModel: '00000000-0000-0000-0000-000000000001',
|
||||
defaultEmbeddingModel: '00000000-0000-0000-0000-000000000002',
|
||||
defaultSummaryModel: '00000000-0000-0000-0000-000000000003',
|
||||
}
|
||||
|
||||
const response = await client.settings.post(settingsData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error for invalid UUID format', async () => {
|
||||
const invalidData = {
|
||||
userId: testUserId,
|
||||
defaultChatModel: 'invalid-uuid',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.settings.post(invalidData as any)
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
|
||||
it('should return error for missing userId', async () => {
|
||||
const invalidData = {
|
||||
defaultChatModel: null,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await client.settings.post(invalidData as any)
|
||||
|
||||
expect([400, 422]).toContain(response.status)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /settings/:userId', () => {
|
||||
it('should update settings successfully', async () => {
|
||||
// 先创建设置
|
||||
await client.settings.post({
|
||||
userId: `${testUserId}-update`,
|
||||
defaultChatModel: null,
|
||||
})
|
||||
|
||||
const updateData = {
|
||||
defaultChatModel: '00000000-0000-0000-0000-000000000001',
|
||||
defaultEmbeddingModel: '00000000-0000-0000-0000-000000000002',
|
||||
defaultSummaryModel: null,
|
||||
}
|
||||
|
||||
const response = await client.settings({
|
||||
userId: `${testUserId}-update`,
|
||||
}).put(updateData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return error for non-existent settings', async () => {
|
||||
const updateData = {
|
||||
defaultChatModel: '00000000-0000-0000-0000-000000000001',
|
||||
}
|
||||
|
||||
const response = await client.settings({
|
||||
userId: 'non-existent-user',
|
||||
}).put(updateData)
|
||||
|
||||
expect(response.status).toBe(200) // API 返回 200 但 success: false
|
||||
expect(response.data?.success).toBe(false)
|
||||
expect(response.data?.error).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('PATCH /settings', () => {
|
||||
it('should upsert settings successfully', async () => {
|
||||
const settingsData = {
|
||||
userId: `${testUserId}-upsert`,
|
||||
defaultChatModel: '00000000-0000-0000-0000-000000000001',
|
||||
defaultEmbeddingModel: null,
|
||||
defaultSummaryModel: null,
|
||||
}
|
||||
|
||||
const response = await client.settings.patch(settingsData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data).toBeDefined()
|
||||
expect(response.data?.success).toBe(true)
|
||||
expect(response.data?.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('should update existing settings on upsert', async () => {
|
||||
const userId = `${testUserId}-upsert-update`
|
||||
|
||||
// 先创建
|
||||
await client.settings.post({
|
||||
userId,
|
||||
defaultChatModel: null,
|
||||
})
|
||||
|
||||
// 然后 upsert
|
||||
const upsertData = {
|
||||
userId,
|
||||
defaultChatModel: '00000000-0000-0000-0000-000000000001',
|
||||
}
|
||||
|
||||
const response = await client.settings.patch(upsertData)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data?.success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createClient } from '../src/client'
|
||||
|
||||
export const getTestClient = () => {
|
||||
return createClient()
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/* Modules */
|
||||
"module": "ES2022", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
"moduleResolution": "Bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
@@ -98,6 +98,10 @@
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["src/**/*", "src/**/**/*"]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@memohome/shared": "workspace:*",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"pg": "^8.16.3"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Model } from '@memohome/shared'
|
||||
import { jsonb, pgTable, uuid } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const model = pgTable('model', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
model: jsonb('model').notNull().$type<Model>(),
|
||||
})
|
||||
@@ -1 +1,3 @@
|
||||
export * from './history'
|
||||
export * from './history'
|
||||
export * from './model'
|
||||
export * from './settings'
|
||||
@@ -0,0 +1,9 @@
|
||||
import { pgTable, text, uuid } from 'drizzle-orm/pg-core'
|
||||
import { model } from './model'
|
||||
|
||||
export const settings = pgTable('settings', {
|
||||
userId: text('user_id').primaryKey(),
|
||||
defaultChatModel: uuid('default_chat_model').references(() => model.id),
|
||||
defaultEmbeddingModel: uuid('default_embedding_model').references(() => model.id),
|
||||
defaultSummaryModel: uuid('default_summary_model').references(() => model.id),
|
||||
})
|
||||
Generated
+73
@@ -90,9 +90,39 @@ importers:
|
||||
|
||||
packages/api:
|
||||
dependencies:
|
||||
'@elysiajs/cors':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
'@elysiajs/cron':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
'@elysiajs/eden':
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.6(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
'@memohome/agent':
|
||||
specifier: workspace:*
|
||||
version: link:../agent
|
||||
'@memohome/db':
|
||||
specifier: workspace:*
|
||||
version: link:../db
|
||||
'@memohome/memory':
|
||||
specifier: workspace:*
|
||||
version: link:../memory
|
||||
'@memohome/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
drizzle-orm:
|
||||
specifier: ^0.45.1
|
||||
version: 0.45.1(@cloudflare/workers-types@4.20260109.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(bun-types@1.3.5)(pg@8.16.3)(sqlite3@5.1.7)
|
||||
elysia:
|
||||
specifier: latest
|
||||
version: 1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
node-cron:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
zod:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.5
|
||||
devDependencies:
|
||||
bun-types:
|
||||
specifier: latest
|
||||
@@ -100,6 +130,9 @@ importers:
|
||||
|
||||
packages/db:
|
||||
dependencies:
|
||||
'@memohome/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
dotenv:
|
||||
specifier: ^17.2.3
|
||||
version: 17.2.3
|
||||
@@ -524,6 +557,21 @@ packages:
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
'@elysiajs/cors@1.4.1':
|
||||
resolution: {integrity: sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ==}
|
||||
peerDependencies:
|
||||
elysia: '>= 1.4.0'
|
||||
|
||||
'@elysiajs/cron@1.4.1':
|
||||
resolution: {integrity: sha512-Y+jqXtMJ+m17QzNWlWc09ugd1Fn1Wh7lqE+y9qSW8eiQZEqsRADXWmbLuXRSRaSC5dkfOyRSiwNCp6n8L/yuqA==}
|
||||
peerDependencies:
|
||||
elysia: '>= 1.4.0'
|
||||
|
||||
'@elysiajs/eden@1.4.6':
|
||||
resolution: {integrity: sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q==}
|
||||
peerDependencies:
|
||||
elysia: '>=1.4.19'
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
@@ -2129,6 +2177,10 @@ packages:
|
||||
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
croner@6.0.7:
|
||||
resolution: {integrity: sha512-k3Xx3Rcclfr60Yx4TmvsF3Yscuiql8LSvYLaphTsaq5Hk8La4Z/udmUANMOTKpgGGroI2F6/XOr9cU9OFkYluQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -3253,6 +3305,10 @@ packages:
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-cron@4.2.1:
|
||||
resolution: {integrity: sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
@@ -4592,6 +4648,19 @@ snapshots:
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@elysiajs/cors@1.4.1(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
elysia: 1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
|
||||
'@elysiajs/cron@1.4.1(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
croner: 6.0.7
|
||||
elysia: 1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
|
||||
'@elysiajs/eden@1.4.6(elysia@1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
elysia: 1.4.21(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
@@ -6139,6 +6208,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-what: 5.5.0
|
||||
|
||||
croner@6.0.7: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -7253,6 +7324,8 @@ snapshots:
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-cron@4.2.1: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@2.7.0(encoding@0.1.13):
|
||||
|
||||
Reference in New Issue
Block a user