feat: release 2.3.0 with workspace plugin extensions

This commit is contained in:
chenmingxuan committed 2026-10-27 21:38:43 +08:00
1 parent 8380057548
commit bb873ec0e0
44 files changed
+1497 -48

No files matched your search

@@ -0,0 +1,99 @@
@if (enabled() && error()) {
<div class="extension-notice" role="status">{{ error() }} <button type="button" (click)="refresh()">重试</button></div>
}
<ng-template #headerControls>
@if (data()?.plugins?.length) {
<nav class="panel-toggles" aria-label="任务扩展面板">
@for (side of ['left', 'right']; track side) {
@for (plugin of data()!.plugins; track plugin.plugin_id) {
@for (panel of plugin.workspace.panels; track panel.id) {
@if (panel.side === side) {
<button type="button" class="panel-toggle" [class.active]="opened()[key(plugin, panel)]" [attr.aria-expanded]="!!opened()[key(plugin, panel)]"
[attr.aria-label]="(opened()[key(plugin, panel)] ? '收起' : '展开') + panel.title"
[title]="(opened()[key(plugin, panel)] ? '收起' : '展开') + panel.title"
[attr.aria-controls]="'extension-' + key(plugin, panel)" (click)="toggle(plugin, panel)">
<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2" y="3" width="16" height="14" rx="3" /><path [attr.d]="side === 'left' ? 'M7 3v14' : 'M13 3v14'" /></svg>
<span class="panel-toggle-label">{{ panel.title }}</span>
<span class="material-symbols-outlined" aria-hidden="true">{{ (side === 'left') !== !!opened()[key(plugin, panel)] ? 'chevron_left' : 'chevron_right' }}</span>
</button>
}
}
}
}
</nav>
}
</ng-template>
<div class="extension-layout" [class.has-left]="panels('left').length" [class.has-right]="panels('right').length"
[style.--left-width]="width('left') + 'px'" [style.--right-width]="width('right') + 'px'">
@if (panels('left').length) {
<aside class="panel-column left-column" aria-label="任务左侧扩展">
@for (item of panels('left'); track key(item.plugin, item.panel)) {
<ng-container *ngTemplateOutlet="panelContent; context: { plugin: item.plugin, panel: item.panel }" />
}
</aside>
}
<div class="player-content"><ng-content /></div>
@if (panels('right').length) {
<aside class="panel-column right-column" aria-label="任务右侧扩展">
@for (item of panels('right'); track key(item.plugin, item.panel)) {
<ng-container *ngTemplateOutlet="panelContent; context: { plugin: item.plugin, panel: item.panel }" />
}
</aside>
}
</div>
<ng-template #panelContent let-plugin="plugin" let-panel="panel">
<section [id]="'extension-' + key(plugin, panel)" [attr.aria-label]="panel.title">
<header><h3>{{ panel.title }}</h3><span class="panel-kind">{{ panel.kind === 'initial_prompt' ? '原始提示词' : '人工填写 · 选填' }}</span></header>
@if (panel.kind === 'initial_prompt') {
@if (data()?.initial_prompt) {
<app-report-markdown [content]="data()!.initial_prompt" />
} @else { <p class="muted">该任务未记录初始需求提示词。</p> }
} @else {
@if (!data()?.editable) { <p class="muted">任务结束后可填写评审。</p> }
<fieldset [disabled]="!data()?.editable || !!saving()">
@for (field of plugin.workspace.fields; track field.id) {
<div class="field" [class.choice-field]="field.kind === 'choice'" [class.note-field]="field.kind === 'text'">
<span class="field-label">{{ field.label }}</span>
@if (field.description) { <span class="muted">{{ field.description }}</span> }
@if (field.kind === 'choice') {
<div class="choice-group" role="group" [attr.aria-label]="field.label">
@for (option of field.options; track option) {
<button type="button" class="choice-button" [class.selected]="drafts[plugin.plugin_id].values[field.id] === option"
[class.pass]="option === 'Pass' || option === '通过'" [class.failed]="option === 'Failed' || option === '失败'"
[attr.aria-pressed]="drafts[plugin.plugin_id].values[field.id] === option" title="再次点击已选项可清除"
(click)="choose(plugin.plugin_id, field.id, option)">
@if (option === 'Pass' || option === 'Failed' || option === '通过' || option === '失败') {
<svg class="choice-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path [attr.d]="option === 'Pass' || option === '通过' ? 'M5 12l4 4L19 6' : 'M6 6l12 12M18 6L6 18'" />
</svg>
}
<span class="choice-label">{{ option }}</span>
</button>
}
</div>
} @else {
<textarea rows="2" [attr.aria-label]="field.label" [maxLength]="field.max_length" placeholder="添加备注(选填)"
[ngModel]="drafts[plugin.plugin_id].values[field.id] || ''" (ngModelChange)="edit(plugin.plugin_id, field.id, $event)"></textarea>
}
</div>
}
<div class="form-actions">
<button type="button" class="save" (click)="save(plugin)">{{ saving() === plugin.plugin_id ? '正在保存…' : '保存评审' }}</button>
<button type="button" class="reload" [disabled]="!!saving()" (click)="reload(plugin)" title="放弃未保存内容,读取最新记录">重新加载</button>
@if (drafts[plugin.plugin_id].dirty) { <span class="muted unsaved">有未保存内容</span> }
</div>
</fieldset>
@if (notice()) { <p class="notice" role="status">{{ notice() }}</p> }
@if (plugin.history.length) {
<details class="history"><summary>修改记录({{ plugin.history.length }})</summary>
@for (entry of plugin.history; track entry.revision) {
<div class="history-entry"><strong>第 {{ entry.revision }} 次 · {{ entry.saved_at * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}</strong>
@for (field of entry.fields; track field.id) { <p>{{ field.label }}:{{ entry.values[field.id] || '未填写' }}</p> }
</div>
}
</details>
}
}
</section>
</ng-template>