codexmate 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README.vi.md +6 -3
- package/README.zh.md +6 -3
- package/cli/system-prompt-files.js +177 -0
- package/cli.js +27 -0
- package/package.json +1 -1
- package/skills/{codexmate-project-context-recovery → context-recovery}/SKILL.md +2 -2
- package/web-ui/app.js +19 -0
- package/web-ui/modules/app.computed.session.mjs +1 -1
- package/web-ui/modules/app.methods.agents.mjs +5 -12
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.session-actions.mjs +27 -0
- package/web-ui/modules/app.methods.session-browser.mjs +21 -1
- package/web-ui/modules/app.methods.session-trash.mjs +5 -0
- package/web-ui/modules/app.methods.system-prompt.mjs +294 -0
- package/web-ui/modules/i18n/locales/en.mjs +29 -0
- package/web-ui/modules/i18n/locales/ja.mjs +43 -0
- package/web-ui/modules/i18n/locales/vi.mjs +27 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +27 -0
- package/web-ui/modules/i18n/locales/zh.mjs +27 -0
- package/web-ui/modules/request-token.mjs +9 -0
- package/web-ui/partials/index/panel-prompts.html +96 -1
- package/web-ui/partials/index/panel-settings.html +0 -23
- package/web-ui/partials/index/panel-trash.html +36 -1
- package/web-ui/res/web-ui-render.precompiled.js +750 -456
- package/web-ui/session-helpers.mjs +9 -0
- package/web-ui/styles/responsive.css +18 -0
- package/web-ui/styles/sessions-list.css +5 -0
- package/web-ui/styles/sessions-usage.css +20 -1
- package/web-ui/styles/system-prompt.css +23 -0
- package/web-ui/styles/trash-panel.css +9 -0
- package/web-ui/styles.css +1 -0
- /package/skills/{codexmate-project-context-recovery → context-recovery}/scripts/search_sessions.py +0 -0
|
@@ -42,6 +42,8 @@ const zh = Object.freeze({
|
|
|
42
42
|
'common.refresh': '刷新',
|
|
43
43
|
'common.refreshing': '刷新中...',
|
|
44
44
|
'common.loading': '加载中...',
|
|
45
|
+
'common.menu': '菜单',
|
|
46
|
+
'common.registry': '镜像源',
|
|
45
47
|
'common.saving': '保存中...',
|
|
46
48
|
'common.sending': '发送中...',
|
|
47
49
|
'common.scanning': '扫描中...',
|
|
@@ -68,6 +70,7 @@ const zh = Object.freeze({
|
|
|
68
70
|
'common.notConfigured': '未配置',
|
|
69
71
|
'common.enabled': '已启用',
|
|
70
72
|
'common.disabled': '已禁用',
|
|
73
|
+
'common.retry': '重试',
|
|
71
74
|
'cli.missing.title': '{name} CLI 未安装',
|
|
72
75
|
'cli.missing.subtitle': '请先安装 {name} CLI 后再继续使用此页面。',
|
|
73
76
|
'cli.missing.openDocs': '打开安装指南',
|
|
@@ -89,6 +92,7 @@ const zh = Object.freeze({
|
|
|
89
92
|
'field.apiEndpoint': 'API 端点',
|
|
90
93
|
'field.apiKey': '认证密钥',
|
|
91
94
|
'field.baseUrl': 'Base URL',
|
|
95
|
+
'field.url': 'URL',
|
|
92
96
|
'field.provider': '提供商',
|
|
93
97
|
'field.providerName': 'Provider 名称',
|
|
94
98
|
'field.modelName': '模型名称',
|
|
@@ -227,6 +231,7 @@ const zh = Object.freeze({
|
|
|
227
231
|
'subtitle.prompts': '编辑 AGENTS.md 与 CLAUDE.md。',
|
|
228
232
|
'prompts.subTab.codex': 'AGENTS.md (Codex)',
|
|
229
233
|
'prompts.subTab.project': 'CLAUDE.md',
|
|
234
|
+
'prompts.subTab.system': 'System Prompt',
|
|
230
235
|
'prompts.subTab.presets': '预设池',
|
|
231
236
|
'prompts.presets.title': '预设池',
|
|
232
237
|
'prompts.presets.hint': '管理预设。',
|
|
@@ -463,6 +468,7 @@ const zh = Object.freeze({
|
|
|
463
468
|
'toast.templates.deleteConfirm': '删除',
|
|
464
469
|
'toast.templates.deleteCancel': '取消',
|
|
465
470
|
'toast.operation.success': '操作成功',
|
|
471
|
+
'toast.operation.fail': '操作失败',
|
|
466
472
|
'toast.load.fail': '加载文件失败',
|
|
467
473
|
'toast.apply.success': '配置已应用',
|
|
468
474
|
'toast.apply.fail': '应用配置失败',
|
|
@@ -914,6 +920,11 @@ const zh = Object.freeze({
|
|
|
914
920
|
'config.template.editFirst': '先改模板,再应用。',
|
|
915
921
|
'config.template.bridgeCodexOnly': '{hint} 模板仅限 Codex 编辑。',
|
|
916
922
|
'config.localBridge.enabledCount': '已启用 {enabled}/{total}',
|
|
923
|
+
'config.localBridge.poolSettings': '轮询池设置',
|
|
924
|
+
'config.localBridge.poolHint': '选择参与负载均衡的候选提供商。',
|
|
925
|
+
'config.localBridge.noProviders': '暂无可用候选提供商,请先添加直连提供商。',
|
|
926
|
+
'config.transformProvider.title': '使用转换提供商',
|
|
927
|
+
'config.providerBridgeHint': '{label} 桥接模式',
|
|
917
928
|
'config.template.openEditor': '打开模板编辑器',
|
|
918
929
|
'modal.configTemplate.title': 'Config 模板编辑器(手动确认应用)',
|
|
919
930
|
'modal.configTemplate.placeholder': '在这里编辑 config.toml 模板内容',
|
|
@@ -1384,6 +1395,12 @@ const zh = Object.freeze({
|
|
|
1384
1395
|
'settings.trash.retentionLabel': '保留天数',
|
|
1385
1396
|
'settings.trash.retentionUnit': '天',
|
|
1386
1397
|
'settings.trash.retentionHint': '范围 1-365 天,默认 30 天。每次加载回收站时自动清理过期记录。',
|
|
1398
|
+
'settings.trash.count': '共 {count} 项',
|
|
1399
|
+
'settings.trash.retentionShort': '保留 {days} 天',
|
|
1400
|
+
'settings.trash.disabled': '回收站已关闭',
|
|
1401
|
+
'settings.trash.disabledHint': '已删除的会话将直接永久删除,不再进入回收站。',
|
|
1402
|
+
'settings.trash.clearShort': '清空',
|
|
1403
|
+
'settings.trash.loadMoreItems': '加载更多(剩余 {count} 项)',
|
|
1387
1404
|
|
|
1388
1405
|
'settings.webhook.title': 'Webhook',
|
|
1389
1406
|
'settings.webhook.meta': '发送事件通知到外部服务',
|
|
@@ -1557,6 +1574,16 @@ const zh = Object.freeze({
|
|
|
1557
1574
|
'kilocode.writeRequired': '请先打开 KiloCode 写入开关',
|
|
1558
1575
|
'kilocode.saved': 'KiloCode 配置已保存',
|
|
1559
1576
|
'kilocode.autoSaved': 'KiloCode 配置已同步',
|
|
1577
|
+
'sysPrompt.scope.label': '作用域',
|
|
1578
|
+
'sysPrompt.scope.global': '全局 (~/.pi/agent/)',
|
|
1579
|
+
'sysPrompt.scope.project': '项目 (<cwd>/.pi/)',
|
|
1580
|
+
'sysPrompt.mode.label': '模式',
|
|
1581
|
+
'sysPrompt.mode.append': '追加到默认提示词 (APPEND_SYSTEM.md)',
|
|
1582
|
+
'sysPrompt.mode.system': '替换默认提示词 (SYSTEM.md)',
|
|
1583
|
+
'sysPrompt.placeholder': '在此输入 system prompt 内容…',
|
|
1584
|
+
'sysPrompt.hint.unsaved': '有未保存的改动',
|
|
1585
|
+
'sysPrompt.hint.twoStepSave': '点击预览查看差异,确认后保存。',
|
|
1586
|
+
'sysPrompt.toast.saved': 'System prompt 已保存',
|
|
1560
1587
|
});
|
|
1561
1588
|
|
|
1562
1589
|
export { zh };
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<div class="prompts-md-tabs" role="tablist" :aria-label="t('tab.prompts')">
|
|
9
9
|
<button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'codex' }]" role="tab" :aria-selected="promptsSubTab === 'codex'" @click="switchPromptsSubTab('codex')">{{ t('prompts.subTab.codex') }}</button>
|
|
10
10
|
<button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'claude-project' }]" role="tab" :aria-selected="promptsSubTab === 'claude-project'" @click="switchPromptsSubTab('claude-project')">{{ t('prompts.subTab.project') }}</button>
|
|
11
|
+
<button type="button" :class="['prompts-md-tab', { active: promptsSubTab === 'system' }]" role="tab" :aria-selected="promptsSubTab === 'system'" @click="switchPromptsSubTab('system')">{{ t('prompts.subTab.system') }}</button>
|
|
11
12
|
</div>
|
|
12
13
|
|
|
13
14
|
<div v-if="promptsSubTab === 'claude-project'" class="project-path-selector">
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
</div>
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
<div class="prompts-editor">
|
|
37
|
+
<div v-if="promptsSubTab !== 'system'" class="prompts-editor">
|
|
37
38
|
<div class="prompts-editor-toolbar">
|
|
38
39
|
<div class="form-hint">
|
|
39
40
|
{{ agentsPath || t('common.notLoaded') }}
|
|
@@ -189,4 +190,98 @@
|
|
|
189
190
|
</div>
|
|
190
191
|
|
|
191
192
|
</div>
|
|
193
|
+
|
|
194
|
+
<div v-if="promptsSubTab === 'system'">
|
|
195
|
+
<div class="sys-prompt-controls">
|
|
196
|
+
<div class="sys-prompt-control-group">
|
|
197
|
+
<label class="form-label">{{ t('sysPrompt.scope.label') }}</label>
|
|
198
|
+
<select class="form-input sys-prompt-select" :value="sysPromptScope" @change="switchSysPromptScope($event.target.value)" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible">
|
|
199
|
+
<option value="global">{{ t('sysPrompt.scope.global') }}</option>
|
|
200
|
+
<option value="project">{{ t('sysPrompt.scope.project') }}</option>
|
|
201
|
+
</select>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="sys-prompt-control-group">
|
|
204
|
+
<label class="form-label">{{ t('sysPrompt.mode.label') }}</label>
|
|
205
|
+
<select class="form-input sys-prompt-select" :value="sysPromptMode" @change="switchSysPromptMode($event.target.value)" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible">
|
|
206
|
+
<option value="append">{{ t('sysPrompt.mode.append') }}</option>
|
|
207
|
+
<option value="system">{{ t('sysPrompt.mode.system') }}</option>
|
|
208
|
+
</select>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div class="prompts-editor">
|
|
213
|
+
<div class="prompts-editor-toolbar">
|
|
214
|
+
<div class="form-hint">
|
|
215
|
+
{{ sysPromptPath || t('common.notLoaded') }}
|
|
216
|
+
<span v-if="sysPromptPath">
|
|
217
|
+
({{ sysPromptExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
|
|
218
|
+
</span>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="prompts-editor-actions">
|
|
221
|
+
<div class="prompts-editor-group prompts-editor-group--secondary">
|
|
222
|
+
<button class="btn-mini" @click="exportSysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || !sysPromptContent" :title="t('modal.agents.export')">
|
|
223
|
+
<svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M8 11l4 4 4-4M4 17v2h16v-2"/></svg>
|
|
224
|
+
</button>
|
|
225
|
+
<button class="btn-mini" @click="copySysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || !sysPromptContent" :title="t('modal.agents.copy')">
|
|
226
|
+
<svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
227
|
+
</button>
|
|
228
|
+
<button class="btn-mini" @click="pasteSysPromptContent" :disabled="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible" :title="t('common.paste')">
|
|
229
|
+
<svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/></svg>
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="prompts-editor-group prompts-editor-group--workflow">
|
|
233
|
+
<button class="btn-mini" @click="cancelSysPromptEdit" :disabled="sysPromptSaving || sysPromptDiffLoading" :title="t('common.cancel')">
|
|
234
|
+
<svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
235
|
+
</button>
|
|
236
|
+
<button v-if="sysPromptDiffVisible" class="btn-mini" @click="resetSysPromptDiffState" :disabled="sysPromptSaving || sysPromptDiffLoading" :title="t('common.backToEdit')">
|
|
237
|
+
<svg class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
238
|
+
</button>
|
|
239
|
+
<button class="btn-mini btn-confirm-mini" @click="applySystemPrompt" :disabled="sysPromptSaving || sysPromptLoading || sysPromptDiffLoading || (!sysPromptDiffVisible && !hasSysPromptContentChanged()) || (sysPromptDiffVisible && !sysPromptDiffHasChangesValue)" :title="sysPromptDiffVisible ? t('common.save') : t('common.preview')">
|
|
240
|
+
<svg v-if="sysPromptDiffVisible" class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
|
|
241
|
+
<svg v-else class="btn-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
242
|
+
</button>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="form-group">
|
|
247
|
+
<div v-if="sysPromptDiffVisible">
|
|
248
|
+
<div
|
|
249
|
+
v-if="!sysPromptDiffLoading && !sysPromptDiffError && !sysPromptDiffTruncated && (sysPromptDiffStats.added || sysPromptDiffStats.removed)"
|
|
250
|
+
class="agents-diff-summary">
|
|
251
|
+
<span class="agents-diff-stat add">+{{ sysPromptDiffStats.added }}</span>
|
|
252
|
+
<span class="agents-diff-stat del">-{{ sysPromptDiffStats.removed }}</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div v-if="sysPromptDiffLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
255
|
+
<div v-else-if="sysPromptDiffError" class="state-message error">{{ sysPromptDiffError }}</div>
|
|
256
|
+
<div v-else-if="sysPromptDiffTruncated" class="agents-diff-empty">{{ t('diff.tooLargeSkip') }}</div>
|
|
257
|
+
<div v-else-if="!sysPromptDiffHasChangesValue" class="agents-diff-empty">{{ t('diff.noChanges') }}</div>
|
|
258
|
+
<div v-else class="agents-diff-view agents-diff-editor">
|
|
259
|
+
<div
|
|
260
|
+
v-for="(line, index) in sysPromptDiffLines"
|
|
261
|
+
:key="'sys-prompt-diff-' + index"
|
|
262
|
+
:class="['agents-diff-line', line.type]">
|
|
263
|
+
<span class="agents-diff-line-sign">
|
|
264
|
+
{{ line.type === 'add' ? '+' : (line.type === 'del' ? '-' : ' ') }}
|
|
265
|
+
</span>
|
|
266
|
+
<span class="agents-diff-line-text">{{ line.value }}</span>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
<div :class="['editor-frame', { 'editor-frame--loading': sysPromptLoading }]">
|
|
271
|
+
<div v-if="sysPromptLoading" class="editor-skeleton">
|
|
272
|
+
<div class="skeleton-line" v-for="i in 6" :key="i"></div>
|
|
273
|
+
</div>
|
|
274
|
+
<textarea
|
|
275
|
+
v-model="sysPromptContent"
|
|
276
|
+
class="form-input template-editor"
|
|
277
|
+
spellcheck="false"
|
|
278
|
+
:readonly="sysPromptLoading || sysPromptSaving || sysPromptDiffVisible"
|
|
279
|
+
:placeholder="t('sysPrompt.placeholder')"></textarea>
|
|
280
|
+
</div>
|
|
281
|
+
<div v-if="sysPromptContextHint()" :class="['prompts-context-hint', { 'prompts-context-hint--warn': sysPromptContextHint().warn }]">
|
|
282
|
+
{{ sysPromptContextHint().text }}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
192
287
|
</div>
|
|
@@ -185,29 +185,6 @@
|
|
|
185
185
|
</button>
|
|
186
186
|
</div>
|
|
187
187
|
</section>
|
|
188
|
-
|
|
189
|
-
<section class="settings-card" :aria-label="t('settings.trashConfig.title')">
|
|
190
|
-
<div class="settings-card-main">
|
|
191
|
-
<div class="settings-card-content">
|
|
192
|
-
<div class="settings-card-title">{{ t('settings.trashConfig.title') }}</div>
|
|
193
|
-
<p class="settings-card-desc">{{ t('settings.trashConfig.meta') }}</p>
|
|
194
|
-
<label class="settings-toggle-row">
|
|
195
|
-
<input type="checkbox" :checked="sessionTrashEnabled" @change="setSessionTrashEnabled($event.target.checked)">
|
|
196
|
-
<span class="toggle-track">
|
|
197
|
-
<span class="toggle-thumb"></span>
|
|
198
|
-
</span>
|
|
199
|
-
<span>{{ t('settings.deleteBehavior.toggle') }}</span>
|
|
200
|
-
</label>
|
|
201
|
-
<div class="settings-retention">
|
|
202
|
-
<label for="settings-trash-retention-days">{{ t('settings.trash.retentionLabel') }}</label>
|
|
203
|
-
<input id="settings-trash-retention-days" type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
|
|
204
|
-
<span>{{ t('settings.trash.retentionUnit') }}</span>
|
|
205
|
-
</div>
|
|
206
|
-
<p class="settings-card-hint">{{ t('settings.trash.retentionHint') }}</p>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
</section>
|
|
210
|
-
|
|
211
188
|
<section class="settings-card settings-card--destructive" :aria-label="t('settings.reset.title')">
|
|
212
189
|
<div class="settings-card-main">
|
|
213
190
|
<div class="settings-card-content">
|
|
@@ -6,8 +6,43 @@
|
|
|
6
6
|
role="tabpanel"
|
|
7
7
|
aria-labelledby="tab-trash">
|
|
8
8
|
<div v-if="!loading" class="trash-panel-shell">
|
|
9
|
+
<!-- Trash configuration -->
|
|
10
|
+
<section class="settings-card trash-config-card" :aria-label="t('settings.trashConfig.title')">
|
|
11
|
+
<div class="settings-card-main">
|
|
12
|
+
<div class="settings-card-content">
|
|
13
|
+
<div class="settings-card-title">{{ t('settings.trashConfig.title') }}</div>
|
|
14
|
+
<p class="settings-card-desc">{{ t('settings.trashConfig.meta') }}</p>
|
|
15
|
+
<label class="settings-toggle-row">
|
|
16
|
+
<input type="checkbox" :checked="sessionTrashEnabled" @change="setSessionTrashEnabled($event.target.checked)">
|
|
17
|
+
<span class="toggle-track">
|
|
18
|
+
<span class="toggle-thumb"></span>
|
|
19
|
+
</span>
|
|
20
|
+
<span>{{ t('settings.deleteBehavior.toggle') }}</span>
|
|
21
|
+
</label>
|
|
22
|
+
<div class="settings-retention">
|
|
23
|
+
<label for="settings-trash-retention-days">{{ t('settings.trash.retentionLabel') }}</label>
|
|
24
|
+
<input id="settings-trash-retention-days" type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
|
|
25
|
+
<span>{{ t('settings.trash.retentionUnit') }}</span>
|
|
26
|
+
</div>
|
|
27
|
+
<p class="settings-card-hint">{{ t('settings.trash.retentionHint') }}</p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<!-- Disabled state -->
|
|
33
|
+
<div v-if="getSessionTrashViewState() === 'disabled'" class="trash-empty-state trash-disabled-state">
|
|
34
|
+
<svg class="trash-empty-svg" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.2">
|
|
35
|
+
<path d="M20 22l4 32h16l4-32" />
|
|
36
|
+
<path d="M14 22h36" />
|
|
37
|
+
<path d="M24 22v-4a4 4 0 014-4h8a4 4 0 014 4v4" />
|
|
38
|
+
<path d="M10 10l44 44" stroke-width="2" stroke-linecap="round" />
|
|
39
|
+
</svg>
|
|
40
|
+
<div class="trash-empty-title">{{ t('settings.trash.disabled') }}</div>
|
|
41
|
+
<div class="trash-empty-hint">{{ t('settings.trash.disabledHint') }}</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
9
44
|
<!-- Empty state -->
|
|
10
|
-
<div v-if="getSessionTrashViewState() === 'empty'" class="trash-empty-state">
|
|
45
|
+
<div v-else-if="getSessionTrashViewState() === 'empty'" class="trash-empty-state">
|
|
11
46
|
<svg class="trash-empty-svg" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.2">
|
|
12
47
|
<path d="M20 22l4 32h16l4-32"/>
|
|
13
48
|
<path d="M14 22h36"/>
|