feat: ai module (#1649)
* fix: pass `truncate` Signed-off-by: Innei <i@innei.in> * feat: add openai summary Signed-off-by: Innei <i@innei.in> * feat: ai list api Signed-off-by: Innei <i@innei.in> --------- Signed-off-by: Innei <i@innei.in>
This commit is contained in:
34
packages/api-client/__tests__/controllers/ai.test.ts
Normal file
34
packages/api-client/__tests__/controllers/ai.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { mockRequestInstance } from '~/__tests__/helpers/instance'
|
||||
import { mockResponse } from '~/__tests__/helpers/response'
|
||||
import { AIController } from '~/controllers'
|
||||
|
||||
describe('test ai client', () => {
|
||||
const client = mockRequestInstance(AIController)
|
||||
|
||||
test('POST /generate-summary', async () => {
|
||||
mockResponse('/ai/generate-summary', {}, 'post', {
|
||||
lang: 'zh-CN',
|
||||
refId: '11',
|
||||
})
|
||||
|
||||
await expect(
|
||||
client.ai.generateSummary('11', 'zh-CN'),
|
||||
).resolves.not.toThrowError()
|
||||
})
|
||||
|
||||
test('GET /summary/:id', async () => {
|
||||
mockResponse(
|
||||
'/ai/summaries/article/11?articleId=11&lang=zh-CN&onlyDb=true',
|
||||
{},
|
||||
'get',
|
||||
)
|
||||
|
||||
await expect(
|
||||
client.ai.getSummary({
|
||||
articleId: '11',
|
||||
lang: 'zh-CN',
|
||||
onlyDb: true,
|
||||
}),
|
||||
).resolves.not.toThrowError()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import camelcaseKeysLib from 'camelcase-keys'
|
||||
|
||||
import { camelcaseKeys } from '~/utils/camelcase-keys'
|
||||
import { camelcase, camelcaseKeys } from '~/utils/camelcase-keys'
|
||||
|
||||
describe('test camelcase keys', () => {
|
||||
it('case 1 normal', () => {
|
||||
@@ -80,7 +80,33 @@ describe('test camelcase keys', () => {
|
||||
]
|
||||
|
||||
expect(camelcaseKeys(arr)).toStrictEqual(
|
||||
camelcaseKeysLib(arr, { deep: true }),
|
||||
camelcaseKeysLib(arr as any, { deep: true }),
|
||||
)
|
||||
})
|
||||
|
||||
it('case 6: filter out mongo id', () => {
|
||||
const obj = {
|
||||
_id: '123',
|
||||
a_b: 1,
|
||||
collections: {
|
||||
posts: {
|
||||
'661bb93307d35005ba96731b': {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(camelcaseKeys(obj)).toStrictEqual({
|
||||
id: '123',
|
||||
aB: 1,
|
||||
collections: {
|
||||
posts: {
|
||||
'661bb93307d35005ba96731b': {},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('case 7: start with underscore should not camelcase', () => {
|
||||
expect(camelcase('_id')).toBe('id')
|
||||
})
|
||||
})
|
||||
|
||||
61
packages/api-client/controllers/ai.ts
Normal file
61
packages/api-client/controllers/ai.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { IRequestAdapter } from '~/interfaces/adapter'
|
||||
import type { IController } from '~/interfaces/controller'
|
||||
import type { IRequestHandler } from '~/interfaces/request'
|
||||
import type { HTTPClient } from '../core'
|
||||
import type { AISummaryModel } from '../models/ai'
|
||||
|
||||
import { autoBind } from '~/utils/auto-bind'
|
||||
|
||||
declare module '../core/client' {
|
||||
interface HTTPClient<
|
||||
T extends IRequestAdapter = IRequestAdapter,
|
||||
ResponseWrapper = unknown,
|
||||
> {
|
||||
ai: AIController<ResponseWrapper>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @support core >= 5.6.0
|
||||
*/
|
||||
export class AIController<ResponseWrapper> implements IController {
|
||||
base = 'ai'
|
||||
name = 'ai'
|
||||
|
||||
constructor(private client: HTTPClient) {
|
||||
autoBind(this)
|
||||
}
|
||||
|
||||
public get proxy(): IRequestHandler<ResponseWrapper> {
|
||||
return this.client.proxy(this.base)
|
||||
}
|
||||
|
||||
async getSummary({
|
||||
articleId,
|
||||
lang = 'zh-CN',
|
||||
onlyDb,
|
||||
}: {
|
||||
articleId: string
|
||||
lang?: string
|
||||
onlyDb?: boolean
|
||||
}) {
|
||||
return this.proxy.summaries.article(articleId).get<AISummaryModel>({
|
||||
params: {
|
||||
lang,
|
||||
onlyDb,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async generateSummary(articleId: string, lang = 'zh-CN', token = '') {
|
||||
return this.proxy('generate-summary').post<AISummaryModel>({
|
||||
params: {
|
||||
token,
|
||||
},
|
||||
data: {
|
||||
lang,
|
||||
refId: articleId,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AckController } from './ack'
|
||||
import { ActivityController } from './activity'
|
||||
import { AggregateController } from './aggregate'
|
||||
import { AIController } from './ai'
|
||||
import { CategoryController } from './category'
|
||||
import { CommentController } from './comment'
|
||||
import { LinkController } from './link'
|
||||
@@ -22,6 +23,7 @@ import { TopicController } from './topic'
|
||||
import { UserController } from './user'
|
||||
|
||||
export const allControllers = [
|
||||
AIController,
|
||||
AckController,
|
||||
ActivityController,
|
||||
AggregateController,
|
||||
@@ -43,6 +45,7 @@ export const allControllers = [
|
||||
]
|
||||
|
||||
export const allControllerNames = [
|
||||
'ai',
|
||||
'ack',
|
||||
'activity',
|
||||
'aggregate',
|
||||
@@ -69,6 +72,7 @@ export const allControllerNames = [
|
||||
] as const
|
||||
|
||||
export {
|
||||
AIController,
|
||||
AckController,
|
||||
ActivityController,
|
||||
AggregateController,
|
||||
|
||||
8
packages/api-client/models/ai.ts
Normal file
8
packages/api-client/models/ai.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface AISummaryModel {
|
||||
id: string
|
||||
created: string
|
||||
summary: string
|
||||
hash: string
|
||||
refId: string
|
||||
lang: string
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './activity'
|
||||
export * from './aggregate'
|
||||
export * from './ai'
|
||||
export * from './base'
|
||||
export * from './category'
|
||||
export * from './comment'
|
||||
|
||||
@@ -11,7 +11,8 @@ export const camelcaseKeys = <T = any>(obj: any): T => {
|
||||
|
||||
if (isPlainObject(obj)) {
|
||||
return Object.keys(obj).reduce((result: any, key) => {
|
||||
result[camelcase(key)] = camelcaseKeys(obj[key])
|
||||
const nextKey = isMongoId(key) ? key : camelcase(key)
|
||||
result[nextKey] = camelcaseKeys(obj[key])
|
||||
return result
|
||||
}, {}) as any
|
||||
}
|
||||
@@ -20,7 +21,9 @@ export const camelcaseKeys = <T = any>(obj: any): T => {
|
||||
}
|
||||
|
||||
export function camelcase(str: string) {
|
||||
return str.replace(/([-_][a-z])/gi, ($1) => {
|
||||
return str.replace(/^_+/, '').replace(/([-_][a-z])/gi, ($1) => {
|
||||
return $1.toUpperCase().replace('-', '').replace('_', '')
|
||||
})
|
||||
}
|
||||
const isMongoId = (id: string) =>
|
||||
id.length === 24 && /^[0-9a-fA-F]{24}$/.test(id)
|
||||
|
||||
Reference in New Issue
Block a user