feat: 优化内容模板变量引用与换行展示

- 内容模板直接引用上游参数并自动维护运行参数

- 支持 Enjoy 变量标签编辑及中文变量显示

- 保留试运行与聊天运行页的模板输出换行
This commit is contained in:
2026-08-11 21:41:58 +08:00
parent fe41d62b8a
commit 765006747a
12 changed files with 419 additions and 67 deletions

View File

@@ -37,7 +37,22 @@ export interface ParameterCandidate {
dataType?: string;
}
const TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;
export type TokenSyntax = 'enjoy' | 'mustache';
const MUSTACHE_TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;
const ENJOY_TOKEN_PATTERN =
/#\(\s*([A-Za-z_$\u4E00-\u9FA5][A-Za-z0-9_$\u4E00-\u9FA5]*(?:\.[A-Za-z_$\u4E00-\u9FA5][A-Za-z0-9_$\u4E00-\u9FA5]*)*)\s*\)/g;
function getTokenPattern(syntax: TokenSyntax): RegExp {
return syntax === 'enjoy' ? ENJOY_TOKEN_PATTERN : MUSTACHE_TOKEN_PATTERN;
}
export function formatParamToken(
tokenKey: string,
syntax: TokenSyntax = 'mustache',
): string {
return syntax === 'enjoy' ? `#(${tokenKey})` : `{{${tokenKey}}}`;
}
export function normalizeTokenKey(tokenKey: string): string {
return tokenKey.trim();
@@ -156,14 +171,16 @@ export function flattenParameterCandidates(
export function parseTokenParts(
content: string,
validParams: string[] = [],
syntax: TokenSyntax = 'mustache',
): TokenPart[] {
const source = content ?? '';
const validSet = new Set(validParams.map(normalizeTokenKey));
const parts: TokenPart[] = [];
const tokenPattern = getTokenPattern(syntax);
let lastIndex = 0;
TOKEN_PATTERN.lastIndex = 0;
let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);
tokenPattern.lastIndex = 0;
let match: RegExpExecArray | null = tokenPattern.exec(source);
while (match) {
if (match.index > lastIndex) {
@@ -183,7 +200,7 @@ export function parseTokenParts(
});
lastIndex = match.index + rawToken.length;
match = TOKEN_PATTERN.exec(source);
match = tokenPattern.exec(source);
}
if (lastIndex < source.length) {
@@ -203,11 +220,15 @@ export function parseTokenParts(
return parts;
}
export function getTokenRanges(content: string): TokenRange[] {
export function getTokenRanges(
content: string,
syntax: TokenSyntax = 'mustache',
): TokenRange[] {
const source = content ?? '';
const ranges: TokenRange[] = [];
TOKEN_PATTERN.lastIndex = 0;
let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);
const tokenPattern = getTokenPattern(syntax);
tokenPattern.lastIndex = 0;
let match: RegExpExecArray | null = tokenPattern.exec(source);
while (match) {
const rawToken = match[0];
@@ -217,7 +238,7 @@ export function getTokenRanges(content: string): TokenRange[] {
text: rawToken,
key: normalizeTokenKey(match[1] || ''),
});
match = TOKEN_PATTERN.exec(source);
match = tokenPattern.exec(source);
}
return ranges;
@@ -229,6 +250,7 @@ export function findTokenRangeAtCursor(
options?: {
includeStart?: boolean;
includeEnd?: boolean;
syntax?: TokenSyntax;
},
): TokenRange | null {
if (!Number.isInteger(cursor) || cursor < 0) {
@@ -237,7 +259,7 @@ export function findTokenRangeAtCursor(
const includeStart = options?.includeStart ?? false;
const includeEnd = options?.includeEnd ?? false;
const ranges = getTokenRanges(content);
const ranges = getTokenRanges(content, options?.syntax);
for (const range of ranges) {
const leftValid = includeStart
? cursor >= range.start
@@ -254,10 +276,12 @@ export function findTokenRangeAtCursor(
export function findBackspaceTokenRange(
content: string,
cursor: number,
syntax: TokenSyntax = 'mustache',
): TokenRange | null {
return findTokenRangeAtCursor(content, cursor, {
includeStart: false,
includeEnd: true,
syntax,
});
}