sonamu 0.7.16 → 0.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/providers/rtzr/error.d.ts +1 -1
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/api/config.d.ts +1 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +1 -1
- package/dist/api/sonamu.d.ts +3 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +48 -38
- package/dist/syncer/checksum.d.ts +8 -3
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +17 -9
- package/dist/syncer/code-generator.js +7 -2
- package/dist/syncer/syncer.d.ts +6 -6
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +27 -13
- package/dist/template/implementations/model.template.js +5 -5
- package/dist/template/implementations/services.template.d.ts +17 -0
- package/dist/template/implementations/services.template.d.ts.map +1 -0
- package/dist/template/implementations/services.template.js +159 -0
- package/dist/template/implementations/view_form.template.js +2 -2
- package/dist/template/implementations/view_id_async_select.template.js +2 -2
- package/dist/template/implementations/view_list.template.js +5 -5
- package/dist/types/types.d.ts +2 -14
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +3 -15
- package/dist/ui/ai-api.d.ts +2 -0
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +43 -49
- package/dist/ui/ai-client.d.ts +10 -0
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +457 -437
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +3 -1
- package/dist/ui-web/assets/index-DzqUrTB-.js +92 -0
- package/dist/ui-web/index.html +1 -1
- package/package.json +11 -7
- package/src/api/config.ts +3 -0
- package/src/api/decorators.ts +6 -1
- package/src/api/sonamu.ts +68 -50
- package/src/shared/app.shared.ts.txt +1 -1
- package/src/shared/web.shared.ts.txt +0 -43
- package/src/syncer/checksum.ts +31 -9
- package/src/syncer/code-generator.ts +8 -1
- package/src/syncer/syncer.ts +38 -26
- package/src/template/implementations/model.template.ts +4 -4
- package/src/template/implementations/services.template.ts +226 -0
- package/src/template/implementations/view_form.template.ts +1 -1
- package/src/template/implementations/view_id_async_select.template.ts +1 -1
- package/src/template/implementations/view_list.template.ts +4 -4
- package/src/types/types.ts +2 -14
- package/src/ui/ai-api.ts +61 -60
- package/src/ui/ai-client.ts +535 -499
- package/src/ui/api.ts +3 -0
- package/src/ui/entity.instructions.md +536 -0
- package/dist/template/implementations/service.template.d.ts +0 -29
- package/dist/template/implementations/service.template.d.ts.map +0 -1
- package/dist/template/implementations/service.template.js +0 -202
- package/dist/ui-web/assets/index-BcbbB-BB.js +0 -95
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +0 -1
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +0 -1
- package/src/template/implementations/service.template.ts +0 -328
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import inflection from "inflection";
|
|
2
|
+
import { diff, unique } from "radashi";
|
|
3
|
+
import {
|
|
4
|
+
apiParamToTsCode,
|
|
5
|
+
apiParamTypeToTsType,
|
|
6
|
+
unwrapPromiseOnce,
|
|
7
|
+
} from "../../api/code-converters";
|
|
8
|
+
import type { ExtendedApi } from "../../api/decorators";
|
|
9
|
+
import { Sonamu } from "../../api/sonamu";
|
|
10
|
+
import type { TemplateOptions } from "../../types/types";
|
|
11
|
+
import { ApiParamType } from "../../types/types";
|
|
12
|
+
import { assertDefined } from "../../utils/utils";
|
|
13
|
+
import { Template } from "../template";
|
|
14
|
+
|
|
15
|
+
export class Template__services extends Template {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("services");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getTargetAndPath() {
|
|
21
|
+
return {
|
|
22
|
+
target: ":target/src/services",
|
|
23
|
+
path: `services.generated.ts`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render({}: TemplateOptions["services"]) {
|
|
28
|
+
const { apis } = Sonamu.syncer;
|
|
29
|
+
|
|
30
|
+
// 모델별로 그룹화
|
|
31
|
+
const apisByModel = new Map<string, ExtendedApi[]>();
|
|
32
|
+
for (const api of apis) {
|
|
33
|
+
const modelName = api.modelName.replace(/Model$/, "").replace(/Frame$/, "");
|
|
34
|
+
if (!apisByModel.has(modelName)) {
|
|
35
|
+
apisByModel.set(modelName, []);
|
|
36
|
+
}
|
|
37
|
+
apisByModel.get(modelName)?.push(api);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const importKeys: string[] = [];
|
|
41
|
+
const namespaces: string[] = [];
|
|
42
|
+
let typeParamNames: string[] = [];
|
|
43
|
+
|
|
44
|
+
for (const [modelName, modelApis] of apisByModel) {
|
|
45
|
+
const functions: string[] = [];
|
|
46
|
+
|
|
47
|
+
for (const api of modelApis) {
|
|
48
|
+
// Context 제외한 파라미터
|
|
49
|
+
const paramsWithoutContext = api.parameters.filter(
|
|
50
|
+
(param) =>
|
|
51
|
+
!ApiParamType.isContext(param.type) &&
|
|
52
|
+
!ApiParamType.isRefKnex(param.type) &&
|
|
53
|
+
!(param.optional === true && param.name.startsWith("_")),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// 타입 파라미터 정의
|
|
57
|
+
const typeParametersAsTsType = api.typeParameters
|
|
58
|
+
.map((typeParam) => apiParamTypeToTsType(typeParam, importKeys))
|
|
59
|
+
.join(", ");
|
|
60
|
+
const typeParamsDef = typeParametersAsTsType ? `<${typeParametersAsTsType}>` : "";
|
|
61
|
+
typeParamNames = typeParamNames.concat(api.typeParameters.map((tp) => tp.id));
|
|
62
|
+
|
|
63
|
+
// 파라미터 정의
|
|
64
|
+
const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
|
|
65
|
+
const paramNames = paramsWithoutContext.map((p) => p.name).join(", ");
|
|
66
|
+
|
|
67
|
+
// 리턴 타입 정의
|
|
68
|
+
const returnTypeDef = apiParamTypeToTsType(
|
|
69
|
+
assertDefined(unwrapPromiseOnce(api.returnType)),
|
|
70
|
+
importKeys,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// 기본 URL
|
|
74
|
+
const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
|
|
75
|
+
|
|
76
|
+
const clients = api.options.clients || [];
|
|
77
|
+
|
|
78
|
+
// 1. axios 함수 생성
|
|
79
|
+
// resourceName이 있으면 get + resourceName 형태로 함수명 생성
|
|
80
|
+
const methodName = api.options.resourceName
|
|
81
|
+
? `get${inflection.camelize(api.options.resourceName)}`
|
|
82
|
+
: api.methodName;
|
|
83
|
+
|
|
84
|
+
// axios-multipart 처리 (파일 업로드)
|
|
85
|
+
if (clients.includes("axios-multipart")) {
|
|
86
|
+
const isMultiple = api.uploadOptions?.mode === "multiple";
|
|
87
|
+
const fileParamName = isMultiple ? "files" : "file";
|
|
88
|
+
const fileParamType = isMultiple ? "File[]" : "File";
|
|
89
|
+
|
|
90
|
+
const formDataAppend = isMultiple
|
|
91
|
+
? `${fileParamName}.forEach(f => { formData.append("${fileParamName}", f); });`
|
|
92
|
+
: `formData.append("${fileParamName}", ${fileParamName});`;
|
|
93
|
+
|
|
94
|
+
const otherParamsAppend = paramsWithoutContext
|
|
95
|
+
.map((param) => `formData.append('${param.name}', String(${param.name}));`)
|
|
96
|
+
.join("\n ");
|
|
97
|
+
|
|
98
|
+
const paramsDefComma = paramsDef !== "" ? ", " : "";
|
|
99
|
+
functions.push(
|
|
100
|
+
`
|
|
101
|
+
export async function ${methodName}${typeParamsDef}(
|
|
102
|
+
${paramsDef}${paramsDefComma}
|
|
103
|
+
${fileParamName}: ${fileParamType},
|
|
104
|
+
onUploadProgress?: (pe: AxiosProgressEvent) => void
|
|
105
|
+
): Promise<${returnTypeDef}> {
|
|
106
|
+
const formData = new FormData();
|
|
107
|
+
${formDataAppend}
|
|
108
|
+
${otherParamsAppend}
|
|
109
|
+
return fetch({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
url: \`${apiBaseUrl}\`,
|
|
112
|
+
headers: {
|
|
113
|
+
"Content-Type": "multipart/form-data",
|
|
114
|
+
},
|
|
115
|
+
onUploadProgress,
|
|
116
|
+
data: formData,
|
|
117
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
`.trim(),
|
|
121
|
+
);
|
|
122
|
+
} else if (api.options.httpMethod === "GET") {
|
|
123
|
+
const hasParams = paramsWithoutContext.length > 0;
|
|
124
|
+
functions.push(
|
|
125
|
+
`
|
|
126
|
+
export async function ${methodName}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
127
|
+
return fetch({
|
|
128
|
+
method: "GET",
|
|
129
|
+
url: \`${apiBaseUrl}${hasParams ? `?\${qs.stringify({ ${paramNames} })}` : ""}\`,
|
|
130
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
`.trim(),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
const hasParams = paramsWithoutContext.length > 0;
|
|
137
|
+
functions.push(
|
|
138
|
+
`
|
|
139
|
+
export async function ${methodName}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
140
|
+
return fetch({
|
|
141
|
+
method: "${api.options.httpMethod}",
|
|
142
|
+
url: \`${apiBaseUrl}\`,
|
|
143
|
+
${hasParams ? `data: { ${paramNames} },` : ""}
|
|
144
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
`.trim(),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. queryOptions + useQuery (tanstack-query)
|
|
152
|
+
if (clients.includes("tanstack-query")) {
|
|
153
|
+
const hookName = api.options.resourceName
|
|
154
|
+
? inflection.camelize(api.options.resourceName, true)
|
|
155
|
+
: inflection.camelize(api.methodName, true);
|
|
156
|
+
|
|
157
|
+
// queryOptions
|
|
158
|
+
functions.push(
|
|
159
|
+
`
|
|
160
|
+
export const ${methodName}QueryOptions = ${typeParamsDef}(${paramsDef}) => queryOptions({
|
|
161
|
+
queryKey: ['${modelName}', '${methodName}'${paramNames ? `, ${paramNames}` : ""}],
|
|
162
|
+
queryFn: () => ${methodName}(${paramNames})
|
|
163
|
+
});
|
|
164
|
+
`.trim(),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// useQuery hook
|
|
168
|
+
functions.push(
|
|
169
|
+
`
|
|
170
|
+
export const use${inflection.camelize(hookName)} = ${typeParamsDef}(${paramsDef}${
|
|
171
|
+
paramsDef ? ", " : ""
|
|
172
|
+
}options?: { enabled?: boolean }) =>
|
|
173
|
+
useQuery({
|
|
174
|
+
...${methodName}QueryOptions(${paramNames}),
|
|
175
|
+
...options
|
|
176
|
+
});
|
|
177
|
+
`.trim(),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 3. useMutation (tanstack-mutation)
|
|
182
|
+
if (clients.includes("tanstack-mutation")) {
|
|
183
|
+
const hookName = inflection.camelize(api.methodName);
|
|
184
|
+
const mutationParamType =
|
|
185
|
+
paramsWithoutContext.length > 0
|
|
186
|
+
? `{ ${paramsWithoutContext
|
|
187
|
+
.map((p) => `${p.name}: ${apiParamTypeToTsType(p.type, [])}`)
|
|
188
|
+
.join(", ")} }`
|
|
189
|
+
: "void";
|
|
190
|
+
const mutationParamNames =
|
|
191
|
+
paramsWithoutContext.length > 0
|
|
192
|
+
? paramsWithoutContext.map((p) => `params.${p.name}`).join(", ")
|
|
193
|
+
: "";
|
|
194
|
+
|
|
195
|
+
functions.push(
|
|
196
|
+
`
|
|
197
|
+
export const use${hookName}Mutation = ${typeParamsDef}() => useMutation({
|
|
198
|
+
mutationFn: (params: ${mutationParamType}) => ${methodName}(${mutationParamNames})
|
|
199
|
+
});
|
|
200
|
+
`.trim(),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
namespaces.push(
|
|
206
|
+
`
|
|
207
|
+
export namespace ${modelName}Service {
|
|
208
|
+
${functions.join("\n\n")}
|
|
209
|
+
}
|
|
210
|
+
`.trim(),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
...this.getTargetAndPath(),
|
|
216
|
+
body: namespaces.join("\n\n"),
|
|
217
|
+
importKeys: diff(unique(importKeys), [...typeParamNames, "ListResult"]),
|
|
218
|
+
customHeaders: [
|
|
219
|
+
`import { queryOptions, useQuery, useMutation } from '@tanstack/react-query';`,
|
|
220
|
+
`import type { AxiosProgressEvent } from 'axios';`,
|
|
221
|
+
`import qs from 'qs';`,
|
|
222
|
+
`import { type ListResult, fetch } from './sonamu.shared';`,
|
|
223
|
+
],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -256,7 +256,7 @@ import { defaultCatch } from '@/services/sonamu.shared';
|
|
|
256
256
|
// import { useCommonModal } from "@/admin-common/CommonModal";
|
|
257
257
|
|
|
258
258
|
import { ${names.capital}SaveParams } from '@/services/${names.fs}/${names.fs}.types';
|
|
259
|
-
import { ${names.capital}Service } from '@/services
|
|
259
|
+
import { ${names.capital}Service } from '@/services/services.generated';
|
|
260
260
|
import { ${names.capital}SubsetA } from '@/services/sonamu.generated';
|
|
261
261
|
${unique(
|
|
262
262
|
columns
|
|
@@ -40,7 +40,7 @@ import { DropdownProps, DropdownItemProps, DropdownOnSearchChangeData, Dropdown
|
|
|
40
40
|
import { ${names.capital}SubsetKey, ${
|
|
41
41
|
names.capital
|
|
42
42
|
}SubsetMapping } from "@/services/sonamu.generated";
|
|
43
|
-
import { ${names.capital}Service } from "@/services
|
|
43
|
+
import { ${names.capital}Service } from "@/services/services.generated";
|
|
44
44
|
import { ${names.capital}ListParams } from "@/services/${names.fs}/${names.fs}.types";
|
|
45
45
|
|
|
46
46
|
export function ${names.capital}IdAsyncSelect<T extends ${names.capital}SubsetKey>(
|
|
@@ -326,7 +326,7 @@ import { DateTime } from "luxon";
|
|
|
326
326
|
import { DelButton, EditButton, AppBreadcrumbs, AddButton, useSelection, useListParams, SonamuCol, numF, formatDate, formatDateTime } from '@sonamu-kit/react-sui';
|
|
327
327
|
|
|
328
328
|
import { ${names.capital}SubsetA } from "@/services/sonamu.generated";
|
|
329
|
-
import { ${names.capital}Service } from '@/services
|
|
329
|
+
import { ${names.capital}Service } from '@/services/services.generated';
|
|
330
330
|
import { ${names.capital}ListParams } from '@/services/${names.fs}/${names.fs}.types';
|
|
331
331
|
${columnImports}
|
|
332
332
|
${filterColumns
|
|
@@ -346,7 +346,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
// 리스트 쿼리
|
|
349
|
-
const { data,
|
|
349
|
+
const { data, refetch, isLoading } = ${names.capital}Service.use${
|
|
350
350
|
names.capitalPlural
|
|
351
351
|
}('A', listParams);
|
|
352
352
|
const { rows, total } = data ?? {};
|
|
@@ -359,7 +359,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
${names.capital}Service.del(ids).then(() => {
|
|
362
|
-
|
|
362
|
+
refetch();
|
|
363
363
|
});
|
|
364
364
|
};
|
|
365
365
|
|
|
@@ -371,7 +371,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
${names.capital}Service.del(selectedKeys).then(() => {
|
|
374
|
-
|
|
374
|
+
refetch();
|
|
375
375
|
});
|
|
376
376
|
};
|
|
377
377
|
|
package/src/types/types.ts
CHANGED
|
@@ -1237,19 +1237,7 @@ export const TemplateOptions = z.object({
|
|
|
1237
1237
|
bridge: z.object({
|
|
1238
1238
|
entityId: z.string(),
|
|
1239
1239
|
}),
|
|
1240
|
-
|
|
1241
|
-
namesRecord: z.object({
|
|
1242
|
-
fs: z.string(),
|
|
1243
|
-
fsPlural: z.string(),
|
|
1244
|
-
camel: z.string(),
|
|
1245
|
-
camelPlural: z.string(),
|
|
1246
|
-
capital: z.string(),
|
|
1247
|
-
capitalPlural: z.string(),
|
|
1248
|
-
upper: z.string(),
|
|
1249
|
-
constant: z.string(),
|
|
1250
|
-
}),
|
|
1251
|
-
modelTsPath: z.string(),
|
|
1252
|
-
}),
|
|
1240
|
+
services: z.object({}),
|
|
1253
1241
|
view_list: z.object({
|
|
1254
1242
|
entityId: z.string(),
|
|
1255
1243
|
extra: z.unknown(),
|
|
@@ -1302,7 +1290,7 @@ export const TemplateKey = z.enum([
|
|
|
1302
1290
|
"model",
|
|
1303
1291
|
"model_test",
|
|
1304
1292
|
"bridge",
|
|
1305
|
-
"
|
|
1293
|
+
"services",
|
|
1306
1294
|
"view_list",
|
|
1307
1295
|
"view_list_columns",
|
|
1308
1296
|
"view_search_input",
|
package/src/ui/ai-api.ts
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
import { convertToModelMessages, type UIMessage } from "ai";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import { BadRequestException } from "../exceptions/so-exceptions";
|
|
4
|
+
import type { FixtureRecord } from "../types/types";
|
|
5
|
+
import { aiClient } from "./ai-client";
|
|
6
|
+
|
|
7
|
+
export async function setAiApi(server: FastifyInstance) {
|
|
8
|
+
await aiClient.init();
|
|
9
|
+
|
|
10
|
+
server.post("/api/ai/fixture/chat", async (request, reply) => {
|
|
11
|
+
const { messages, fixtureRecords } = request.body as {
|
|
12
|
+
messages: UIMessage[];
|
|
13
|
+
fixtureRecords?: FixtureRecord[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (!fixtureRecords || fixtureRecords.length === 0) {
|
|
17
|
+
throw new BadRequestException("픽스쳐 레코드가 없습니다. 픽스쳐 조회 후 시도하세요.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = aiClient.handleFixture(await convertToModelMessages(messages), fixtureRecords);
|
|
21
|
+
const response = result.toUIMessageStreamResponse();
|
|
22
|
+
|
|
23
|
+
reply.raw.writeHead(response.status, Object.fromEntries(Object.entries(response.headers)));
|
|
24
|
+
|
|
25
|
+
if (response.body) {
|
|
26
|
+
const reader = response.body.getReader();
|
|
27
|
+
while (true) {
|
|
28
|
+
const { done, value } = await reader.read();
|
|
29
|
+
if (done) break;
|
|
30
|
+
reply.raw.write(value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reply.raw.end();
|
|
35
|
+
return reply;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Entity/Enum 생성용 AI Chat Stream
|
|
39
|
+
server.post("/api/ai/entity/chat", async (request, reply) => {
|
|
40
|
+
const { messages } = request.body as {
|
|
41
|
+
messages: UIMessage[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = aiClient.handleEntity(await convertToModelMessages(messages));
|
|
45
|
+
const response = result.toUIMessageStreamResponse();
|
|
46
|
+
|
|
47
|
+
reply.raw.writeHead(response.status, Object.fromEntries(Object.entries(response.headers)));
|
|
48
|
+
|
|
49
|
+
if (response.body) {
|
|
50
|
+
const reader = response.body.getReader();
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
reply.raw.write(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
reply.raw.end();
|
|
59
|
+
return reply;
|
|
60
|
+
});
|
|
61
|
+
}
|