Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion plugins/prompt-templates/computed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ function normalizePromptTemplateEntry(item) {
const updatedAt = typeof safe.updatedAt === 'string' ? safe.updatedAt : '';
const createdAt = typeof safe.createdAt === 'string' ? safe.createdAt : updatedAt;
const isBuiltin = safe.isBuiltin === true;
const createdBy = typeof safe.createdBy === 'string' ? safe.createdBy.trim() : '';
const maintainers = Array.isArray(safe.maintainers)
? safe.maintainers.map((m) => (typeof m === 'string' ? m.trim() : '')).filter(Boolean)
: [];
return {
id,
name,
description,
template,
createdAt,
updatedAt,
isBuiltin
isBuiltin,
createdBy,
maintainers
};
}

Expand Down Expand Up @@ -104,6 +110,30 @@ export function createPluginsComputed() {
return pluginsRegistry.map((entry) => entry && entry.meta).filter(Boolean);
},

pluginsActiveMeta() {
const id = typeof this.pluginsActiveId === 'string' ? this.pluginsActiveId.trim() : '';
const entry = pluginsRegistry.find((item) => item && item.id === id) || null;
return entry && entry.meta ? entry.meta : null;
},

pluginsActiveAttribution() {
const meta = this.pluginsActiveMeta;
if (!meta || typeof meta !== 'object') return '';
const createdBy = typeof meta.createdBy === 'string' ? meta.createdBy.trim() : '';
const maintainers = Array.isArray(meta.maintainers)
? meta.maintainers.map((m) => (typeof m === 'string' ? m.trim() : '')).filter(Boolean).join(', ')
: '';
if (!createdBy && !maintainers) return '';
if (typeof this.t !== 'function') {
if (createdBy && maintainers) return `Created by ${createdBy} · Maintained by ${maintainers}`;
if (createdBy) return `Created by ${createdBy}`;
return `Maintained by ${maintainers}`;
}
if (createdBy && maintainers) return this.t('plugins.meta.attribution', { createdBy, maintainers });
if (createdBy) return this.t('plugins.meta.createdBy', { createdBy });
return this.t('plugins.meta.maintainedBy', { maintainers });
},

promptTemplatesList() {
const list = Array.isArray(this.promptTemplatesListRaw) ? this.promptTemplatesListRaw : [];
return list
Expand Down
5 changes: 4 additions & 1 deletion plugins/prompt-templates/manifest.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { pluginOwnership } from './ownership.mjs';

export const pluginMeta = {
id: 'prompt-templates',
title: 'Prompt Templates',
description: 'Standardized, template-driven prompts with variables and copy/export helpers.',
statusLabel: 'standard',
tone: 'configured'
tone: 'configured',
...pluginOwnership
};
8 changes: 6 additions & 2 deletions plugins/prompt-templates/methods.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ function normalizePromptTemplateDraft(draft) {
template: typeof safe.template === 'string' ? safe.template : '',
createdAt: typeof safe.createdAt === 'string' ? safe.createdAt : '',
updatedAt: typeof safe.updatedAt === 'string' ? safe.updatedAt : '',
isBuiltin: safe.isBuiltin === true
isBuiltin: safe.isBuiltin === true,
createdBy: typeof safe.createdBy === 'string' ? safe.createdBy : '',
maintainers: Array.isArray(safe.maintainers) ? safe.maintainers : []
};
}

Expand Down Expand Up @@ -322,7 +324,9 @@ export function createPluginsMethods() {
template: entry.template,
createdAt: entry.createdAt,
updatedAt: entry.updatedAt,
isBuiltin: entry.isBuiltin === true
isBuiltin: entry.isBuiltin === true,
createdBy: entry.createdBy || '',
maintainers: Array.isArray(entry.maintainers) ? entry.maintainers : []
};
this.promptTemplateVarValuesRaw = {};
},
Expand Down
26 changes: 24 additions & 2 deletions plugins/prompt-templates/overview.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
persistPromptTemplatesToStorage,
readPromptTemplatesFromStorage
} from './storage.mjs';
import { pluginOwnership } from './ownership.mjs';

function nowIsoPromptTemplatesOverview() {
return new Date().toISOString();
Expand All @@ -21,7 +22,25 @@ function buildBuiltinCommentPolishTemplate(t) {
].join('\n'),
createdAt: nowIsoPromptTemplatesOverview(),
updatedAt: nowIsoPromptTemplatesOverview(),
isBuiltin: true
isBuiltin: true,
createdBy: pluginOwnership.createdBy,
maintainers: pluginOwnership.maintainers
};
}

function buildBuiltinRuleAckTemplate(t) {
const tr = (key, fallback, params = null) => (typeof t === 'function' ? t(key, params) : fallback);
const line1 = tr('plugins.builtin.ruleAck.line1', '请根据【{{rule}}】,收到请回复');
return {
id: 'builtin_rule_ack',
name: tr('plugins.builtin.ruleAck.name', '规则确认回复'),
description: tr('plugins.builtin.ruleAck.desc', '请根据【{{rule}}】,收到请回复'),
template: line1,
createdAt: nowIsoPromptTemplatesOverview(),
updatedAt: nowIsoPromptTemplatesOverview(),
isBuiltin: true,
createdBy: pluginOwnership.createdBy,
maintainers: pluginOwnership.maintainers
};
}

