codexmate 0.0.43 → 0.0.45
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 +2 -0
- package/README.zh.md +2 -0
- package/cli/claude-proxy.js +611 -14
- package/cli/update.js +77 -7
- package/cli.js +188 -21
- package/package.json +1 -1
- package/web-ui/app.js +36 -3
- package/web-ui/index.html +1 -0
- package/web-ui/logic.claude.mjs +65 -2
- package/web-ui/logic.runtime.mjs +0 -7
- package/web-ui/modules/app.computed.index.mjs +3 -1
- package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
- package/web-ui/modules/app.computed.prompts.mjs +28 -0
- package/web-ui/modules/app.computed.session.mjs +23 -1
- package/web-ui/modules/app.methods.agents.mjs +50 -4
- package/web-ui/modules/app.methods.claude-config.mjs +28 -12
- package/web-ui/modules/app.methods.index.mjs +1 -1
- package/web-ui/modules/app.methods.install.mjs +129 -1
- package/web-ui/modules/app.methods.navigation.mjs +2 -1
- package/web-ui/modules/app.methods.session-actions.mjs +17 -2
- package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +26 -3
- package/web-ui/modules/i18n/locales/en.mjs +42 -5
- package/web-ui/modules/i18n/locales/ja.mjs +42 -5
- package/web-ui/modules/i18n/locales/vi.mjs +51 -0
- package/web-ui/modules/i18n/locales/zh.mjs +42 -5
- package/web-ui/partials/index/layout-footer.html +1 -1
- package/web-ui/partials/index/layout-header.html +64 -0
- package/web-ui/partials/index/modal-config-template-agents.html +12 -13
- package/web-ui/partials/index/modals-basic.html +18 -1
- package/web-ui/partials/index/panel-config-claude.html +4 -7
- package/web-ui/partials/index/panel-config-codex.html +2 -6
- package/web-ui/partials/index/panel-prompts.html +100 -0
- package/web-ui/partials/index/panel-sessions.html +30 -10
- package/web-ui/partials/index/panel-usage.html +34 -18
- package/web-ui/res/web-ui-render.precompiled.js +579 -149
- package/web-ui/styles/controls-forms.css +5 -5
- package/web-ui/styles/layout-shell.css +145 -0
- package/web-ui/styles/modals-core.css +162 -0
- package/web-ui/styles/responsive.css +77 -5
- package/web-ui/styles/sessions-toolbar-trash.css +45 -10
- package/web-ui/styles/sessions-usage.css +31 -2
|
@@ -102,6 +102,18 @@
|
|
|
102
102
|
:disabled="agentsLoading || agentsSaving || agentsDiffVisible">
|
|
103
103
|
{{ t('common.paste') }}
|
|
104
104
|
</button>
|
|
105
|
+
<span class="prompts-editor-actions-sep"></span>
|
|
106
|
+
<button class="btn-mini" @click="closeAgentsModal" :disabled="agentsSaving || agentsDiffLoading">{{ t('common.cancel') }}</button>
|
|
107
|
+
<button
|
|
108
|
+
v-if="agentsDiffVisible"
|
|
109
|
+
class="btn-mini"
|
|
110
|
+
@click="resetAgentsDiffState"
|
|
111
|
+
:disabled="agentsSaving || agentsDiffLoading">
|
|
112
|
+
{{ t('common.backToEdit') }}
|
|
113
|
+
</button>
|
|
114
|
+
<button class="btn-mini btn-confirm-mini" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading || agentsDiffLoading || (agentsDiffVisible && !agentsDiffHasChanges)">
|
|
115
|
+
{{ agentsSaving ? (agentsDiffVisible ? t('common.saving') : t('common.previewing')) : (agentsDiffVisible ? t('common.save') : t('common.preview')) }}
|
|
116
|
+
</button>
|
|
105
117
|
</div>
|
|
106
118
|
</div>
|
|
107
119
|
|
|
@@ -168,18 +180,5 @@
|
|
|
168
180
|
|
|
169
181
|
</div>
|
|
170
182
|
|
|
171
|
-
<div class="btn-group modal-editor-footer">
|
|
172
|
-
<button class="btn btn-cancel" @click="closeAgentsModal" :disabled="agentsSaving || agentsDiffLoading">{{ t('common.cancel') }}</button>
|
|
173
|
-
<button
|
|
174
|
-
v-if="agentsDiffVisible"
|
|
175
|
-
class="btn"
|
|
176
|
-
@click="resetAgentsDiffState"
|
|
177
|
-
:disabled="agentsSaving || agentsDiffLoading">
|
|
178
|
-
{{ t('common.backToEdit') }}
|
|
179
|
-
</button>
|
|
180
|
-
<button class="btn btn-confirm" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading || agentsDiffLoading || (agentsDiffVisible && !agentsDiffHasChanges)">
|
|
181
|
-
{{ agentsSaving ? (agentsDiffVisible ? t('common.applying') : t('common.confirming')) : (agentsDiffVisible ? t('common.apply') : t('common.confirm')) }}
|
|
182
|
-
</button>
|
|
183
|
-
</div>
|
|
184
183
|
</div>
|
|
185
184
|
</div>
|
|
@@ -165,6 +165,15 @@
|
|
|
165
165
|
<input v-model="newClaudeConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
|
|
166
166
|
<div v-if="claudeConfigFieldError('add', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'model') }}</div>
|
|
167
167
|
</div>
|
|
168
|
+
<div class="form-group">
|
|
169
|
+
<label class="form-label">{{ t('claude.targetApi.label') }}</label>
|
|
170
|
+
<select v-model="newClaudeConfig.targetApi" class="form-input">
|
|
171
|
+
<option value="responses">{{ t('claude.targetApi.responses') }}</option>
|
|
172
|
+
<option value="chat_completions">{{ t('claude.targetApi.chatCompletions') }}</option>
|
|
173
|
+
<option value="ollama">{{ t('claude.targetApi.ollama') }}</option>
|
|
174
|
+
</select>
|
|
175
|
+
<div class="form-hint">{{ t('claude.targetApi.hint') }}</div>
|
|
176
|
+
</div>
|
|
168
177
|
|
|
169
178
|
<div class="btn-group">
|
|
170
179
|
<button class="btn btn-cancel" @click="closeClaudeConfigModal">{{ t('common.cancel') }}</button>
|
|
@@ -204,6 +213,15 @@
|
|
|
204
213
|
<input v-model="editingConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
|
|
205
214
|
<div v-if="claudeConfigFieldError('edit', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'model') }}</div>
|
|
206
215
|
</div>
|
|
216
|
+
<div class="form-group">
|
|
217
|
+
<label class="form-label">{{ t('claude.targetApi.label') }}</label>
|
|
218
|
+
<select v-model="editingConfig.targetApi" class="form-input">
|
|
219
|
+
<option value="responses">{{ t('claude.targetApi.responses') }}</option>
|
|
220
|
+
<option value="chat_completions">{{ t('claude.targetApi.chatCompletions') }}</option>
|
|
221
|
+
<option value="ollama">{{ t('claude.targetApi.ollama') }}</option>
|
|
222
|
+
</select>
|
|
223
|
+
<div class="form-hint">{{ t('claude.targetApi.hint') }}</div>
|
|
224
|
+
</div>
|
|
207
225
|
|
|
208
226
|
<div class="btn-group">
|
|
209
227
|
<button class="btn btn-cancel" @click="closeEditConfigModal">{{ t('common.cancel') }}</button>
|
|
@@ -260,4 +278,3 @@
|
|
|
260
278
|
</div>
|
|
261
279
|
</div>
|
|
262
280
|
</div>
|
|
263
|
-
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<label class="settings-toggle-row tool-config-write-toggle">
|
|
35
35
|
<input
|
|
36
36
|
type="checkbox"
|
|
37
|
+
autocomplete="off"
|
|
37
38
|
:checked="isToolConfigWriteAllowed('claude')"
|
|
38
39
|
:disabled="toolConfigPermissionSaving.claude"
|
|
39
40
|
@change="setToolConfigPermission('claude', $event.target.checked)">
|
|
@@ -107,12 +108,6 @@
|
|
|
107
108
|
<button class="btn-tool btn-template-editor" @click="openClaudeConfigTemplateEditor" :disabled="loading || !!initError">{{ t('config.template.openEditor') }}</button>
|
|
108
109
|
</div>
|
|
109
110
|
|
|
110
|
-
<div class="selector-section">
|
|
111
|
-
<div class="selector-header"><span class="selector-title">CLAUDE.md</span></div>
|
|
112
|
-
<button class="btn-tool" @click="openClaudeMdEditor" :disabled="loading || !!initError || agentsLoading">{{ agentsLoading ? t('config.modelLoading') : t('claude.md.open') }}</button>
|
|
113
|
-
<div class="config-template-hint">{{ t('claude.md.hint') }}</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
111
|
<div class="selector-section">
|
|
117
112
|
<div class="selector-header"><span class="selector-title">{{ t('config.health.title') }}</span></div>
|
|
118
113
|
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">{{ healthCheckLoading ? t('config.health.running') : t('config.health.run') }}</button>
|
|
@@ -145,10 +140,12 @@
|
|
|
145
140
|
</div>
|
|
146
141
|
<div v-for="(config, name) in claudeConfigs" :key="name" :class="['card', { active: currentClaudeConfig === name }]" @click="applyClaudeConfig(name)" @keydown.enter.self.prevent="applyClaudeConfig(name)" @keydown.space.self.prevent="applyClaudeConfig(name)" tabindex="0" role="button" :aria-current="currentClaudeConfig === name ? 'true' : null">
|
|
147
142
|
<div class="card-leading">
|
|
148
|
-
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}
|
|
143
|
+
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}<span v-if="config.targetApi === 'chat_completions' || config.targetApi === 'ollama'" class="card-icon-dot" :title="t('config.transformProvider.title')"></span></div>
|
|
149
144
|
<div class="card-content">
|
|
150
145
|
<div class="card-title">{{ name }}</div>
|
|
151
146
|
<div class="card-subtitle card-subtitle-model">{{ config.model || t('claude.model.unset') }}</div>
|
|
147
|
+
<div class="card-subtitle" v-if="config.targetApi === 'chat_completions'">{{ t('claude.targetApi.chatCompletionsBadge') }}</div>
|
|
148
|
+
<div class="card-subtitle" v-else-if="config.targetApi === 'ollama'">{{ t('claude.targetApi.ollamaBadge') }}</div>
|
|
152
149
|
<div class="card-subtitle card-subtitle-url" v-if="config.baseUrl">{{ config.baseUrl }}</div>
|
|
153
150
|
</div>
|
|
154
151
|
</div>
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<label class="settings-toggle-row tool-config-write-toggle">
|
|
35
35
|
<input
|
|
36
36
|
type="checkbox"
|
|
37
|
+
autocomplete="off"
|
|
37
38
|
:checked="isToolConfigWriteAllowed('codex')"
|
|
38
39
|
:disabled="toolConfigPermissionSaving.codex"
|
|
39
40
|
@change="setToolConfigPermission('codex', $event.target.checked)">
|
|
@@ -60,11 +61,6 @@
|
|
|
60
61
|
</div>
|
|
61
62
|
</div>
|
|
62
63
|
|
|
63
|
-
<div class="selector-section">
|
|
64
|
-
<div class="selector-header"><span class="selector-title">AGENTS.md</span></div>
|
|
65
|
-
<button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">{{ agentsLoading ? t('config.modelLoading') : t('config.agents.open') }}</button>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
64
|
<div class="selector-section">
|
|
69
65
|
<div class="selector-header">
|
|
70
66
|
<span class="selector-title">{{ t('config.models') }}</span>
|
|
@@ -203,4 +199,4 @@
|
|
|
203
199
|
</div>
|
|
204
200
|
</div>
|
|
205
201
|
</template>
|
|
206
|
-
</div>
|
|
202
|
+
</div>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!-- Prompts editor -->
|
|
2
|
+
<div
|
|
3
|
+
v-if="mainTab === 'prompts'"
|
|
4
|
+
class="mode-content mode-cards"
|
|
5
|
+
id="panel-prompts"
|
|
6
|
+
role="tabpanel"
|
|
7
|
+
aria-labelledby="tab-prompts">
|
|
8
|
+
<div class="segmented-control">
|
|
9
|
+
<button type="button" :class="['segment', { active: promptsSubTab === 'codex' }]" @click="switchPromptsSubTab('codex')">{{ t('prompts.subTab.codex') }}</button>
|
|
10
|
+
<button type="button" :class="['segment', { active: promptsSubTab === 'claude-md' }]" @click="switchPromptsSubTab('claude-md')">{{ t('prompts.subTab.claude') }}</button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="prompts-editor">
|
|
14
|
+
<div class="prompts-editor-toolbar">
|
|
15
|
+
<div class="form-hint">
|
|
16
|
+
{{ agentsPath || t('common.notLoaded') }}
|
|
17
|
+
<span v-if="agentsPath">
|
|
18
|
+
({{ agentsExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
|
|
19
|
+
</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="prompts-editor-actions">
|
|
22
|
+
<div class="prompts-editor-group prompts-editor-group--secondary">
|
|
23
|
+
<button
|
|
24
|
+
class="btn-mini"
|
|
25
|
+
@click="exportAgentsContent"
|
|
26
|
+
:disabled="agentsLoading">
|
|
27
|
+
{{ t('modal.agents.export') }}
|
|
28
|
+
</button>
|
|
29
|
+
<button
|
|
30
|
+
class="btn-mini"
|
|
31
|
+
@click="copyAgentsContent"
|
|
32
|
+
:disabled="agentsLoading">
|
|
33
|
+
{{ t('modal.agents.copy') }}
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
class="btn-mini"
|
|
37
|
+
@click="pasteAgentsContent"
|
|
38
|
+
:disabled="agentsLoading || agentsSaving || agentsDiffVisible">
|
|
39
|
+
{{ t('common.paste') }}
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="prompts-editor-group prompts-editor-group--workflow">
|
|
43
|
+
<button class="btn-mini" @click="loadPromptsContent" :disabled="agentsSaving || agentsDiffLoading">{{ t('common.cancel') }}</button>
|
|
44
|
+
<button
|
|
45
|
+
v-if="agentsDiffVisible"
|
|
46
|
+
class="btn-mini"
|
|
47
|
+
@click="resetAgentsDiffState"
|
|
48
|
+
:disabled="agentsSaving || agentsDiffLoading">
|
|
49
|
+
{{ t('common.backToEdit') }}
|
|
50
|
+
</button>
|
|
51
|
+
<button class="btn-mini btn-confirm-mini" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading || agentsDiffLoading || (!agentsDiffVisible && !hasAgentsContentChanged()) || (agentsDiffVisible && !agentsDiffHasChanges)">
|
|
52
|
+
{{ agentsSaving ? (agentsDiffVisible ? t('common.saving') : t('common.previewing')) : (agentsDiffVisible ? t('common.save') : t('common.preview')) }}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="form-group">
|
|
59
|
+
<div v-if="agentsDiffVisible">
|
|
60
|
+
<div
|
|
61
|
+
v-if="!agentsDiffLoading && !agentsDiffError && !agentsDiffTruncated && (agentsDiffStats.added || agentsDiffStats.removed)"
|
|
62
|
+
class="agents-diff-summary">
|
|
63
|
+
<span class="agents-diff-stat add">+{{ agentsDiffStats.added }}</span>
|
|
64
|
+
<span class="agents-diff-stat del">-{{ agentsDiffStats.removed }}</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div v-if="agentsDiffLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
67
|
+
<div v-else-if="agentsDiffError" class="state-message error">{{ agentsDiffError }}</div>
|
|
68
|
+
<div v-else-if="agentsDiffTruncated" class="agents-diff-empty">{{ t('diff.tooLargeSkip') }}</div>
|
|
69
|
+
<div v-else-if="!agentsDiffHasChanges" class="agents-diff-empty">{{ t('diff.noChanges') }}</div>
|
|
70
|
+
<div v-else class="agents-diff-view agents-diff-editor">
|
|
71
|
+
<div
|
|
72
|
+
v-for="(line, index) in agentsDiffLines"
|
|
73
|
+
:key="line.key || (line.type + '-' + index)"
|
|
74
|
+
:class="['agents-diff-line', line.type]">
|
|
75
|
+
<span class="agents-diff-line-sign">
|
|
76
|
+
{{ line.type === 'add' ? '+' : (line.type === 'del' ? '-' : ' ') }}
|
|
77
|
+
</span>
|
|
78
|
+
<span class="agents-diff-line-text">{{ line.value }}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<div :class="['editor-frame', { 'editor-frame--loading': agentsLoading }]">
|
|
83
|
+
<div v-if="agentsLoading" class="editor-skeleton">
|
|
84
|
+
<div class="skeleton-line" v-for="i in 6" :key="i"></div>
|
|
85
|
+
</div>
|
|
86
|
+
<textarea
|
|
87
|
+
v-model="agentsContent"
|
|
88
|
+
class="form-input template-editor"
|
|
89
|
+
spellcheck="false"
|
|
90
|
+
:readonly="agentsLoading || agentsSaving || agentsDiffVisible"
|
|
91
|
+
@input="onAgentsContentInput"
|
|
92
|
+
:placeholder="t(promptsSubTab === 'claude-md' ? 'modal.agents.placeholder.claudeMd' : 'modal.agents.placeholder')"></textarea>
|
|
93
|
+
</div>
|
|
94
|
+
<div v-if="promptsContextHint" :class="['prompts-context-hint', { 'prompts-context-hint--warn': promptsContextHint.warn }]">
|
|
95
|
+
{{ promptsContextHint.text }}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
@@ -219,33 +219,53 @@
|
|
|
219
219
|
</div>
|
|
220
220
|
</div>
|
|
221
221
|
<div class="session-actions">
|
|
222
|
-
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession"
|
|
223
|
-
|
|
222
|
+
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession"
|
|
223
|
+
:title="sessionDetailLoading ? t('sessions.preview.loading') : t('sessions.preview.refresh')"
|
|
224
|
+
:aria-label="sessionDetailLoading ? t('sessions.preview.loading') : t('sessions.preview.refresh')">
|
|
225
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 8a5.5 5.5 0 0 1 9.4-3.8"/><path d="M13.5 8a5.5 5.5 0 0 1-9.4 3.8"/><polyline points="2.5 2 2.5 5 5.5 5"/><polyline points="13.5 14 13.5 11 10.5 11"/></svg>
|
|
224
226
|
</button>
|
|
225
227
|
<button
|
|
226
228
|
v-if="isDeleteAvailable(activeSession)"
|
|
227
229
|
class="btn-session-delete"
|
|
228
230
|
@click="deleteSession(activeSession)"
|
|
229
|
-
:disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]"
|
|
230
|
-
|
|
231
|
+
:disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]"
|
|
232
|
+
:title="(activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? (sessionTrashEnabled === false ? t('sessions.preview.deleting') : t('sessions.preview.moving')) : (sessionTrashEnabled === false ? t('sessions.preview.deleteHard') : t('sessions.preview.moveToTrash'))"
|
|
233
|
+
:aria-label="(activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? (sessionTrashEnabled === false ? t('sessions.preview.deleting') : t('sessions.preview.moving')) : (sessionTrashEnabled === false ? t('sessions.preview.deleteHard') : t('sessions.preview.moveToTrash'))">
|
|
234
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 4 4 4 13 4"/><path d="M5.5 4V2.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1V4"/><path d="M12 4v9.5a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 4 13.5V4"/></svg>
|
|
231
235
|
</button>
|
|
232
236
|
<button
|
|
233
237
|
class="btn-session-export"
|
|
234
238
|
@click="exportSession(activeSession)"
|
|
235
|
-
:disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]"
|
|
236
|
-
|
|
239
|
+
:disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]"
|
|
240
|
+
:title="(activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? t('sessions.preview.exporting') : t('sessions.preview.export')"
|
|
241
|
+
:aria-label="(activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? t('sessions.preview.exporting') : t('sessions.preview.export')">
|
|
242
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8"/><polyline points="4 7 8 10.5 12 7"/><path d="M2.5 12v1.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V12"/></svg>
|
|
237
243
|
</button>
|
|
244
|
+
<div class="session-link-group">
|
|
238
245
|
<button
|
|
239
246
|
class="btn-session-open"
|
|
240
247
|
@click="copySessionLink(activeSession)"
|
|
241
|
-
:disabled="!activeSession"
|
|
242
|
-
|
|
248
|
+
:disabled="!activeSession || !canBuildStandaloneUrl(activeSession)"
|
|
249
|
+
:title="t('sessions.preview.copyLink')"
|
|
250
|
+
:aria-label="t('sessions.preview.copyLink')">
|
|
251
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="2.5" width="4" height="2" rx="1"/><path d="M4.5 4h7a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5"/><line x1="6" y1="7.5" x2="10" y2="7.5"/><line x1="6" y1="10" x2="10" y2="10"/></svg>
|
|
243
252
|
</button>
|
|
253
|
+
<button
|
|
254
|
+
class="btn-session-open"
|
|
255
|
+
@click="openSessionLink(activeSession)"
|
|
256
|
+
:disabled="!activeSession || !canBuildStandaloneUrl(activeSession)"
|
|
257
|
+
:title="t('sessions.preview.openLink')"
|
|
258
|
+
:aria-label="t('sessions.preview.openLink')">
|
|
259
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2h-8.5A1.5 1.5 0 0 0 2 3.5v8A1.5 1.5 0 0 0 3.5 13h8a1.5 1.5 0 0 0 1.5-1.5v-3"/><path d="M13 2v4h-4"/><path d="M13 2L7 8"/></svg>
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
244
262
|
<button
|
|
245
263
|
class="btn-session-open"
|
|
246
264
|
@click="copySessionPath(activeSession)"
|
|
247
|
-
:disabled="!activeSession || !getSessionFilePath(activeSession)"
|
|
248
|
-
|
|
265
|
+
:disabled="!activeSession || !getSessionFilePath(activeSession)"
|
|
266
|
+
:title="t('sessions.preview.copyPath')"
|
|
267
|
+
:aria-label="t('sessions.preview.copyPath')">
|
|
268
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="2.5" width="4" height="2" rx="1"/><path d="M4.5 4h7a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5"/><line x1="6" y1="7.5" x2="10" y2="7.5"/><line x1="6" y1="10" x2="10" y2="10"/></svg>
|
|
249
269
|
</button>
|
|
250
270
|
</div>
|
|
251
271
|
</div>
|
|
@@ -75,26 +75,42 @@
|
|
|
75
75
|
<section v-if="sessionUsageWave.points && sessionUsageWave.points.length" class="usage-wave-section">
|
|
76
76
|
<div class="usage-card-title">{{ t('usage.daily.title') }}</div>
|
|
77
77
|
<div class="usage-wave-container">
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
</linearGradient>
|
|
84
|
-
</defs>
|
|
85
|
-
<path :d="sessionUsageWave.areaPath" :fill="'url(#wave-gradient-' + sessionsUsageTimeRange + ')'" class="usage-wave-area"/>
|
|
86
|
-
<path :d="sessionUsageWave.linePath" fill="none" :stroke="'var(--color-brand)'" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="usage-wave-line"/>
|
|
87
|
-
<line v-if="sessionsUsageSelectedDay" x1="0" :x2="sessionUsageWave.width" :y1="sessionUsageWave.hoverY" :y2="sessionUsageWave.hoverY" stroke="currentColor" stroke-width="1" stroke-dasharray="4 4" opacity="0.5" class="usage-wave-hover-line"/>
|
|
88
|
-
<circle v-if="sessionsUsageSelectedDay" :cx="sessionUsageWave.hoverX" :cy="sessionUsageWave.hoverY" r="5" :fill="'var(--color-surface)'" :stroke="'var(--color-brand)'" stroke-width="2.5" class="usage-wave-hover-point"/>
|
|
89
|
-
</svg>
|
|
90
|
-
<div class="usage-wave-labels">
|
|
91
|
-
<span v-for="label in sessionUsageWave.labels" :key="label.key"
|
|
92
|
-
class="usage-wave-label"
|
|
93
|
-
:class="{ active: sessionsUsageSelectedDay === label.key }"
|
|
94
|
-
@click="selectSessionsUsageDay(label.key)">
|
|
95
|
-
{{ label.text }}
|
|
78
|
+
<div class="usage-wave-yaxis">
|
|
79
|
+
<span v-for="tick in sessionUsageWave.yTicks" :key="tick.value"
|
|
80
|
+
class="usage-wave-ytick"
|
|
81
|
+
:style="{ bottom: tick.percent + '%' }">
|
|
82
|
+
{{ tick.label }}
|
|
96
83
|
</span>
|
|
97
84
|
</div>
|
|
85
|
+
<div class="usage-wave-chart-area">
|
|
86
|
+
<svg class="usage-wave-chart" viewBox="0 0 800 140" preserveAspectRatio="none">
|
|
87
|
+
<defs>
|
|
88
|
+
<linearGradient :id="'wave-gradient-' + sessionsUsageTimeRange" x1="0" y1="0" x2="0" y2="1">
|
|
89
|
+
<stop offset="0%" :stop-color="'var(--color-brand)'" stop-opacity="0.35"/>
|
|
90
|
+
<stop offset="100%" :stop-color="'var(--color-brand)'" stop-opacity="0"/>
|
|
91
|
+
</linearGradient>
|
|
92
|
+
</defs>
|
|
93
|
+
<line v-for="tick in sessionUsageWave.yTicks" :key="'g-' + tick.value"
|
|
94
|
+
x1="0" :x2="sessionUsageWave.width"
|
|
95
|
+
:y1="tick.y" :y2="tick.y"
|
|
96
|
+
stroke="currentColor" stroke-width="0.5" opacity="0.12"
|
|
97
|
+
class="usage-wave-gridline"/>
|
|
98
|
+
<path :d="sessionUsageWave.areaPath" :fill="'url(#wave-gradient-' + sessionsUsageTimeRange + ')'" class="usage-wave-area"/>
|
|
99
|
+
<path :d="sessionUsageWave.linePath" fill="none" :stroke="'var(--color-brand)'" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="usage-wave-line"/>
|
|
100
|
+
<line v-if="sessionsUsageSelectedDay" x1="0" :x2="sessionUsageWave.width" :y1="sessionUsageWave.hoverY" :y2="sessionUsageWave.hoverY" stroke="currentColor" stroke-width="1" stroke-dasharray="4 4" opacity="0.5" class="usage-wave-hover-line"/>
|
|
101
|
+
<circle v-if="sessionsUsageSelectedDay" :cx="sessionUsageWave.hoverX" :cy="sessionUsageWave.hoverY" r="5" :fill="'var(--color-surface)'" :stroke="'var(--color-brand)'" stroke-width="2.5" class="usage-wave-hover-point"/>
|
|
102
|
+
</svg>
|
|
103
|
+
<div class="usage-wave-labels">
|
|
104
|
+
<button v-for="label in sessionUsageWave.labels" :key="label.key"
|
|
105
|
+
type="button"
|
|
106
|
+
class="usage-wave-label"
|
|
107
|
+
:class="{ active: sessionsUsageSelectedDay === label.key }"
|
|
108
|
+
:aria-pressed="sessionsUsageSelectedDay === label.key"
|
|
109
|
+
@click="selectSessionsUsageDay(label.key)">
|
|
110
|
+
{{ label.text }}
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
98
114
|
</div>
|
|
99
115
|
<div v-if="sessionsUsageSelectedDaySummary" class="usage-daydetail">
|
|
100
116
|
<div class="usage-daydetail-header">
|