优化优先级

This commit is contained in:
2026-06-23 09:53:52 +08:00
parent 875eb21241
commit c3739b18eb
5 changed files with 287 additions and 94 deletions
+67 -64
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed, nextTick, watch } from 'vue';
import { usePromptStore, splitTokens, normalizeSymbols, parseDetailedToken, constructToken } from '../stores/promptStore';
import { usePromptStore, splitTokens, normalizeSymbols, parseDetailedToken, constructToken, toNumericForm, toBracketForm, formatWeight } from '../stores/promptStore';
import type { LangCode, PresetFolder } from '../types';
import NotificationToast from './NotificationToast.vue';
import TranslationPopup from './TranslationPopup.vue';
@@ -37,7 +37,13 @@ let rafId: number | null = null;
const DRAG_THRESHOLD = 3; // 像素阈值,避免误触
const editingIndex = ref<number | null>(null);
const presetName = ref('');
const selectedFolderId = ref<string>('');
// 跨页面切换保留所选文件夹(刷新后重置为默认)
const selectedFolderId = computed({
get: () => store.editorSelectedFolderId,
set: (v: string) => { store.editorSelectedFolderId = v; },
});
// 保存预设时是否同时标记为收藏
const saveAsFavorite = ref(false);
const viewMode = ref<'compact' | 'detail'>('compact');
const showPresetDropdown = ref(false);
const showTranslationPopup = ref(false);
@@ -73,9 +79,13 @@ function handleClickOutside(event: Event) {
onMounted(() => {
document.addEventListener('click', handleClickOutside);
// 仅首次进入时初始化为默认文件夹;后续页面切换保留用户上次的选择
if (!store.editorFolderInitialized) {
const defaultFolder = store.presetManagement?.settings?.defaultFolder;
if (defaultFolder) {
selectedFolderId.value = defaultFolder;
store.editorSelectedFolderId = defaultFolder;
}
store.editorFolderInitialized = true;
}
store.searchQuery = ''; // Reset search query to ensure all tags are shown
});
@@ -134,7 +144,10 @@ const suggestions = ref<string[]>([]);
const editSuggestions = ref<string[]>([]);
const editorInputRef = ref<InstanceType<typeof EditorInput> | null>(null);
const tokenMappingRef = ref<InstanceType<typeof TokenMappingPanel> | null>(null);
const priorityStyle = ref<'{}' | '()' | '[]' | '<>' | 'suffix'>('{}');
// 优先级模式开关:false = 括号嵌套,true = 数字权重后缀
const numericMode = ref(false);
// 括号嵌套时使用的括号样式(默认圆括号)
const bracketStyle = ref<'()' | '{}' | '[]' | '<>'>('()');
const priorityStep = ref(0.1);
function splitTokensLocal(txt: string): string[] {
return splitTokens(txt);
@@ -166,25 +179,38 @@ function roundToDecimals(v: number, decimals: number): number {
const m = Math.pow(10, decimals);
return Math.round(v * m) / m;
}
function adjustWeight(core: string, delta: number): string {
const idx = core.lastIndexOf(':');
let base = core;
let w: number | null = null;
if (idx > -1) {
const num = parseFloat(core.slice(idx + 1).trim());
if (!isNaN(num)) { base = core.slice(0, idx); w = num; }
}
const stepStr = String(priorityStep.value);
const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
const cur = w == null ? 1.0 : w;
let nw = cur + delta;
function stepDecimals(): number {
const s = String(priorityStep.value);
return s.includes('.') ? (s.split('.')[1]?.length ?? 0) : 0;
}
nw = roundToDecimals(nw, decimals);
// 数字权重模式:确保 () 容器,按步进调整显式权重(默认 1,即无后缀)
function adjustNumericWeight(token: string, deltaSteps: number): string {
const { core, weight, prefix, suffix } = parseDetailedToken(token);
if (prefix || suffix) return token;
const base = weight ?? 1;
let next = roundToDecimals(base + deltaSteps * priorityStep.value, stepDecimals());
if (next < 0) next = 0;
if (next === 1) return `(${core})`;
return `(${core}:${formatWeight(next)})`;
}
// If weight is 1, return base without suffix
if (nw === 1) return base;
// 括号嵌套模式:在最外层再套一层选定括号
function addBracketLayer(token: string): string {
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(token);
const newWrappers = [bracketStyle.value, ...wrappers];
return constructToken(core, weight, newWrappers, prefix, suffix);
}
return base + ':' + nw;
// 括号嵌套模式:去掉最外层括号;若移除后无 () 承载裸权重则一并清除
function removeBracketLayer(token: string): string {
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(token);
if (prefix || suffix) return token;
if (wrappers.length === 0) return core;
const newWrappers = wrappers.slice(1);
const hasParen = newWrappers.includes('()');
const keepWeight = weight !== undefined && weight !== 1 && hasParen ? weight : undefined;
return constructToken(core, keepWeight, newWrappers, prefix, suffix);
}
const text = ref('');
@@ -338,25 +364,11 @@ function replaceCnComma() { applyFullPrompt(normalizeSymbols(text.value)); }
function formatPrompt() { applyFullPrompt(normalizePromptLocal(text.value)); }
function unifyPriorityStyle() {
const tokens = splitTokens(text.value);
const processed = tokens.map(token => {
const { core, weight, wrappers } = parseDetailedToken(token);
let result = core;
let currentWrappers = [...wrappers];
if (weight !== undefined && weight !== 1) {
const lastWrapper = currentWrappers[currentWrappers.length - 1];
if (lastWrapper === '()') {
currentWrappers.pop();
}
const wStr = Number.isInteger(weight) ? weight.toString() : weight.toFixed(2).replace(/\.?0+$/, '');
result = `(${result}:${wStr})`;
}
return store.wrapToken(result, currentWrappers);
});
applyFullPrompt(processed.join(', '));
showNotification('已统一优先级样式', 'success');
const list = splitTokens(text.value).map(token =>
numericMode.value ? toNumericForm(token) : toBracketForm(token)
);
applyFullPrompt(list.join(', '));
showNotification(numericMode.value ? '已统一为数字权重' : '已统一为括号样式', 'success');
}
// 新增功能方法
@@ -416,34 +428,18 @@ function addWrapperToToken(index: number) {
const tokens = splitTokensLocal(text.value);
if (index < 0 || index >= tokens.length) return;
const token = tokens[index]!;
const parsed = store.parseTokenWrappers(token);
const core = parsed?.core ?? token;
const wrappers = parsed?.wrappers ?? [];
if (priorityStyle.value === 'suffix') {
const newCore = adjustWeight(core, +priorityStep.value);
tokens[index] = store.wrapToken(newCore, wrappers);
} else {
const newWrappers = [...wrappers, priorityStyle.value];
tokens[index] = store.wrapToken(core, newWrappers);
}
tokens[index] = numericMode.value ? adjustNumericWeight(token, +1) : addBracketLayer(token);
applyFullPrompt(tokens.join(', '));
showNotification('已添加优先级', 'success');
showNotification(numericMode.value ? '已提升权重' : '已添加优先级', 'success');
}
function removeWrapperFromToken(index: number) {
const tokens = splitTokensLocal(text.value);
if (index < 0 || index >= tokens.length) return;
const token = tokens[index]!;
const { core, wrappers } = store.parseTokenWrappers(token);
if (priorityStyle.value === 'suffix') {
const newCore = adjustWeight(core, -priorityStep.value);
tokens[index] = store.wrapToken(newCore, wrappers);
} else if (wrappers.length > 0) {
const newWrappers = wrappers.slice(0, -1);
tokens[index] = store.wrapToken(core, newWrappers);
}
tokens[index] = numericMode.value ? adjustNumericWeight(token, -1) : removeBracketLayer(token);
applyFullPrompt(tokens.join(', '));
showNotification('已调整优先级', 'success');
showNotification(numericMode.value ? '已降低权重' : '已移除优先级', 'success');
}
function getTokenWrapperInfo(token: string) {
@@ -784,10 +780,16 @@ function savePreset() {
type: 'positive',
content: store.promptText,
description: '从编辑器快速保存',
folderId: folderId
folderId: folderId,
isFavorite: saveAsFavorite.value
});
showNotification(`预设「${name}」已保存到预设管理`, 'success');
showNotification(
saveAsFavorite.value
? `预设「${name}」已保存并收藏`
: `预设「${name}」已保存到预设管理`,
'success'
);
presetName.value = '';
}
@@ -857,13 +859,14 @@ function isRemoveDisabled(token: string): boolean {
<div class="pe-root">
<EditorToolbar :languages="store.languages as LangCode[]" v-model:selected-lang="selectedLang"
v-model:preset-name="presetName" v-model:selected-folder-id="selectedFolderId"
v-model:save-as-favorite="saveAsFavorite"
v-model:show-preset-dropdown="showPresetDropdown" :folder-tree="folderTree" :flattened-folders="flattenedFolders"
@copy="copyLeft" @save-preset="savePreset" @preset-load="handlePresetLoad" @preset-save="handlePresetSave"
@preset-delete="handlePresetDelete" @preset-rename="handlePresetRename" />
<div class="pe-main">
<EditorInput ref="editorInputRef" v-model:text="text" v-model:priority-style="priorityStyle"
v-model:priority-step="priorityStep" :suggestions="suggestions"
<EditorInput ref="editorInputRef" v-model:text="text" v-model:numeric-mode="numericMode"
v-model:bracket-style="bracketStyle" v-model:priority-step="priorityStep" :suggestions="suggestions"
:get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
@update-suggestions="updateSuggestionsFromText" @copy="copyLeft" @replace-cn-comma="replaceCnComma"
@format-prompt="formatPrompt" @unify-priority="unifyPriorityStyle" @toggle-underscore="toggleUnderscoreSpace"
@@ -871,7 +874,7 @@ function isRemoveDisabled(token: string): boolean {
<TokenMappingPanel ref="tokenMappingRef" :tokens="tokens" :selected-lang="selectedLang"
v-model:view-mode="viewMode" :dragging-index="draggingIndex" :over-index="overIndex" :insert-side="insertSide"
:is-dragging="isDragging" :edit-suggestions="editSuggestions" :priority-style="priorityStyle"
:is-dragging="isDragging" :edit-suggestions="editSuggestions" :numeric-mode="numericMode"
:display-trans="displayTrans" :is-unmapped="isUnmapped" :get-token-wrapper-info="getTokenWrapperInfo"
:has-weight-suffix="hasWeightSuffix" :get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
@pointer-down="onPointerDown" @panel-dragover="handlePanelDragOver" @panel-dragleave="handlePanelDragLeave"
+93 -16
View File
@@ -6,14 +6,16 @@ import PromptQuickAdd from '../PromptQuickAdd.vue';
const props = defineProps<{
text: string;
suggestions: string[];
priorityStyle: '{}' | '()' | '[]' | '<>' | 'suffix';
numericMode: boolean;
bracketStyle: '()' | '{}' | '[]' | '<>';
priorityStep: number;
getSuggestions: (prefix: string, limit: number) => string[];
}>();
const emit = defineEmits<{
'update:text': [value: string];
'update:priorityStyle': [value: '{}' | '()' | '[]' | '<>' | 'suffix'];
'update:numericMode': [value: boolean];
'update:bracketStyle': [value: '()' | '{}' | '[]' | '<>'];
'update:priorityStep': [value: number];
'update-suggestions': [];
'copy': [];
@@ -33,9 +35,14 @@ const localText = computed({
set: (v: string) => emit('update:text', v),
});
const localPriorityStyle = computed({
get: () => props.priorityStyle,
set: (v: '{}' | '()' | '[]' | '<>' | 'suffix') => emit('update:priorityStyle', v),
const localNumericMode = computed({
get: () => props.numericMode,
set: (v: boolean) => emit('update:numericMode', v),
});
const localBracketStyle = computed({
get: () => props.bracketStyle,
set: (v: '()' | '{}' | '[]' | '<>') => emit('update:bracketStyle', v),
});
const localPriorityStep = computed({
@@ -204,24 +211,41 @@ defineExpose({
切换 _/空格
</button>
<div class="pe-priority-group">
<label class="pe-priority-label">优先级样式</label>
<select class="pe-priority-select" v-model="localPriorityStyle" title="选择新增优先级的样式">
<option value="{}">{}</option>
<option value="()">()</option>
<option value="[]">[]</option>
<option value="<>">&lt;&gt;</option>
<option value="suffix">后缀数字</option>
<button
type="button"
class="pe-mode-toggle"
:class="{ 'is-numeric': localNumericMode }"
role="switch"
:aria-checked="localNumericMode"
@click="localNumericMode = !localNumericMode"
:title="localNumericMode ? '当前:数字权重模式(点击切换为括号嵌套)' : '当前:括号嵌套模式(点击切换为数字权重)'"
>
<span class="pe-mode-opt" :class="{ active: !localNumericMode }">括号</span>
<span class="pe-mode-opt" :class="{ active: localNumericMode }">数字</span>
<span class="pe-mode-knob"></span>
</button>
<template v-if="!localNumericMode">
<label class="pe-priority-label">括号样式</label>
<select class="pe-priority-select" v-model="localBracketStyle" title="选择外套括号的样式">
<option value="()">( )</option>
<option value="{}">{ }</option>
<option value="[]">[ ]</option>
<option value="<>">&lt; &gt;</option>
</select>
<label class="pe-priority-label">后缀数字间隔</label>
</template>
<template v-else>
<label class="pe-priority-label">权重步进</label>
<input
type="number"
class="pe-priority-step"
v-model.number="localPriorityStep"
title="设置增减间隔"
title="设置每次加减的权重步进"
min="0.01"
step="0.01"
placeholder="1"
step="0.05"
placeholder="0.1"
/>
</template>
</div>
</div>
<ul class="pe-suggest" v-if="suggestions.length">
@@ -322,6 +346,59 @@ defineExpose({
flex-wrap: wrap;
}
/* 模式切换开关:括号 / 数字 */
.pe-mode-toggle {
position: relative;
display: inline-flex;
align-items: center;
padding: 0;
border: 1px solid var(--color-border);
border-radius: 999px;
background-color: var(--color-bg-secondary);
cursor: pointer;
overflow: hidden;
transition: all 0.2s ease;
user-select: none;
}
.pe-mode-toggle:hover {
border-color: var(--color-border-hover);
}
.pe-mode-opt {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
width: 3rem;
padding: 0.4rem 0;
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-text-secondary);
transition: color 0.25s ease;
}
.pe-mode-opt.active {
color: white;
}
.pe-mode-knob {
position: absolute;
top: 2px;
left: 2px;
width: calc(50% - 2px);
height: calc(100% - 4px);
border-radius: 999px;
background-color: var(--color-accent);
box-shadow: var(--shadow-sm);
transition: transform 0.25s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.pe-mode-toggle.is-numeric .pe-mode-knob {
transform: translateX(100%);
}
.pe-priority-label {
font-size: 0.875rem;
font-weight: 500;
+46
View File
@@ -3,12 +3,14 @@ import { computed } from 'vue';
import type { LangCode, PresetFolder } from '../../types';
import PresetDropdown from '../PresetDropdown.vue';
import FolderSelector from '../preset/FolderSelector.vue';
import IconHeart from '../icons/IconHeart.vue';
const props = defineProps<{
languages: LangCode[];
selectedLang: LangCode;
presetName: string;
selectedFolderId: string;
saveAsFavorite: boolean;
folderTree: any[];
flattenedFolders: any[];
showPresetDropdown: boolean;
@@ -18,6 +20,7 @@ const emit = defineEmits<{
'update:selectedLang': [value: LangCode];
'update:presetName': [value: string];
'update:selectedFolderId': [value: string];
'update:saveAsFavorite': [value: boolean];
'update:showPresetDropdown': [value: boolean];
'copy': [];
'save-preset': [];
@@ -42,6 +45,11 @@ const localSelectedFolderId = computed({
set: (v: string) => emit('update:selectedFolderId', v),
});
const localSaveAsFavorite = computed({
get: () => props.saveAsFavorite,
set: (v: boolean) => emit('update:saveAsFavorite', v),
});
const localShowPresetDropdown = computed({
get: () => props.showPresetDropdown,
set: (v: boolean) => emit('update:showPresetDropdown', v),
@@ -74,6 +82,16 @@ const localShowPresetDropdown = computed({
/>
</div>
<input class="pe-preset-name" placeholder="保存为预设名称" v-model="localPresetName" />
<button
type="button"
class="pe-fav-toggle"
:class="{ active: localSaveAsFavorite }"
@click="localSaveAsFavorite = !localSaveAsFavorite"
:title="localSaveAsFavorite ? '保存时收藏:已开启' : '保存时收藏:已关闭'"
:aria-pressed="localSaveAsFavorite"
>
<IconHeart :active="localSaveAsFavorite" width="16" height="16" />
</button>
<button @click="emit('save-preset')" title="保存当前提示词为预设">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" stroke="currentColor" stroke-width="2"/>
@@ -203,6 +221,34 @@ const localShowPresetDropdown = computed({
transition: all 0.2s ease;
}
.pe-fav-toggle {
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
width: 38px;
height: 38px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: var(--color-bg-primary);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.pe-fav-toggle:hover {
background-color: var(--color-bg-tertiary);
border-color: var(--color-border-hover);
color: #ec4899;
}
.pe-fav-toggle.active {
color: #ec4899;
border-color: #f9a8d4;
background-color: rgba(236, 72, 153, 0.08);
}
.pe-folder-select-wrapper {
width: 130px;
}
+6 -5
View File
@@ -12,7 +12,7 @@ const props = defineProps<{
insertSide: 'before' | 'after' | null;
isDragging: boolean;
editSuggestions: string[];
priorityStyle: '{}' | '()' | '[]' | '<>' | 'suffix';
numericMode: boolean;
displayTrans: (key: string) => string;
isUnmapped: (key: string) => boolean;
getTokenWrapperInfo: (token: string) => { wrapperCount: number };
@@ -160,6 +160,7 @@ function updateEditSuggestions() {
}
function isRemoveDisabled(token: string): boolean {
if (props.numericMode) return false;
const info = props.getTokenWrapperInfo(token);
return info.wrapperCount === 0 && !props.hasWeightSuffix(token);
}
@@ -260,7 +261,7 @@ defineExpose({
</span>
</div>
<div class="pe-token-controls-compact">
<button @click="emit('add-wrapper', i)" class="pe-add-wrapper-btn" :title="`添加优先级(样式:${priorityStyle}`">
<button @click="emit('add-wrapper', i)" class="pe-add-wrapper-btn" :title="numericMode ? '提升权重' : '添加优先级(外套括号)'">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
@@ -270,7 +271,7 @@ defineExpose({
<button
@click="emit('remove-wrapper', i)"
class="pe-remove-wrapper-btn"
title="移除优先级"
:title="numericMode ? '降低权重' : '移除优先级'"
:disabled="isRemoveDisabled(k)"
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -317,7 +318,7 @@ defineExpose({
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
<button @click="emit('add-wrapper', i)" class="pe-add-wrapper-detail-btn" :title="`添加优先级(样式:${priorityStyle}`">
<button @click="emit('add-wrapper', i)" class="pe-add-wrapper-detail-btn" :title="numericMode ? '提升权重' : '添加优先级(外套括号)'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
<line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
@@ -327,7 +328,7 @@ defineExpose({
<button
@click="emit('remove-wrapper', i)"
class="pe-remove-wrapper-detail-btn"
title="移除优先级"
:title="numericMode ? '降低权重' : '移除优先级'"
:disabled="isRemoveDisabled(k)"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+66
View File
@@ -56,6 +56,9 @@ export const usePromptStore = defineStore('promptStore', {
maxPresets: 1000
}
} as PresetManagement,
// 编辑器内的目标文件夹选择:跨页面切换保留,刷新后重置为默认(不参与持久化)
editorSelectedFolderId: '' as string,
editorFolderInitialized: false,
}),
getters: {
categories: (s) => s.dataset?.categories ?? [],
@@ -1415,3 +1418,66 @@ export function constructToken(core: string, weight: number | undefined, wrapper
return result;
}
// ===== 优先级 / 权重转换工具 =====
// () 每层的等效权重系数(沿用 A1111 约定 ×1.1)
const EMPHASIS_FACTOR = 1.1;
function roundTo(value: number, decimals: number): number {
const m = Math.pow(10, decimals);
return Math.round(value * m) / m;
}
// 将权重格式化为简洁字符串(去掉多余尾随 0,例如 1.10 -> 1.1
export function formatWeight(weight: number): string {
return String(parseFloat(weight.toFixed(3)));
}
// 是否为 <...> 形式(如 LoRA / 嵌入触发),统一时保持原样不破坏
function isAngleWrapped(token: string): boolean {
const t = normalizeSymbols(token).trim();
return t.startsWith('<') && t.endsWith('>');
}
// 计算 token 的有效数字权重:综合显式权重与括号层级
export function getEffectiveWeight(token: string): number {
const { weight, wrappers } = parseDetailedToken(token);
const extra = [...wrappers];
let w = 1;
if (weight !== undefined) {
w = weight;
// 显式权重会消耗最内层的一个 () 作为语法容器,不再计入额外强调
const idx = extra.lastIndexOf('()');
if (idx !== -1) extra.splice(idx, 1);
}
for (const wrap of extra) {
if (wrap === '()') w *= EMPHASIS_FACTOR;
else if (wrap === '[]') w /= EMPHASIS_FACTOR;
else if (wrap === '{}') w *= 1.05;
// <> 视为中性,不计入强调
}
return roundTo(w, 3);
}
// 统一为数字权重形式:(core:weight),权重为 1 时省略
export function toNumericForm(token: string): string {
if (isAngleWrapped(token)) return normalizeSymbols(token).trim();
const { core, prefix, suffix } = parseDetailedToken(token);
if (prefix || suffix) return constructToken(core, undefined, [], prefix, suffix);
const w = roundTo(getEffectiveWeight(token), 2);
if (w === 1) return core;
return `(${core}:${formatWeight(w)})`;
}
// 统一为括号嵌套形式:根据有效权重换算 () / [] 层数
export function toBracketForm(token: string): string {
if (isAngleWrapped(token)) return normalizeSymbols(token).trim();
const { core, prefix, suffix } = parseDetailedToken(token);
if (prefix || suffix) return constructToken(core, undefined, [], prefix, suffix);
const w = getEffectiveWeight(token);
const n = Math.round(Math.log(w) / Math.log(EMPHASIS_FACTOR));
if (!Number.isFinite(n) || n === 0) return core;
const style = n > 0 ? '()' : '[]';
const layers = Array.from({ length: Math.min(Math.abs(n), 8) }, () => style);
return constructToken(core, undefined, layers);
}