Expand All @@ -48,7 +67,10 @@ export async function loadPromptTemplatesOverview(ctx, options = {}) {

const t = typeof app.t === 'function' ? app.t : null;
const rawList = readPromptTemplatesFromStorage(localStorage);
const normalized = ensureBuiltinTemplates(rawList, [buildBuiltinCommentPolishTemplate(t)]);
const normalized = ensureBuiltinTemplates(rawList, [
buildBuiltinCommentPolishTemplate(t),
buildBuiltinRuleAckTemplate(t)
]);
app.promptTemplatesListRaw = normalized;
persistPromptTemplatesToStorage(normalized, localStorage);

Expand Down
4 changes: 4 additions & 0 deletions plugins/prompt-templates/ownership.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const pluginOwnership = {
createdBy: 'ymkiux',
maintainers: ['ymkiux']
};
2 changes: 2 additions & 0 deletions tests/unit/web-ui-behavior-parity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'mainTabSubtitle',
'configTemplateDiffHasChanges',
'pluginsCatalog',
'pluginsActiveMeta',
'pluginsActiveAttribution',
'promptTemplatesList',
'filteredPromptTemplates',
'promptTemplateDraft',
Expand Down
32 changes: 4 additions & 28 deletions tools/dev/reset-main.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
#!/usr/bin/env node
/**
* Interactive reset workflow:
* - blank input => reset working tree to origin/main
* - PR number => fetch PR snapshot and reset local branch to that PR head
* - always discard local changes, clean untracked files/dirs, and validate
* the final branch / commit / clean-tree state
*
* Cross-platform: requires Node.js and git in PATH.
* Reset workflow:
* - no args => reset working tree to origin/main
* - PR number arg => fetch PR snapshot and reset local branch to that PR head
*/

const { execSync } = require('child_process');
const readline = require('readline');

const DEFAULT_REMOTE = 'origin';
const DEFAULT_MAIN_BRANCH = 'main';
Expand Down Expand Up @@ -140,25 +135,7 @@ function resolveArgPrNumber(argv = process.argv.slice(2)) {
return normalizePrNumberInput(first || '');
}

function promptForPrNumber({ stdin = process.stdin, stdout = process.stdout } = {}) {
if (!stdin || !stdout || stdin.isTTY === false || stdout.isTTY === false) {
return Promise.resolve('');
}

return new Promise((resolve, reject) => {
const rl = readline.createInterface({ input: stdin, output: stdout });
rl.question('PR 编号(留空则重置到 origin/main): ', (answer) => {
rl.close();
try {
resolve(normalizePrNumberInput(answer));
} catch (err) {
reject(err);
}
});
});
}

async function main({ argv = process.argv.slice(2), stdin = process.stdin, stdout = process.stdout } = {}) {
async function main({ argv = process.argv.slice(2) } = {}) {
try {
run('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
} catch (err) {
Expand Down Expand Up @@ -187,6 +164,5 @@ module.exports = {
validateFinalState,
executeResetPlan,
resolveArgPrNumber,
promptForPrNumber,
main
};
14 changes: 14 additions & 0 deletions web-ui/modules/i18n.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,17 @@ const DICT = Object.freeze({
'plugins.promptTemplates.varModal.cancel': '取消',
'plugins.promptTemplates.varModal.add': '添加',

'plugins.meta.attribution': '创建者:{createdBy} · 维护者:{maintainers}',
'plugins.meta.createdBy': '创建者:{createdBy}',
'plugins.meta.maintainedBy': '维护者:{maintainers}',

// Built-in prompt templates
'plugins.builtin.commentPolish.name': '代码注释润色',
'plugins.builtin.commentPolish.desc': '轻微收敛以下代码注释 {{code}}',
'plugins.builtin.commentPolish.line1': '轻微收敛以下代码注释',
'plugins.builtin.ruleAck.name': '规则确认回复',
'plugins.builtin.ruleAck.desc': '请根据【{{rule}}】,收到请回复',
'plugins.builtin.ruleAck.line1': '请根据【{{rule}}】,收到请回复',

// Toasts
'toast.copy.empty': '没有可复制内容',
Expand Down Expand Up @@ -1160,10 +1167,17 @@ const DICT = Object.freeze({
'plugins.promptTemplates.varModal.cancel': 'Cancel',
'plugins.promptTemplates.varModal.add': 'Add',

'plugins.meta.attribution': 'Created by {createdBy} · Maintained by {maintainers}',
'plugins.meta.createdBy': 'Created by {createdBy}',
'plugins.meta.maintainedBy': 'Maintained by {maintainers}',

// Built-in prompt templates
'plugins.builtin.commentPolish.name': 'Comment polish',
'plugins.builtin.commentPolish.desc': 'Polish the following code comments {{code}}',
'plugins.builtin.commentPolish.line1': 'Polish the following code comments',
'plugins.builtin.ruleAck.name': 'Rule acknowledgement',
'plugins.builtin.ruleAck.desc': 'Please follow 【{{rule}}】, reply when received',
'plugins.builtin.ruleAck.line1': 'Please follow 【{{rule}}】, reply when received',

// Toasts
'toast.copy.empty': 'Nothing to copy',
Expand Down
7 changes: 7 additions & 0 deletions web-ui/partials/index/panel-plugins.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<div v-else-if="pluginsActiveId === 'prompt-templates'" class="plugins-panel">
<div class="plugins-panel-head">
<div class="plugins-panel-title">{{ t('plugins.promptTemplates.title') }}</div>
<div v-if="pluginsActiveAttribution" class="plugins-panel-note">{{ pluginsActiveAttribution }}</div>
</div>

<div class="prompt-templates-modebar" role="tablist" :aria-label="t('plugins.promptTemplates.mode.aria')">
Expand All @@ -67,6 +68,9 @@
<div class="prompt-compose-selected">
<div class="prompt-compose-selected-title">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.name) ? promptComposerActiveTemplate.name : t('plugins.promptTemplates.compose.chooseTemplate') }}</div>
<div class="prompt-compose-selected-meta">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.description) ? promptComposerActiveTemplate.description : t('plugins.promptTemplates.compose.chooseTemplateHint') }}</div>
<div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
{{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy || '', maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}
</div>
Comment on lines +71 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Attribution string renders awkwardly when only one of createdBy/maintainers is present.

The v-if allows rendering when either createdBy or maintainers.length is truthy, but the binding always uses plugins.meta.attribution ("Created by {createdBy} · Maintained by {maintainers}"). If a built-in template has only maintainers (or only a creator), the UI will display things like Created by · Maintained by ymkiux.

The computed pluginsActiveAttribution in plugins/prompt-templates/computed.mjs (lines 132-134) already handles this correctly by selecting between plugins.meta.attribution, plugins.meta.createdBy, and plugins.meta.maintainedBy. These inline template bindings should mirror that logic, or — simpler — expose a per-template attribution computed helper and bind it here (and at lines 189-191) to avoid duplicating the rendering rules.

♻️ Sketch of an inline fix (duplicated in both locations)
-                                            <div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
-                                                {{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy || '', maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}
-                                            </div>
+                                            <div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
+                                                <template v-if="promptComposerActiveTemplate.createdBy && promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length">{{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy, maintainers: promptComposerActiveTemplate.maintainers.join(', ') }) }}</template>
+                                                <template v-else-if="promptComposerActiveTemplate.createdBy">{{ t('plugins.meta.createdBy', { createdBy: promptComposerActiveTemplate.createdBy }) }}</template>
+                                                <template v-else>{{ t('plugins.meta.maintainedBy', { maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}</template>
+                                            </div>

A cleaner alternative is to add a computed promptComposerActiveAttribution / promptTemplateDraftAttribution that returns the resolved string (same shape as pluginsActiveAttribution) and bind {{ ... }} to it here.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
{{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy || '', maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}
</div>
<div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
<template v-if="promptComposerActiveTemplate.createdBy && promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length">{{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy, maintainers: promptComposerActiveTemplate.maintainers.join(', ') }) }}</template>
<template v-else-if="promptComposerActiveTemplate.createdBy">{{ t('plugins.meta.createdBy', { createdBy: promptComposerActiveTemplate.createdBy }) }}</template>
<template v-else>{{ t('plugins.meta.maintainedBy', { maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}</template>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/partials/index/panel-plugins.html` around lines 71 - 73, The inline
attribution binding currently always uses t('plugins.meta.attribution') and
renders awkwardly when only createdBy or maintainers exists; replace that inline
logic by exposing and using a resolved attribution computed property (e.g.,
promptComposerActiveAttribution) that mirrors the logic in
pluginsActiveAttribution (choose between plugins.meta.attribution,
plugins.meta.createdBy, or plugins.meta.maintainedBy based on which fields are
present) and bind the template to that computed property instead of the
hardcoded t('plugins.meta.attribution') string; also apply the same replacement
at the other occurrence (the block around lines referenced as 189-191) so both
places share the same computed helper.

</div>

<div class="prompt-compose-form">
Expand Down Expand Up @@ -182,6 +186,9 @@
<div v-if="promptTemplateDraft.isBuiltin" class="plugins-panel-note">
{{ t('plugins.promptTemplates.editor.builtinReadOnly') }}
</div>
<div v-if="promptTemplateDraft.isBuiltin && (promptTemplateDraft.createdBy || (promptTemplateDraft.maintainers && promptTemplateDraft.maintainers.length))" class="plugins-panel-note">
{{ t('plugins.meta.attribution', { createdBy: promptTemplateDraft.createdBy || '', maintainers: (promptTemplateDraft.maintainers || []).join(', ') }) }}
</div>
<input class="form-input" type="text" v-model.trim="promptTemplateDraft.description" :disabled="promptTemplateDraft.isBuiltin" :placeholder="t('plugins.promptTemplates.editor.descPlaceholder')" :aria-label="t('plugins.promptTemplates.editor.descAria')">
</div>

Expand Down
Loading