Release v0.4.0
This commit is contained in:
+318
-1
@@ -51,6 +51,10 @@ module.exports = Editor.Panel.define({
|
||||
<ui-checkbox id="sessionsInput"></ui-checkbox>
|
||||
MCP Sessions
|
||||
</label>
|
||||
<label class="checkbox-line">
|
||||
<ui-checkbox id="javascriptSafetyInput"></ui-checkbox>
|
||||
JavaScript Safety Checks
|
||||
</label>
|
||||
</div>
|
||||
<div id="updateStatus" class="client-status muted"></div>
|
||||
<p>Changes auto-save. Port/profile changes restart the server when needed.</p>
|
||||
@@ -64,6 +68,19 @@ module.exports = Editor.Panel.define({
|
||||
<ui-button id="useFullBtn">Full</ui-button>
|
||||
<ui-button id="useCustomBtn">Custom</ui-button>
|
||||
</div>
|
||||
<div class="profile-manager">
|
||||
<div class="row">
|
||||
<ui-input id="toolProfileNameInput" placeholder="Profile name"></ui-input>
|
||||
<ui-select id="savedToolProfileSelect"></ui-select>
|
||||
<ui-button id="saveToolProfileBtn">Save Profile</ui-button>
|
||||
<ui-button id="applyToolProfileBtn">Apply</ui-button>
|
||||
<ui-button id="deleteToolProfileBtn">Delete</ui-button>
|
||||
<ui-button id="exportToolProfilesBtn">Export</ui-button>
|
||||
<ui-button id="importToolProfilesBtn">Import</ui-button>
|
||||
</div>
|
||||
<ui-textarea id="toolProfileImportText"></ui-textarea>
|
||||
</div>
|
||||
<div id="categoryControls" class="category-controls"></div>
|
||||
<div class="tool-config-grid">
|
||||
<label>Enabled Categories <ui-textarea id="enabledCategoriesInput"></ui-textarea></label>
|
||||
<label>Disabled Categories <ui-textarea id="disabledCategoriesInput"></ui-textarea></label>
|
||||
@@ -180,6 +197,54 @@ module.exports = Editor.Panel.define({
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.profile-manager {
|
||||
margin-top: 8px;
|
||||
}
|
||||
#toolProfileNameInput {
|
||||
min-width: 150px;
|
||||
}
|
||||
#savedToolProfileSelect {
|
||||
min-width: 150px;
|
||||
}
|
||||
#toolProfileImportText {
|
||||
min-height: 54px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.category-controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.category-row {
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
background: rgba(0,0,0,0.10);
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
.category-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.category-name {
|
||||
color: var(--color-normal-contrast);
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
.category-count {
|
||||
color: var(--color-normal-contrast-weakest);
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.category-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -298,6 +363,9 @@ module.exports = Editor.Panel.define({
|
||||
.activity-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.category-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
`,
|
||||
$: {
|
||||
@@ -309,6 +377,7 @@ module.exports = Editor.Panel.define({
|
||||
portInput: '#portInput',
|
||||
profileSelect: '#profileSelect',
|
||||
sessionsInput: '#sessionsInput',
|
||||
javascriptSafetyInput: '#javascriptSafetyInput',
|
||||
restartBtn: '#restartBtn',
|
||||
copyUrlBtn: '#copyUrlBtn',
|
||||
copyHealthCurlBtn: '#copyHealthCurlBtn',
|
||||
@@ -319,6 +388,15 @@ module.exports = Editor.Panel.define({
|
||||
useCoreBtn: '#useCoreBtn',
|
||||
useFullBtn: '#useFullBtn',
|
||||
useCustomBtn: '#useCustomBtn',
|
||||
toolProfileNameInput: '#toolProfileNameInput',
|
||||
savedToolProfileSelect: '#savedToolProfileSelect',
|
||||
saveToolProfileBtn: '#saveToolProfileBtn',
|
||||
applyToolProfileBtn: '#applyToolProfileBtn',
|
||||
deleteToolProfileBtn: '#deleteToolProfileBtn',
|
||||
exportToolProfilesBtn: '#exportToolProfilesBtn',
|
||||
importToolProfilesBtn: '#importToolProfilesBtn',
|
||||
toolProfileImportText: '#toolProfileImportText',
|
||||
categoryControls: '#categoryControls',
|
||||
enabledCategoriesInput: '#enabledCategoriesInput',
|
||||
disabledCategoriesInput: '#disabledCategoriesInput',
|
||||
enabledToolsInput: '#enabledToolsInput',
|
||||
@@ -353,19 +431,23 @@ module.exports = Editor.Panel.define({
|
||||
const portText = status.portFallbackActive
|
||||
? ` | Port fallback: ${status.requestedPort} -> ${status.port}`
|
||||
: '';
|
||||
const attachText = status.attachedToExisting ? ' | Attached listener' : '';
|
||||
this.$.statusText.textContent =
|
||||
`${status.url || ''} | Project: ${status.projectName || ''} | Cocos ${status.cocosVersion || ''}${portText}`;
|
||||
`${status.url || ''} | Project: ${status.projectName || ''} | Cocos ${status.cocosVersion || ''}${portText}${attachText}`;
|
||||
|
||||
this.$.enabledInput.value = Boolean(isRunning || config.autostart);
|
||||
this.$.portInput.value = Number(config.port || status.port || 8765);
|
||||
this.$.profileSelect.value = config.toolProfile || status.toolProfile || 'core';
|
||||
this.$.sessionsInput.value = Boolean(config.enableSessions || status.enableSessions);
|
||||
this.$.javascriptSafetyInput.value = config.executeJavascriptSafetyChecks !== false;
|
||||
this.$.enabledCategoriesInput.value = this.formatList(config.enabledToolCategories);
|
||||
this.$.disabledCategoriesInput.value = this.formatList(config.disabledToolCategories);
|
||||
this.$.enabledToolsInput.value = this.formatList(config.enabledTools);
|
||||
this.$.disabledToolsInput.value = this.formatList(config.disabledTools);
|
||||
this.renderToolProfiles();
|
||||
this.renderUpdateStatus();
|
||||
this.renderToolSummary();
|
||||
this.renderCategoryControls();
|
||||
this.renderClientTargets();
|
||||
this.renderActivity();
|
||||
},
|
||||
@@ -373,11 +455,136 @@ module.exports = Editor.Panel.define({
|
||||
return Array.isArray(value) ? value.join('\n') : '';
|
||||
},
|
||||
parseList(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => String(item || '').trim()).filter(Boolean);
|
||||
}
|
||||
return String(value || '')
|
||||
.split(/[\n,]/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
normalizeToolProfile(profile) {
|
||||
const name = String(profile && profile.name || '').trim();
|
||||
if (!name) {
|
||||
throw new Error('Profile name is required.');
|
||||
}
|
||||
const mode = String(profile.toolProfile || 'core').toLowerCase();
|
||||
return {
|
||||
name: name.slice(0, 80),
|
||||
toolProfile: mode === 'full' || mode === 'custom' ? mode : 'core',
|
||||
enabledToolCategories: this.parseList(profile.enabledToolCategories).map((item) => item.toLowerCase()),
|
||||
disabledToolCategories: this.parseList(profile.disabledToolCategories).map((item) => item.toLowerCase()),
|
||||
enabledTools: this.parseList(profile.enabledTools),
|
||||
disabledTools: this.parseList(profile.disabledTools),
|
||||
updatedAt: profile.updatedAt || new Date().toISOString(),
|
||||
};
|
||||
},
|
||||
normalizeToolProfiles(value) {
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
(Array.isArray(value) ? value : []).forEach((profile) => {
|
||||
try {
|
||||
const normalized = this.normalizeToolProfile(profile);
|
||||
const key = normalized.name.toLowerCase();
|
||||
const existing = result.findIndex((item) => item.name.toLowerCase() === key);
|
||||
if (existing >= 0) {
|
||||
result[existing] = normalized;
|
||||
} else if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
result.push(normalized);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore malformed imported entries in the panel; backend normalization repeats this.
|
||||
}
|
||||
});
|
||||
return result.sort((left, right) => left.name.localeCompare(right.name));
|
||||
},
|
||||
currentToolProfileSnapshot(name) {
|
||||
return this.normalizeToolProfile({
|
||||
name,
|
||||
toolProfile: this.$.profileSelect.value || 'core',
|
||||
enabledToolCategories: this.parseList(this.$.enabledCategoriesInput.value).map((item) => item.toLowerCase()),
|
||||
disabledToolCategories: this.parseList(this.$.disabledCategoriesInput.value).map((item) => item.toLowerCase()),
|
||||
enabledTools: this.parseList(this.$.enabledToolsInput.value),
|
||||
disabledTools: this.parseList(this.$.disabledToolsInput.value),
|
||||
});
|
||||
},
|
||||
getSavedToolProfiles() {
|
||||
const config = this.state && this.state.config ? this.state.config : {};
|
||||
return this.normalizeToolProfiles(config.savedToolProfiles || []);
|
||||
},
|
||||
renderToolProfiles() {
|
||||
const config = this.state && this.state.config ? this.state.config : {};
|
||||
const profiles = this.getSavedToolProfiles();
|
||||
const selected = this.$.savedToolProfileSelect.value || config.activeToolProfileName || (profiles[0] && profiles[0].name) || '';
|
||||
this.$.savedToolProfileSelect.innerHTML = '';
|
||||
if (profiles.length) {
|
||||
profiles.forEach((profile) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = profile.name;
|
||||
option.textContent = profile.name;
|
||||
this.$.savedToolProfileSelect.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
const option = document.createElement('option');
|
||||
option.value = '';
|
||||
option.textContent = 'No saved profiles';
|
||||
this.$.savedToolProfileSelect.appendChild(option);
|
||||
}
|
||||
this.$.savedToolProfileSelect.value = selected;
|
||||
if (!this.$.toolProfileNameInput.value) {
|
||||
this.$.toolProfileNameInput.value = selected || config.activeToolProfileName || '';
|
||||
}
|
||||
},
|
||||
renderCategoryControls() {
|
||||
const catalog = (this.state && this.state.toolCatalog) || [];
|
||||
const groups = catalog.reduce((acc, tool) => {
|
||||
const category = tool.category || 'other';
|
||||
if (!acc[category]) {
|
||||
acc[category] = { total: 0, enabled: 0 };
|
||||
}
|
||||
acc[category].total += 1;
|
||||
if (tool.enabled) {
|
||||
acc[category].enabled += 1;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.$.categoryControls.innerHTML = '';
|
||||
Object.keys(groups).sort().forEach((category) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'category-row';
|
||||
|
||||
const heading = document.createElement('div');
|
||||
heading.className = 'category-heading';
|
||||
const name = document.createElement('div');
|
||||
name.className = 'category-name';
|
||||
name.textContent = category;
|
||||
const count = document.createElement('div');
|
||||
count.className = 'category-count';
|
||||
count.textContent = `${groups[category].enabled}/${groups[category].total}`;
|
||||
heading.appendChild(name);
|
||||
heading.appendChild(count);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'category-actions';
|
||||
[
|
||||
['enable', 'Enable'],
|
||||
['disable', 'Disable'],
|
||||
['clear', 'Clear'],
|
||||
].forEach(([mode, label]) => {
|
||||
const button = document.createElement('ui-button');
|
||||
button.textContent = label;
|
||||
button.dataset.category = category;
|
||||
button.dataset.mode = mode;
|
||||
actions.appendChild(button);
|
||||
});
|
||||
|
||||
row.appendChild(heading);
|
||||
row.appendChild(actions);
|
||||
this.$.categoryControls.appendChild(row);
|
||||
});
|
||||
},
|
||||
renderUpdateStatus() {
|
||||
const update = this.state && this.state.updateInfo;
|
||||
if (!update) {
|
||||
@@ -546,11 +753,103 @@ module.exports = Editor.Panel.define({
|
||||
enabledTools: this.parseList(this.$.enabledToolsInput.value),
|
||||
disabledTools: this.parseList(this.$.disabledToolsInput.value),
|
||||
enableSessions: Boolean(this.$.sessionsInput.value),
|
||||
executeJavascriptSafetyChecks: Boolean(this.$.javascriptSafetyInput.value),
|
||||
autostart: Boolean(this.$.enabledInput.value),
|
||||
maxInteractionLogEntries: this.state && this.state.config ? this.state.config.maxInteractionLogEntries : 50,
|
||||
lastClientTargetId: this.$.clientTargetSelect.value || 'claude_code',
|
||||
activeToolProfileName: this.$.toolProfileNameInput.value || '',
|
||||
savedToolProfiles: this.getSavedToolProfiles(),
|
||||
};
|
||||
},
|
||||
async saveCurrentToolProfile() {
|
||||
const name = this.$.toolProfileNameInput.value || this.$.savedToolProfileSelect.value;
|
||||
const snapshot = this.currentToolProfileSnapshot(name);
|
||||
const profiles = this.getSavedToolProfiles();
|
||||
const key = snapshot.name.toLowerCase();
|
||||
const existing = profiles.findIndex((profile) => profile.name.toLowerCase() === key);
|
||||
if (existing >= 0) {
|
||||
profiles[existing] = snapshot;
|
||||
} else {
|
||||
profiles.push(snapshot);
|
||||
}
|
||||
this.state.config.savedToolProfiles = this.normalizeToolProfiles(profiles);
|
||||
this.state.config.activeToolProfileName = snapshot.name;
|
||||
await this.persistConfig({ showOutput: true });
|
||||
},
|
||||
async applySavedToolProfile() {
|
||||
const name = this.$.savedToolProfileSelect.value;
|
||||
const profile = this.getSavedToolProfiles().find((item) => item.name === name);
|
||||
if (!profile) {
|
||||
this.showOutput('Select a saved profile first.');
|
||||
return;
|
||||
}
|
||||
this.$.profileSelect.value = profile.toolProfile;
|
||||
this.$.enabledCategoriesInput.value = this.formatList(profile.enabledToolCategories);
|
||||
this.$.disabledCategoriesInput.value = this.formatList(profile.disabledToolCategories);
|
||||
this.$.enabledToolsInput.value = this.formatList(profile.enabledTools);
|
||||
this.$.disabledToolsInput.value = this.formatList(profile.disabledTools);
|
||||
this.$.toolProfileNameInput.value = profile.name;
|
||||
this.state.config.activeToolProfileName = profile.name;
|
||||
await this.persistConfig({ showOutput: true });
|
||||
},
|
||||
async deleteSavedToolProfile() {
|
||||
const name = this.$.savedToolProfileSelect.value;
|
||||
if (!name) {
|
||||
this.showOutput('Select a saved profile first.');
|
||||
return;
|
||||
}
|
||||
this.state.config.savedToolProfiles = this.getSavedToolProfiles()
|
||||
.filter((profile) => profile.name !== name);
|
||||
if (this.state.config.activeToolProfileName === name) {
|
||||
this.state.config.activeToolProfileName = '';
|
||||
}
|
||||
this.$.toolProfileNameInput.value = '';
|
||||
await this.persistConfig({ showOutput: true });
|
||||
},
|
||||
exportSavedToolProfiles() {
|
||||
const payload = JSON.stringify({ version: 1, profiles: this.getSavedToolProfiles() }, null, 2);
|
||||
this.$.toolProfileImportText.value = payload;
|
||||
this.copyText(payload, 'Copied tool profiles to clipboard.');
|
||||
},
|
||||
async importSavedToolProfiles() {
|
||||
try {
|
||||
const payload = JSON.parse(this.$.toolProfileImportText.value || '{}');
|
||||
const incoming = Array.isArray(payload)
|
||||
? payload
|
||||
: Array.isArray(payload.profiles)
|
||||
? payload.profiles
|
||||
: [];
|
||||
if (!incoming.length) {
|
||||
throw new Error('No profiles found.');
|
||||
}
|
||||
this.state.config.savedToolProfiles = this.normalizeToolProfiles([
|
||||
...this.getSavedToolProfiles(),
|
||||
...incoming,
|
||||
]);
|
||||
await this.persistConfig({ showOutput: true });
|
||||
} catch (error) {
|
||||
this.showOutput(`Import profiles failed: ${error.message}`);
|
||||
}
|
||||
},
|
||||
async setCategoryExposure(category, mode) {
|
||||
const enabled = new Set(this.parseList(this.$.enabledCategoriesInput.value).map((item) => item.toLowerCase()));
|
||||
const disabled = new Set(this.parseList(this.$.disabledCategoriesInput.value).map((item) => item.toLowerCase()));
|
||||
const key = String(category || '').toLowerCase();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
enabled.delete(key);
|
||||
disabled.delete(key);
|
||||
if (mode === 'enable') {
|
||||
enabled.add(key);
|
||||
} else if (mode === 'disable') {
|
||||
disabled.add(key);
|
||||
}
|
||||
this.$.profileSelect.value = 'custom';
|
||||
this.$.enabledCategoriesInput.value = Array.from(enabled).sort().join('\n');
|
||||
this.$.disabledCategoriesInput.value = Array.from(disabled).sort().join('\n');
|
||||
await this.persistConfig({ showOutput: true });
|
||||
},
|
||||
async handleEnableToggle() {
|
||||
const shouldEnable = Boolean(this.$.enabledInput.value);
|
||||
const wasRunning = Boolean(this.state && this.state.status && this.state.status.running);
|
||||
@@ -587,6 +886,7 @@ module.exports = Editor.Panel.define({
|
||||
this.$.portInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.profileSelect.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.sessionsInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.javascriptSafetyInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.enabledCategoriesInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.disabledCategoriesInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
this.$.enabledToolsInput.addEventListener('change', () => this.persistConfig({ showOutput: true }));
|
||||
@@ -611,6 +911,23 @@ module.exports = Editor.Panel.define({
|
||||
this.$.profileSelect.value = 'custom';
|
||||
this.persistConfig({ showOutput: true });
|
||||
});
|
||||
this.$.saveToolProfileBtn.addEventListener('click', () => this.saveCurrentToolProfile());
|
||||
this.$.applyToolProfileBtn.addEventListener('click', () => this.applySavedToolProfile());
|
||||
this.$.deleteToolProfileBtn.addEventListener('click', () => this.deleteSavedToolProfile());
|
||||
this.$.exportToolProfilesBtn.addEventListener('click', () => this.exportSavedToolProfiles());
|
||||
this.$.importToolProfilesBtn.addEventListener('click', () => this.importSavedToolProfiles());
|
||||
this.$.savedToolProfileSelect.addEventListener('change', () => {
|
||||
this.$.toolProfileNameInput.value = this.$.savedToolProfileSelect.value || '';
|
||||
});
|
||||
this.$.categoryControls.addEventListener('click', (event) => {
|
||||
const target = event.target && typeof event.target.closest === 'function'
|
||||
? event.target.closest('ui-button')
|
||||
: event.target;
|
||||
if (!target || !target.dataset || !target.dataset.category) {
|
||||
return;
|
||||
}
|
||||
this.setCategoryExposure(target.dataset.category, target.dataset.mode);
|
||||
});
|
||||
this.$.clientTargetSelect.addEventListener('confirm', () => this.renderClientTargetStatus());
|
||||
this.$.clientTargetSelect.addEventListener('change', () => {
|
||||
this.renderClientTargetStatus();
|
||||
|
||||
Reference in New Issue
Block a user