@appaflytech/zeyrek-ai-mcp 0.0.2 → 0.0.4
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/.env.example +21 -7
- package/.env.production.example +42 -0
- package/README.md +14 -7
- package/dist/config.js +113 -46
- package/dist/knowledge/componentCatalog.js +476 -32
- package/dist/knowledge/componentEvents.js +154 -12
- package/dist/knowledge/errorPath.js +64 -0
- package/dist/knowledge/normalize.js +12 -1
- package/dist/knowledge/outlines.js +9 -2
- package/dist/knowledge/rules.js +184 -3
- package/dist/knowledge/styleResolve.js +485 -0
- package/dist/observability/trace.js +4 -0
- package/dist/platform/http.js +3 -0
- package/dist/platform/responses.js +80 -4
- package/dist/platform/screenshot.js +106 -0
- package/dist/resources/examples/layout-app-shell.json +368 -0
- package/dist/resources/examples/microservice-table-list.json +1 -0
- package/dist/resources/examples/middleware-authenticate.json +243 -47
- package/dist/resources/examples/workflow-basic.json +24 -2
- package/dist/resources/guides/component.md +6 -3
- package/dist/resources/guides/entity.md +33 -1
- package/dist/resources/guides/external-component.md +129 -0
- package/dist/resources/guides/layout.md +147 -0
- package/dist/resources/guides/microservice.md +35 -0
- package/dist/resources/guides/middleware.md +10 -0
- package/dist/resources/guides/page.md +137 -15
- package/dist/resources/guides/translation.md +80 -0
- package/dist/resources/guides/workflow.md +127 -2
- package/dist/server.js +4 -0
- package/dist/tools/externalComponents.js +285 -0
- package/dist/tools/layouts.js +48 -6
- package/dist/tools/microservices.js +2 -2
- package/dist/tools/pages.js +98 -30
- package/dist/tools/platformInfo.js +2 -2
- package/dist/tools/projects.js +7 -2
- package/dist/tools/screens.js +36 -0
- package/dist/tools/translations.js +37 -0
- package/dist/tools/workflows.js +3 -3
- package/package.json +4 -1
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ÖZET — Bir düğümün EFEKTİF stili: tasarımcının ekranda gördüğü sonucun
|
|
3
|
+
* gateway tarafında yeniden kurulması. `page_runtime_get(resolveStyles: true)`
|
|
4
|
+
* her düğüme bu modülün ürettiği `resolvedStyle` + `styleLayers`'ı ekler ve
|
|
5
|
+
* "bu kutu neden böyle görünüyor" sorusu cevaplanabilir olur.
|
|
6
|
+
*
|
|
7
|
+
* KATMAN SIRASI (karar K4, 2026-08-30) — tipli stil komponentlerinde:
|
|
8
|
+
* tema componentStyles → $preset = TABAN (varsayılan verir)
|
|
9
|
+
* tipli gruplar (panel seçimleri) = ortada (temayı alan alan ezer)
|
|
10
|
+
* düğümün serbest `style`'ı = EN ÜST (kaçış kapısı, son söz)
|
|
11
|
+
* Tipli olmayan komponentlerde eski birleşik kaskad geçerlidir
|
|
12
|
+
* (componentStyles → preset → düğüm).
|
|
13
|
+
*
|
|
14
|
+
* KAYNAK EŞLEŞMESİ: kaskad `zeyrek-frontend/packages/ng-apps-screen-builder/
|
|
15
|
+
* src/style-presets.ts`'in, tipli derleme `zeyrek-frontend/packages/ng-apps-ui/
|
|
16
|
+
* src/style-groups/style-group-compose.ts`'in (aibox bölge uzantısı
|
|
17
|
+
* `.../components/aibox/aibox-style-logic.ts`) birebir kopyasıdır. Gateway
|
|
18
|
+
* frontend workspace'ine dahil olmadığı için paket import edilemez; kopyanın
|
|
19
|
+
* sürüklenmesini `test/style-resolve.test.ts` üç yoldan kilitler: kaynak metin
|
|
20
|
+
* ipleri, ortak ALTIN vakalar (`style-group-golden.json`) ve `components.json`
|
|
21
|
+
* ile ALAN pariteleri. Yeni bir komponent tipli gruplara katıldığında buradaki
|
|
22
|
+
* `TYPED_STYLE_COMPONENTS` kaydına eklenir — başka hiçbir yer değişmez.
|
|
23
|
+
*/
|
|
24
|
+
export const STYLE_PRESET_KEY = '$preset';
|
|
25
|
+
/**
|
|
26
|
+
* Satır yüksekliği ÜÇ yazımı da kabul eder: oran (1.4), yüzde ('140%') ve CSS
|
|
27
|
+
* uzunluğu ('24px'). Çıplak sayı ikircikli tek durumdur — eski panel YÜZDE
|
|
28
|
+
* yazıyordu (140 → 1.4), bu yüzden 4'ten büyük çıplak sayı yüzde sayılır.
|
|
29
|
+
*/
|
|
30
|
+
export const asLineHeight = (value) => {
|
|
31
|
+
if (!isSet(value))
|
|
32
|
+
return undefined;
|
|
33
|
+
const text = String(value).trim();
|
|
34
|
+
if (!text)
|
|
35
|
+
return undefined;
|
|
36
|
+
const numeric = Number(text);
|
|
37
|
+
if (Number.isFinite(numeric)) {
|
|
38
|
+
if (numeric <= 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
return numeric >= 4 ? numeric / 100 : numeric;
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
};
|
|
44
|
+
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
export const deepMergeStyle = (base, override) => {
|
|
46
|
+
const result = { ...base };
|
|
47
|
+
for (const key in override) {
|
|
48
|
+
const next = override[key];
|
|
49
|
+
const current = result[key];
|
|
50
|
+
result[key] = isPlainObject(current) && isPlainObject(next) ? deepMergeStyle(current, next) : next;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
export const resolveNodeStyleLayers = (style, source, nodeType) => {
|
|
55
|
+
const styleObject = isPlainObject(style) ? style : undefined;
|
|
56
|
+
const { [STYLE_PRESET_KEY]: presetName, ...own } = styleObject ?? {};
|
|
57
|
+
const componentStyle = nodeType && isPlainObject(source?.componentStyles?.[nodeType]) ? source.componentStyles[nodeType] : undefined;
|
|
58
|
+
const preset = typeof presetName === 'string' && isPlainObject(source?.presets?.[presetName]) ? source.presets[presetName] : undefined;
|
|
59
|
+
let theme;
|
|
60
|
+
if (componentStyle || preset) {
|
|
61
|
+
theme = deepMergeStyle(componentStyle ?? {}, preset ?? {});
|
|
62
|
+
delete theme[STYLE_PRESET_KEY];
|
|
63
|
+
}
|
|
64
|
+
if (!styleObject && typeof style === 'string' && style)
|
|
65
|
+
return { theme, own: style };
|
|
66
|
+
return { theme, own: Object.keys(own).length ? own : undefined };
|
|
67
|
+
};
|
|
68
|
+
export const resolveNodeStyle = (style, source, nodeType) => {
|
|
69
|
+
if (!isPlainObject(style) && style !== undefined && style !== null)
|
|
70
|
+
return style;
|
|
71
|
+
const { theme, own } = resolveNodeStyleLayers(style, source, nodeType);
|
|
72
|
+
const result = deepMergeStyle(theme ?? {}, isPlainObject(own) ? own : {});
|
|
73
|
+
return Object.keys(result).length ? result : undefined;
|
|
74
|
+
};
|
|
75
|
+
/* ---- Tipli grup derlemesi (style-group-compose.ts kopyası) ---- */
|
|
76
|
+
// Kademeler tema ölçeğinin token'larıdır (frontend T.1 ile birebir):
|
|
77
|
+
// tema "Border radius"u değişince sm/lg oransal izler, kaynak tek yerdedir.
|
|
78
|
+
const RADIUS_VALUES = {
|
|
79
|
+
none: '0',
|
|
80
|
+
sm: 'var(--ng-radius-sm)',
|
|
81
|
+
md: 'var(--ng-radius-md)',
|
|
82
|
+
lg: 'var(--ng-radius-lg)',
|
|
83
|
+
pill: 'var(--ng-radius-pill)'
|
|
84
|
+
};
|
|
85
|
+
const SHADOW_VALUES = {
|
|
86
|
+
none: 'none',
|
|
87
|
+
sm: 'var(--ng-shadow-sm)',
|
|
88
|
+
md: 'var(--ng-shadow-md)',
|
|
89
|
+
lg: 'var(--ng-shadow-lg)'
|
|
90
|
+
};
|
|
91
|
+
const DEFAULT_DIVIDER_COLOR = 'var(--ng-border-primary)';
|
|
92
|
+
const isUnresolvedExpression = (value) => typeof value === 'string' && value.includes('{{');
|
|
93
|
+
const isSet = (value) => value !== undefined && value !== null && value !== '' && !isUnresolvedExpression(value);
|
|
94
|
+
const asNumber = (value) => {
|
|
95
|
+
if (!isSet(value))
|
|
96
|
+
return undefined;
|
|
97
|
+
const parsed = Number(value);
|
|
98
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* DEĞER MODELİ (K9): boyut alanları CSS METNİ taşır ('18px', '1.2em', '50%');
|
|
102
|
+
* çıplak sayı px sayılır (eski sayfa toleransı); sıfır ve boşluk "seçilmedi".
|
|
103
|
+
*/
|
|
104
|
+
const asDimension = (value) => {
|
|
105
|
+
if (!isSet(value))
|
|
106
|
+
return undefined;
|
|
107
|
+
if (typeof value === 'number')
|
|
108
|
+
return Number.isFinite(value) && value !== 0 ? `${value}px` : undefined;
|
|
109
|
+
const text = String(value).trim();
|
|
110
|
+
if (!text)
|
|
111
|
+
return undefined;
|
|
112
|
+
const numeric = Number(text);
|
|
113
|
+
// METİN sıfır gerçek seçimdir (bileşenin taban dolgusunu kaldırmanın tek
|
|
114
|
+
// yolu); yalnız SAYI 0 "seçilmedi" sayılır (eski sayı girdisi toleransı).
|
|
115
|
+
if (Number.isFinite(numeric))
|
|
116
|
+
return `${numeric}px`;
|
|
117
|
+
return text;
|
|
118
|
+
};
|
|
119
|
+
const assign = (target, key, value) => {
|
|
120
|
+
if (isSet(value))
|
|
121
|
+
target[key] = value;
|
|
122
|
+
};
|
|
123
|
+
const nest = (target, selector, rules) => {
|
|
124
|
+
if (!Object.keys(rules).length)
|
|
125
|
+
return;
|
|
126
|
+
target[selector] = isPlainObject(target[selector]) ? deepMergeStyle(target[selector], rules) : rules;
|
|
127
|
+
};
|
|
128
|
+
/** `radius` TEK alandır: preset anahtarı ('lg') YA DA CSS uzunluğu ('18px'). */
|
|
129
|
+
const resolveRadius = (value) => {
|
|
130
|
+
if (!isSet(value))
|
|
131
|
+
return undefined;
|
|
132
|
+
return RADIUS_VALUES[value] ?? asDimension(value);
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Gölge TEK alanda iki yazımı taşır: eski preset anahtarı ('md') tema
|
|
136
|
+
* token'ına çevrilir, geri kalan her şey (çok katmanlı `box-shadow`, `inset`,
|
|
137
|
+
* `var(--ng-shadow-lg)`) olduğu gibi CSS'e iner.
|
|
138
|
+
*/
|
|
139
|
+
const resolveShadow = (shadow) => {
|
|
140
|
+
if (!isSet(shadow))
|
|
141
|
+
return undefined;
|
|
142
|
+
return SHADOW_VALUES[shadow] ?? String(shadow);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Grupların alan listeleri — frontend `style-groups/style-group-schemas.ts`
|
|
146
|
+
* `STYLE_GROUP_FIELDS`'in kopyası. Parite testi `components.json`'daki gerçek
|
|
147
|
+
* şemayı bu listeyle karşılaştırır: frontend'e eklenen bir alan buraya
|
|
148
|
+
* yansıtılmazsa test kırmızıya düşer.
|
|
149
|
+
*/
|
|
150
|
+
export const STYLE_GROUP_FIELDS = {
|
|
151
|
+
surfaceStyle: [
|
|
152
|
+
'background',
|
|
153
|
+
'radius',
|
|
154
|
+
'borderTop',
|
|
155
|
+
'borderRight',
|
|
156
|
+
'borderBottom',
|
|
157
|
+
'borderLeft',
|
|
158
|
+
'shadow',
|
|
159
|
+
'contentOverflow'
|
|
160
|
+
],
|
|
161
|
+
typographyStyle: [
|
|
162
|
+
'textColor',
|
|
163
|
+
'fontFamily',
|
|
164
|
+
'fontSize',
|
|
165
|
+
'fontWeight',
|
|
166
|
+
'fontStyle',
|
|
167
|
+
'textDecoration',
|
|
168
|
+
'lineHeight',
|
|
169
|
+
'letterSpacing',
|
|
170
|
+
'textAlign'
|
|
171
|
+
],
|
|
172
|
+
hoverStyle: ['background', 'borderColor', 'shadow', 'lift', 'transitionMs']
|
|
173
|
+
};
|
|
174
|
+
export function composeTypedStyle(input, ext) {
|
|
175
|
+
const groups = {};
|
|
176
|
+
if (isPlainObject(input.surfaceStyle)) {
|
|
177
|
+
const surface = input.surfaceStyle;
|
|
178
|
+
assign(groups, 'background', surface.background);
|
|
179
|
+
assign(groups, 'borderRadius', resolveRadius(surface.radius));
|
|
180
|
+
assign(groups, 'boxShadow', resolveShadow(surface.shadow));
|
|
181
|
+
// Kenarlık YÖN BAŞINA yazılır (CSS'in `border` kısayolu yön başına değer
|
|
182
|
+
// taşıyamaz). Hiçbir yön yazılmamışsa EMEKLİ üçlü okunur — kayıtlı
|
|
183
|
+
// sayfalar bozulmasın diye; bir yön yazıldığı anda takma ad susar.
|
|
184
|
+
let wroteSide = false;
|
|
185
|
+
for (const side of ['borderTop', 'borderRight', 'borderBottom', 'borderLeft']) {
|
|
186
|
+
if (isSet(surface[side])) {
|
|
187
|
+
groups[side] = surface[side];
|
|
188
|
+
wroteSide = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!wroteSide) {
|
|
192
|
+
const width = asNumber(surface.borderWidth);
|
|
193
|
+
if (width !== undefined) {
|
|
194
|
+
// 0 geçerli bir seçimdir: "çerçeveyi kapat" demektir, atlanamaz.
|
|
195
|
+
groups.border = width > 0 ? `${width}px ${surface.borderStyle || 'solid'} ${surface.borderColor || DEFAULT_DIVIDER_COLOR}` : 'none';
|
|
196
|
+
}
|
|
197
|
+
else if (isSet(surface.borderColor) || isSet(surface.borderStyle)) {
|
|
198
|
+
groups.border = `1px ${surface.borderStyle || 'solid'} ${surface.borderColor || DEFAULT_DIVIDER_COLOR}`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (isSet(surface.contentOverflow) && ext?.overflowTarget) {
|
|
202
|
+
nest(groups, ext.overflowTarget, { overflow: surface.contentOverflow });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (isPlainObject(input.typographyStyle)) {
|
|
206
|
+
const typography = input.typographyStyle;
|
|
207
|
+
assign(groups, 'color', typography.textColor);
|
|
208
|
+
assign(groups, 'fontFamily', typography.fontFamily);
|
|
209
|
+
assign(groups, 'textAlign', typography.textAlign);
|
|
210
|
+
assign(groups, 'fontStyle', typography.fontStyle);
|
|
211
|
+
assign(groups, 'textDecoration', typography.textDecoration);
|
|
212
|
+
assign(groups, 'fontSize', asDimension(typography.fontSize));
|
|
213
|
+
assign(groups, 'letterSpacing', asDimension(typography.letterSpacing));
|
|
214
|
+
if (isSet(typography.fontWeight))
|
|
215
|
+
groups.fontWeight = String(typography.fontWeight);
|
|
216
|
+
const lineHeight = asLineHeight(typography.lineHeight);
|
|
217
|
+
if (lineHeight !== undefined)
|
|
218
|
+
groups.lineHeight = lineHeight;
|
|
219
|
+
}
|
|
220
|
+
if (isPlainObject(input.hoverStyle)) {
|
|
221
|
+
const hover = input.hoverStyle;
|
|
222
|
+
const rules = {};
|
|
223
|
+
assign(rules, 'background', hover.background);
|
|
224
|
+
assign(rules, 'borderColor', hover.borderColor);
|
|
225
|
+
assign(rules, 'boxShadow', resolveShadow(hover.shadow));
|
|
226
|
+
const lift = asDimension(hover.lift);
|
|
227
|
+
if (lift !== undefined)
|
|
228
|
+
rules.transform = `translateY(calc(-1 * ${lift}))`;
|
|
229
|
+
if (Object.keys(rules).length) {
|
|
230
|
+
const duration = asNumber(hover.transitionMs) ?? 160;
|
|
231
|
+
groups.transition = `background-color ${duration}ms ease, border-color ${duration}ms ease, box-shadow ${duration}ms ease, transform ${duration}ms ease`;
|
|
232
|
+
nest(groups, '&:hover', rules);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
ext?.extra?.(input, groups);
|
|
236
|
+
// K4: tema tabandır, gruplar üstüne alan alan biner.
|
|
237
|
+
const themed = isPlainObject(input.themeStyle) ? deepMergeStyle(input.themeStyle, groups) : groups;
|
|
238
|
+
// Metin `style` çizilemez (designerStyle nesne olmayanı eler); atılır,
|
|
239
|
+
// tema + gruplar korunur.
|
|
240
|
+
if (typeof input.style === 'string')
|
|
241
|
+
return Object.keys(themed).length ? themed : undefined;
|
|
242
|
+
const merged = isPlainObject(input.style) ? deepMergeStyle(themed, input.style) : themed;
|
|
243
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
244
|
+
}
|
|
245
|
+
/* ---- aibox'ın komponente özgü uzantısı (aibox-style-logic.ts kopyası) ---- */
|
|
246
|
+
export const HEADER_SECTION_SELECTOR = '& .ng-box-section--header';
|
|
247
|
+
export const FOOTER_SECTION_SELECTOR = '& .ng-box-section--footer';
|
|
248
|
+
const composeAiBoxSections = (input, root) => {
|
|
249
|
+
if (!isPlainObject(input.sectionStyle))
|
|
250
|
+
return;
|
|
251
|
+
const section = input.sectionStyle;
|
|
252
|
+
const color = section.dividerColor || DEFAULT_DIVIDER_COLOR;
|
|
253
|
+
const padding = asDimension(section.sectionPadding);
|
|
254
|
+
const header = {};
|
|
255
|
+
const footer = {};
|
|
256
|
+
// `false` "yok say" değil "ayracı kaldır" demektir.
|
|
257
|
+
if (isSet(section.headerDivider))
|
|
258
|
+
header.borderBottom = section.headerDivider ? `1px solid ${color}` : 'none';
|
|
259
|
+
if (isSet(section.footerDivider))
|
|
260
|
+
footer.borderTop = section.footerDivider ? `1px solid ${color}` : 'none';
|
|
261
|
+
if (padding !== undefined) {
|
|
262
|
+
header.padding = padding;
|
|
263
|
+
footer.padding = padding;
|
|
264
|
+
}
|
|
265
|
+
nest(root, HEADER_SECTION_SELECTOR, header);
|
|
266
|
+
nest(root, FOOTER_SECTION_SELECTOR, footer);
|
|
267
|
+
};
|
|
268
|
+
/* ---- text: emekli prop takma adı (frontend `text-style-logic.ts` kopyası) ---- */
|
|
269
|
+
/** Semantik renk → tema token'ı (`shared/utils.ts` STATUS_COLOR_VAR_MAP kopyası). */
|
|
270
|
+
const STATUS_COLOR_VARS = {
|
|
271
|
+
primary: '--ng-accent-primary',
|
|
272
|
+
info: '--ng-status-info',
|
|
273
|
+
success: '--ng-status-success',
|
|
274
|
+
warning: '--ng-status-warning',
|
|
275
|
+
error: '--ng-status-error'
|
|
276
|
+
};
|
|
277
|
+
const isRenderableColorLike = (value) => typeof value === 'string' && /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\(|var\(--)/i.test(value.trim());
|
|
278
|
+
/**
|
|
279
|
+
* `text`'in EMEKLİ prop'ları (`isBold` / `align` / `color`+`customColor`) kayıtlı
|
|
280
|
+
* sayfalarda aynı CSS'e iner — yalnız tipli alan BOŞSA; tasarımcı
|
|
281
|
+
* `typographyStyle`'da o alanı doldurduysa takma ad susar. Gruplardan sonra,
|
|
282
|
+
* tema birleşiminden önce koşar (tema tabanını ezer, K4).
|
|
283
|
+
*/
|
|
284
|
+
export const applyLegacyTextProps = (input, root) => {
|
|
285
|
+
if (root.fontWeight === undefined && input.isBold === true)
|
|
286
|
+
root.fontWeight = '600';
|
|
287
|
+
if (root.textAlign === undefined && isSet(input.align))
|
|
288
|
+
root.textAlign = input.align;
|
|
289
|
+
if (root.color === undefined && isSet(input.color)) {
|
|
290
|
+
const color = String(input.color);
|
|
291
|
+
root.color =
|
|
292
|
+
color === 'custom' && isRenderableColorLike(input.customColor)
|
|
293
|
+
? String(input.customColor).trim()
|
|
294
|
+
: `var(${STATUS_COLOR_VARS[color] ?? STATUS_COLOR_VARS.primary})`;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
/* ---- container: başlık bölgesi (frontend `container-style-logic.ts` kopyası) ---- */
|
|
298
|
+
const CONTAINER_HEADER_SELECTOR = '&__header';
|
|
299
|
+
const CONTAINER_TITLE_SELECTOR = '&__title';
|
|
300
|
+
/**
|
|
301
|
+
* `headerStyle` container'a özgü gruptur: başlık metninin rengi/puntosu/
|
|
302
|
+
* kalınlığı `&__title` parçasına, ayraç ve bölge dolgusu `&__header`
|
|
303
|
+
* parçasına iner. İkisi de GERÇEK elemandır (SectionHeader onları çizer).
|
|
304
|
+
*/
|
|
305
|
+
export const composeContainerHeader = (input, root) => {
|
|
306
|
+
const header = input.headerStyle;
|
|
307
|
+
if (!isPlainObject(header))
|
|
308
|
+
return;
|
|
309
|
+
const region = {};
|
|
310
|
+
const title = {};
|
|
311
|
+
if (isSet(header.divider)) {
|
|
312
|
+
region.borderBottom = header.divider ? `1px solid ${header.dividerColor || DEFAULT_DIVIDER_COLOR}` : 'none';
|
|
313
|
+
}
|
|
314
|
+
else if (isSet(header.dividerColor)) {
|
|
315
|
+
region.borderBottom = `1px solid ${header.dividerColor}`;
|
|
316
|
+
}
|
|
317
|
+
const padding = asDimension(header.padding);
|
|
318
|
+
if (padding !== undefined)
|
|
319
|
+
region.padding = padding;
|
|
320
|
+
const margin = asDimension(header.margin);
|
|
321
|
+
if (margin !== undefined)
|
|
322
|
+
region.margin = margin;
|
|
323
|
+
const minHeight = asDimension(header.minHeight);
|
|
324
|
+
if (minHeight !== undefined)
|
|
325
|
+
region.minHeight = minHeight;
|
|
326
|
+
if (isSet(header.align))
|
|
327
|
+
region.alignItems = header.align;
|
|
328
|
+
if (isSet(header.titleColor))
|
|
329
|
+
title.color = header.titleColor;
|
|
330
|
+
const size = asDimension(header.titleSize);
|
|
331
|
+
if (size !== undefined)
|
|
332
|
+
title.fontSize = size;
|
|
333
|
+
if (isSet(header.titleWeight))
|
|
334
|
+
title.fontWeight = String(header.titleWeight);
|
|
335
|
+
nest(root, CONTAINER_HEADER_SELECTOR, region);
|
|
336
|
+
nest(root, CONTAINER_TITLE_SELECTOR, title);
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Tipli stil komponentlerinin kaydı. Yeni bir komponent göçtüğünde buraya bir
|
|
340
|
+
* satır eklenir: hangi grupları taşıdığı, varsa komponente özgü uzantısı ve
|
|
341
|
+
* uzantının okuduğu EMEKLİ prop'lar (`legacyProps` — rapor bunları derleyiciye
|
|
342
|
+
* geçirir ki kayıtlı sayfanın takma adı `resolvedStyle`'da da görünsün).
|
|
343
|
+
* `resolveNodeStyleReport` tamamen bu kayıttan sürülür.
|
|
344
|
+
*/
|
|
345
|
+
/**
|
|
346
|
+
* statcard uzantısı — frontend `statcard-style-logic.ts`in aynası.
|
|
347
|
+
* `valueStyle` gerçek eleman sınıfını hedefler; hizalama ÜÇ kurala çevrilir
|
|
348
|
+
* (yalnız `text-align` yazmak kontrolü ölü gösterirdi).
|
|
349
|
+
*/
|
|
350
|
+
const STATCARD_FLEX_ALIGN = {
|
|
351
|
+
left: 'flex-start',
|
|
352
|
+
center: 'center',
|
|
353
|
+
right: 'flex-end',
|
|
354
|
+
justify: 'stretch'
|
|
355
|
+
};
|
|
356
|
+
function composeStatCardParts(input, root) {
|
|
357
|
+
const value = input.valueStyle;
|
|
358
|
+
if (isPlainObject(value)) {
|
|
359
|
+
const target = {};
|
|
360
|
+
const v = value;
|
|
361
|
+
if (isSet(v.color))
|
|
362
|
+
target.color = v.color;
|
|
363
|
+
if (isSet(v.fontSize))
|
|
364
|
+
target.fontSize = asDimension(v.fontSize);
|
|
365
|
+
if (isSet(v.fontWeight))
|
|
366
|
+
target.fontWeight = v.fontWeight;
|
|
367
|
+
nest(root, '& .ng-statcard__value', target);
|
|
368
|
+
}
|
|
369
|
+
const typography = isPlainObject(input.typographyStyle)
|
|
370
|
+
? input.typographyStyle
|
|
371
|
+
: undefined;
|
|
372
|
+
if (typography && isSet(typography.textAlign)) {
|
|
373
|
+
const flex = STATCARD_FLEX_ALIGN[String(typography.textAlign)];
|
|
374
|
+
if (flex) {
|
|
375
|
+
nest(root, '& .ng-statcard__main', { alignItems: flex });
|
|
376
|
+
nest(root, '& .ng-statcard__value-row', { justifyContent: flex });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
export const TYPED_STYLE_COMPONENTS = {
|
|
381
|
+
box: {
|
|
382
|
+
groups: ['surfaceStyle', 'typographyStyle', 'hoverStyle', 'sectionStyle'],
|
|
383
|
+
ext: { overflowTarget: '&__body', extra: composeAiBoxSections }
|
|
384
|
+
},
|
|
385
|
+
// `aibox` EMEKLİ (K5): tipli modeli box devraldı, kayıtlı düğümler box olarak
|
|
386
|
+
// çizilir. Kayıt burada kalır ki o düğümlerin resolvedStyle'ı doğru çıksın.
|
|
387
|
+
aibox: {
|
|
388
|
+
groups: ['surfaceStyle', 'typographyStyle', 'hoverStyle', 'sectionStyle'],
|
|
389
|
+
ext: { overflowTarget: '&__body', extra: composeAiBoxSections }
|
|
390
|
+
},
|
|
391
|
+
container: {
|
|
392
|
+
groups: ['surfaceStyle', 'typographyStyle', 'hoverStyle', 'headerStyle'],
|
|
393
|
+
ext: { overflowTarget: '&__body', extra: composeContainerHeader }
|
|
394
|
+
},
|
|
395
|
+
statcard: {
|
|
396
|
+
groups: ['surfaceStyle', 'typographyStyle', 'hoverStyle', 'valueStyle'],
|
|
397
|
+
ext: { extra: composeStatCardParts },
|
|
398
|
+
// Kartın kaydırılan gövdesi yok; taşma alanı ölü kural üretirdi.
|
|
399
|
+
omit: { surfaceStyle: ['contentOverflow'] }
|
|
400
|
+
},
|
|
401
|
+
text: {
|
|
402
|
+
groups: ['surfaceStyle', 'typographyStyle'],
|
|
403
|
+
ext: { extra: applyLegacyTextProps },
|
|
404
|
+
legacyProps: ['isBold', 'align', 'color', 'customColor'],
|
|
405
|
+
// Taşma hedefi olmayan komponentte contentOverflow ölü kural üretirdi.
|
|
406
|
+
omit: { surfaceStyle: ['contentOverflow'] }
|
|
407
|
+
},
|
|
408
|
+
icon: {
|
|
409
|
+
// Yalnız hover: surface çipin (chipShape/chipColor), typography da boyutun
|
|
410
|
+
// (size) ve rengin (color) çift kanalı olurdu — görsel kararlar semantik
|
|
411
|
+
// prop'larda yaşar (gerekçe komponent şemasında).
|
|
412
|
+
groups: ['hoverStyle'],
|
|
413
|
+
// Kökte kenarlık yok; hover'ın borderColor alanı ölü kural üretirdi.
|
|
414
|
+
omit: { hoverStyle: ['borderColor'] }
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
/** text derlemesi — testler ve dış tüketiciler için sarmalayıcı. */
|
|
418
|
+
export function composeTextStyle(input) {
|
|
419
|
+
return composeTypedStyle(input, TYPED_STYLE_COMPONENTS.text.ext);
|
|
420
|
+
}
|
|
421
|
+
/** aibox derlemesi — testler ve dış tüketiciler için sarmalayıcı. */
|
|
422
|
+
export function composeAiBoxStyle(input) {
|
|
423
|
+
return composeTypedStyle(input, TYPED_STYLE_COMPONENTS.aibox.ext);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Prop zarfını HER SEVİYEDE açar.
|
|
427
|
+
*
|
|
428
|
+
* Platform yalnız üst seviyeyi değil, bir prop'un `data`'sının İÇİNİ de
|
|
429
|
+
* `{ type, data }` zarfıyla saklar. Yalnız üst seviyeyi açmak
|
|
430
|
+
* `surfaceStyle.borderColor`'ı ham nesne bırakır ve derleme
|
|
431
|
+
* `1px solid [object Object]` üretir (gerçek sayfada ölçüldü). Frontend'de
|
|
432
|
+
* aynı işi `mapSchemaPropToRawProp` yapar.
|
|
433
|
+
*/
|
|
434
|
+
const unwrapProp = (value) => {
|
|
435
|
+
if (Array.isArray(value))
|
|
436
|
+
return value.map(unwrapProp);
|
|
437
|
+
if (!isPlainObject(value))
|
|
438
|
+
return value;
|
|
439
|
+
if ('data' in value && ('type' in value || 'ref' in value)) {
|
|
440
|
+
return unwrapProp(value.data);
|
|
441
|
+
}
|
|
442
|
+
const result = {};
|
|
443
|
+
for (const [key, item] of Object.entries(value))
|
|
444
|
+
result[key] = unwrapProp(item);
|
|
445
|
+
return result;
|
|
446
|
+
};
|
|
447
|
+
const rawProp = (props, name) => {
|
|
448
|
+
const prop = props?.[name];
|
|
449
|
+
// İfadeye bağlı prop'un değeri çalışma anında oluşur, burada çözülemez.
|
|
450
|
+
return prop && typeof prop === 'object' && 'data' in prop ? unwrapProp(prop.data) : undefined;
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Bir düğümün efektif stilini ve hangi katmanların katkı verdiğini döner.
|
|
454
|
+
* Tipli komponentte K4 sırası (`composeTypedStyle`), diğerlerinde eski
|
|
455
|
+
* birleşik kaskad uygulanır.
|
|
456
|
+
*/
|
|
457
|
+
export function resolveNodeStyleReport(node, source) {
|
|
458
|
+
const props = node.props;
|
|
459
|
+
const ownStyle = rawProp(props, 'style');
|
|
460
|
+
const config = node.type ? TYPED_STYLE_COMPONENTS[node.type] : undefined;
|
|
461
|
+
const presetName = isPlainObject(ownStyle) ? ownStyle[STYLE_PRESET_KEY] : undefined;
|
|
462
|
+
const typedGroups = config ? config.groups.filter(name => isPlainObject(rawProp(props, name))) : [];
|
|
463
|
+
const layers = {
|
|
464
|
+
typedGroups,
|
|
465
|
+
componentStyle: Boolean(node.type && isPlainObject(source?.componentStyles?.[node.type])),
|
|
466
|
+
preset: typeof presetName === 'string' && presetName ? presetName : null,
|
|
467
|
+
own: isPlainObject(ownStyle) ? Object.keys(ownStyle).filter(key => key !== STYLE_PRESET_KEY).length > 0 : Boolean(ownStyle)
|
|
468
|
+
};
|
|
469
|
+
if (!config) {
|
|
470
|
+
// Kayıtsız tip: eski birleşik kaskad.
|
|
471
|
+
const cascaded = resolveNodeStyle(ownStyle, source, node.type);
|
|
472
|
+
return cascaded === undefined ? { styleLayers: layers } : { resolvedStyle: cascaded, styleLayers: layers };
|
|
473
|
+
}
|
|
474
|
+
// Tipli komponent gruplar boşken de tipli yoldan geçer: uzantı emekli
|
|
475
|
+
// prop'ları okuyabilir (text'in isBold/align/color takma adı) ve K4 sırası
|
|
476
|
+
// gruplar boşken birleşik kaskadla aynı sonucu verir.
|
|
477
|
+
const { theme, own } = resolveNodeStyleLayers(ownStyle, source, node.type);
|
|
478
|
+
const input = { themeStyle: theme, style: own };
|
|
479
|
+
for (const name of config.groups)
|
|
480
|
+
input[name] = rawProp(props, name);
|
|
481
|
+
for (const name of config.legacyProps ?? [])
|
|
482
|
+
input[name] = rawProp(props, name);
|
|
483
|
+
const composed = composeTypedStyle(input, config.ext);
|
|
484
|
+
return composed === undefined ? { styleLayers: layers } : { resolvedStyle: composed, styleLayers: layers };
|
|
485
|
+
}
|
|
@@ -187,6 +187,10 @@ function describeResult(result) {
|
|
|
187
187
|
: '';
|
|
188
188
|
if (call?.isError)
|
|
189
189
|
return { ok: false, summary: clip(text, 400) };
|
|
190
|
+
const runtimeErrors = call?.structuredContent?.runtimeErrors;
|
|
191
|
+
if (Array.isArray(runtimeErrors) && runtimeErrors.length > 0) {
|
|
192
|
+
return { ok: false, summary: `çalışma anı hatası (platform HTTP 200 döndü): ${clip(runtimeErrors.map(String).join(' | '), 400)}` };
|
|
193
|
+
}
|
|
190
194
|
const effect = summarizeEffect(text);
|
|
191
195
|
return { ok: true, summary: `${Buffer.byteLength(text, 'utf8')} B yanıt${effect ? ` · ${effect}` : ''}` };
|
|
192
196
|
}
|
package/dist/platform/http.js
CHANGED
|
@@ -76,6 +76,9 @@ export class Platform {
|
|
|
76
76
|
environment: this.resolveEnvironment(options.environment)
|
|
77
77
|
}, true);
|
|
78
78
|
}
|
|
79
|
+
async accessToken() {
|
|
80
|
+
return (await this.authorizationHeader()).replace(/^Bearer\s+/i, '');
|
|
81
|
+
}
|
|
79
82
|
async authorizationHeader() {
|
|
80
83
|
if (this.config.authMode === 'passthrough') {
|
|
81
84
|
const incoming = requestAuthStorage.getStore();
|
|
@@ -14,6 +14,75 @@ export function prune(value) {
|
|
|
14
14
|
}
|
|
15
15
|
return value;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Adımın kimliği bir seviye aşağıdadır: `StepOutputDto`'nun `Name` alanı YOKTUR, ad
|
|
19
|
+
* `steps[].step.name` içinde durur (`WorkflowPreviewDto.cs:40` → `WorkflowStepDto(Guid Id,
|
|
20
|
+
* string Name, StepType Type)`, satır 93). `hasError`/`errorCode`/`errorMessage` ise üst
|
|
21
|
+
* seviyededir (satır 43-45).
|
|
22
|
+
*/
|
|
23
|
+
function stepName(node) {
|
|
24
|
+
const nested = node.step;
|
|
25
|
+
if (nested && typeof nested === 'object') {
|
|
26
|
+
const name = nested.name;
|
|
27
|
+
if (typeof name === 'string' && name !== '')
|
|
28
|
+
return name;
|
|
29
|
+
}
|
|
30
|
+
return typeof node.name === 'string' && node.name !== '' ? node.name : '(adsız adım)';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* İç içe düğümler `hasError` TAŞIMAZ, hep bir `steps[]` dizisine geri döner:
|
|
34
|
+
* `children` elemanı `ChildStepOutputDto : WorkflowPreviewDto`'dur (`WorkflowPreviewDto.cs:51`
|
|
35
|
+
* + `:110`), yani kendi `hasError`/`children` alanı yok — gerçek yol
|
|
36
|
+
* `steps[].children[].steps[].hasError`. `automations` elemanı da `AutomationNodeDto`'dur ve
|
|
37
|
+
* hatası aynı biçimde `automations[].steps[]` altındadır (`WorkflowPreviewDto.cs:52`, `:135`).
|
|
38
|
+
*/
|
|
39
|
+
function collectStepErrors(nodes, into) {
|
|
40
|
+
if (!Array.isArray(nodes))
|
|
41
|
+
return;
|
|
42
|
+
for (const entry of nodes) {
|
|
43
|
+
if (!entry || typeof entry !== 'object')
|
|
44
|
+
continue;
|
|
45
|
+
const node = entry;
|
|
46
|
+
if (node.hasError === true) {
|
|
47
|
+
const code = typeof node.errorCode === 'string' && node.errorCode !== '' ? node.errorCode : 'bilinmeyen kod';
|
|
48
|
+
const message = typeof node.errorMessage === 'string' && node.errorMessage !== '' ? `: ${node.errorMessage}` : '';
|
|
49
|
+
into.push(`${stepName(node)} — ${code}${message}`);
|
|
50
|
+
}
|
|
51
|
+
collectStepErrors(node.steps, into);
|
|
52
|
+
collectStepErrors(node.children, into);
|
|
53
|
+
collectStepErrors(node.automations, into);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Patlayan bir preview/execute HTTP 200 döner; hata gövdenin içinde `steps[].hasError` alanındadır
|
|
58
|
+
* (WorkflowPreviewDto.cs:38-52). Yanıttaki `executionSuccess` tek başına yeterli değil: platform onu
|
|
59
|
+
* `Steps.LastOrDefault()?.HasError is false` ile hesaplar (WorkflowPreviewDto.cs:25), yani ortada
|
|
60
|
+
* patlayıp hata dalından dönen akış `true` üretir. Bu yüzden önce TÜM adımlar taranır.
|
|
61
|
+
*
|
|
62
|
+
* Adım taraması yalnız `WorkflowPreviewDto` döndüren uçları kapsar (workflow/automation preview).
|
|
63
|
+
* Microservice tool'ları `tenantRaw` kullanır ve `http.ts:242` 4xx/5xx'te fırlatmadan
|
|
64
|
+
* `{ httpStatus, body }` döner; `MicroserviceExecutionItem`'da `hasError` de `steps` de yoktur
|
|
65
|
+
* (MicroserviceExecutionItem.cs:3-17), yani HTTP 500 ile biten bir çağrı adım taramasına hiç
|
|
66
|
+
* yakalanmıyordu. Üst seviyedeki `httpStatus` yalnız o raw sarmalından gelir — platform gövdeleri
|
|
67
|
+
* bu adı hiç kullanmaz (DTO'lar `httpStatusCode` yazar), bu yüzden yanlış pozitif üretmez.
|
|
68
|
+
*/
|
|
69
|
+
export function runtimeErrorSummary(payload) {
|
|
70
|
+
if (!payload || typeof payload !== 'object')
|
|
71
|
+
return [];
|
|
72
|
+
const record = payload;
|
|
73
|
+
const body = (record.data && typeof record.data === 'object' && !Array.isArray(record.data) ? record.data : record);
|
|
74
|
+
const errors = [];
|
|
75
|
+
if (typeof record.httpStatus === 'number' && record.httpStatus >= 400) {
|
|
76
|
+
errors.push(`httpStatus: ${record.httpStatus} — çağrı ${record.httpStatus} ile bitti; gövde başarı çıktısı değil hata gövdesidir. Adım adım bakmak için microservice_debug kullanın.`);
|
|
77
|
+
}
|
|
78
|
+
const stepErrors = [];
|
|
79
|
+
collectStepErrors(body.steps, stepErrors);
|
|
80
|
+
if (stepErrors.length === 0 && body.executionSuccess === false) {
|
|
81
|
+
stepErrors.push('executionSuccess: false — akış başarısız bitti, adım listesinde hata işareti yok');
|
|
82
|
+
}
|
|
83
|
+
errors.push(...stepErrors);
|
|
84
|
+
return errors;
|
|
85
|
+
}
|
|
17
86
|
function findLongestArray(value, best) {
|
|
18
87
|
if (Array.isArray(value)) {
|
|
19
88
|
if (!best.array || value.length > best.array.length)
|
|
@@ -57,14 +126,21 @@ function asStructured(payload) {
|
|
|
57
126
|
return { data: payload };
|
|
58
127
|
}
|
|
59
128
|
export function toolResult(payload, config) {
|
|
60
|
-
const
|
|
129
|
+
const pruned = prune(payload);
|
|
130
|
+
const runtimeErrors = runtimeErrorSummary(pruned);
|
|
131
|
+
if (runtimeErrors.length > 0) {
|
|
132
|
+
logStep('akış hatası ✕', `${runtimeErrors.length} adım hata ile bitti — platform HTTP 200 döndürdüğü için çağrı başarılı görünüyordu`, runtimeErrors);
|
|
133
|
+
}
|
|
134
|
+
const { payload: bounded, truncated } = shrink(pruned, config.maxResponseBytes);
|
|
61
135
|
const structured = asStructured(bounded);
|
|
62
136
|
if (truncated) {
|
|
63
137
|
logStep('kırpma ✂', `yanıt ${config.maxResponseBytes} bayt sınırını aştı — en uzun liste yarıya indirilerek küçültüldü (MAX_RESPONSE_BYTES)`);
|
|
64
138
|
}
|
|
65
|
-
const enriched =
|
|
66
|
-
|
|
67
|
-
:
|
|
139
|
+
const enriched = {
|
|
140
|
+
...structured,
|
|
141
|
+
...(runtimeErrors.length > 0 ? { runtimeErrors } : {}),
|
|
142
|
+
...(truncated ? { truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' } : {})
|
|
143
|
+
};
|
|
68
144
|
return {
|
|
69
145
|
content: [{ type: 'text', text: JSON.stringify(enriched) }],
|
|
70
146
|
structuredContent: enriched
|