@olares/lares-mobile 0.15.4 → 0.15.6

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.
Files changed (44) hide show
  1. package/README.md +6 -2
  2. package/package.json +2 -2
  3. package/src/App.vue +924 -184
  4. package/src/chat/ChatBar.vue +2 -2
  5. package/src/chat/Composer.vue +467 -16
  6. package/src/chat/Deliverables.vue +22 -10
  7. package/src/chat/HistoryPanel.vue +747 -55
  8. package/src/chat/MessageActions.vue +88 -34
  9. package/src/chat/QuestionCard.vue +5 -5
  10. package/src/chat/StageRow.vue +8 -5
  11. package/src/chat/Transcript.vue +20 -6
  12. package/src/chat/composer-tokens.js +32 -0
  13. package/src/chat/format.js +37 -0
  14. package/src/desktop/DesktopComposer.vue +871 -0
  15. package/src/desktop/DesktopContextMeter.vue +81 -0
  16. package/src/desktop/DesktopGoal.vue +88 -0
  17. package/src/desktop/DesktopPreview.vue +58 -0
  18. package/src/desktop/DesktopPreviewTabs.vue +82 -0
  19. package/src/desktop/DesktopQuestions.vue +118 -0
  20. package/src/desktop/DesktopQueue.vue +203 -0
  21. package/src/desktop/DesktopShell.vue +203 -0
  22. package/src/desktop/DesktopSidebar.vue +645 -0
  23. package/src/desktop/DesktopTodos.vue +52 -0
  24. package/src/desktop/DirectoryBrowser.vue +170 -0
  25. package/src/desktop/DirectoryNewFolder.vue +124 -0
  26. package/src/desktop/ui/PcDialog.vue +85 -0
  27. package/src/desktop/ui/PcPopover.vue +110 -0
  28. package/src/desktop/ui/PcTooltip.vue +95 -0
  29. package/src/desktop/ui/place.js +24 -0
  30. package/src/desktop/workspace-view.js +121 -0
  31. package/src/host.js +20 -2
  32. package/src/index.d.ts +2 -0
  33. package/src/mobile/MobileDirectoryNewFolder.vue +121 -0
  34. package/src/mobile/MobileDirectorySheet.vue +162 -0
  35. package/src/mobile/MobileNewSession.vue +217 -0
  36. package/src/mobile/MobileShell.vue +367 -0
  37. package/src/motion.css +166 -0
  38. package/src/preview/Preview.vue +91 -150
  39. package/src/preview/PreviewBody.vue +185 -0
  40. package/src/settings/AgentSettings.vue +126 -36
  41. package/src/settings/Select.vue +183 -0
  42. package/src/settings/SettingRow.vue +5 -4
  43. package/src/settings/Sheet.vue +4 -4
  44. package/src/styles.css +9 -0
@@ -0,0 +1,367 @@
1
+ <template>
2
+ <div class="lares-mobile-shell">
3
+ <LaresChatBar
4
+ :t="t"
5
+ :title="barTitle"
6
+ :starting="state.starting"
7
+ @history="$emit('history')"
8
+ @create="$emit('create', state.currentWorkspaceId || null)"
9
+ />
10
+ <LaresHistoryPanel
11
+ :open="state.panel === 'history'"
12
+ :sessions="state.sessions"
13
+ :workspaces="state.workspaces"
14
+ :workspaces-ready="state.workspacesReady"
15
+ :workspace-error="state.workspaceError"
16
+ :archived-session-ids="state.archivedSessionIds"
17
+ :session-id="state.sessionId"
18
+ :ready="state.sessionsReady"
19
+ :unseen="state.unseen"
20
+ :t="t"
21
+ @close="$emit('close-history')"
22
+ @pick="$emit('pick-session', $event)"
23
+ @create="$emit('create', $event)"
24
+ @browse-workspaces="$emit('browse-workspaces')"
25
+ @refresh-workspaces="$emit('refresh-workspaces')"
26
+ @rename-session="(id, title) => $emit('rename-session', id, title)"
27
+ @archive-session="$emit('archive-session', $event)"
28
+ @move-session="(workspaceId, sessionId, before) => $emit('move-session', workspaceId, sessionId, before)"
29
+ />
30
+ <LaresMobileDirectorySheet
31
+ :open="state.directory.open"
32
+ :state="state.directory"
33
+ :t="t"
34
+ @close="$emit('close-directory')"
35
+ @browse="$emit('browse-directory', $event)"
36
+ @choose="$emit('choose-directory', $event)"
37
+ @create-folder="$emit('create-folder', $event)"
38
+ @toggle-hidden="$emit('toggle-hidden', $event)"
39
+ />
40
+ <div class="lares-shell__conversation" :data-hero="newSessionHero ? 'true' : 'false'">
41
+ <p v-if="state.failed" class="lares-shell__status">{{ state.failText }}</p>
42
+ <button
43
+ v-if="state.failed"
44
+ type="button"
45
+ class="lares-shell__retry"
46
+ :disabled="state.starting"
47
+ @click="$emit('retry')"
48
+ >
49
+ {{ t("probe.retry") }}
50
+ </button>
51
+
52
+ <Transition name="lares-view" mode="out-in">
53
+ <LaresMobileNewSession
54
+ v-if="newSessionHero"
55
+ key="hero"
56
+ :t="t"
57
+ :workspace-label="workspaceLabel"
58
+ :workspace-busy="!state.workspacesReady && !state.workspaceError"
59
+ :model-label="state.modelLabel"
60
+ :model-busy="state.modelBusy"
61
+ @workspace="workspaceSheet = true"
62
+ @model="$emit('open-model')"
63
+ >
64
+ <LaresComposer
65
+ ref="composer"
66
+ hero
67
+ :draft="state.draft"
68
+ :sending="state.sending"
69
+ :can-send="state.canSend"
70
+ :model-label="state.modelLabel"
71
+ :model-busy="state.modelBusy"
72
+ :effort-label="state.effortLabel"
73
+ :effort-disabled="state.effortDisabled"
74
+ :attach-pending="state.upload.pending > 0"
75
+ :attach-disabled="!state.sessionId || state.historyLoading || state.starting"
76
+ :voice-phase="state.voicePhase"
77
+ :voice-elapsed="state.voiceElapsed"
78
+ :voice-error="state.voiceErrorText"
79
+ :failures="state.upload.failures"
80
+ :draft-images="state.draftImages"
81
+ :references="state.references"
82
+ :references-loading="state.referencesLoading"
83
+ :references-error="state.referencesError"
84
+ :commands="state.commands"
85
+ :commands-error="state.commandsError"
86
+ :t="t"
87
+ @update:draft="$emit('update-draft', $event)"
88
+ @send="$emit('send')"
89
+ @model="$emit('open-model')"
90
+ @effort="$emit('open-effort')"
91
+ @files="$emit('files', $event)"
92
+ @voice="$emit('voice')"
93
+ @hold-start="$emit('hold-start')"
94
+ @hold-end="$emit('hold-end')"
95
+ @open-commands="$emit('open-commands')"
96
+ @open-references="$emit('open-references', $event)"
97
+ @pick-command="$emit('pick-command', $event)"
98
+ @remove-image="$emit('remove-image', $event)"
99
+ />
100
+ </LaresMobileNewSession>
101
+
102
+ <div v-else key="chat" class="lares-shell__chat">
103
+ <LaresTranscript
104
+ ref="transcript"
105
+ :items="state.viewItems"
106
+ :running="state.running"
107
+ :loading="state.historyLoading || state.starting"
108
+ :previews="state.previews"
109
+ :media-url="mediaUrl"
110
+ :scroll-top="scrollTop"
111
+ :remember-scroll="rememberScroll"
112
+ :sticking="sticking"
113
+ :session-id="state.sessionId"
114
+ :question="state.question"
115
+ :question-busy="state.questionBusy"
116
+ :t="t"
117
+ @open="$emit('open-file', $event)"
118
+ @media="pinLog"
119
+ @answer="forwardAnswer"
120
+ />
121
+ <LaresComposer
122
+ ref="composer"
123
+ :draft="state.draft"
124
+ :sending="state.sending"
125
+ :can-send="state.canSend"
126
+ :model-label="state.modelLabel"
127
+ :model-busy="state.modelBusy"
128
+ :effort-label="state.effortLabel"
129
+ :effort-disabled="state.effortDisabled"
130
+ :attach-pending="state.upload.pending > 0"
131
+ :attach-disabled="!state.sessionId || state.historyLoading || state.starting"
132
+ :voice-phase="state.voicePhase"
133
+ :voice-elapsed="state.voiceElapsed"
134
+ :voice-error="state.voiceErrorText"
135
+ :failures="state.upload.failures"
136
+ :draft-images="state.draftImages"
137
+ :references="state.references"
138
+ :references-loading="state.referencesLoading"
139
+ :references-error="state.referencesError"
140
+ :commands="state.commands"
141
+ :commands-error="state.commandsError"
142
+ :t="t"
143
+ @update:draft="$emit('update-draft', $event)"
144
+ @send="$emit('send')"
145
+ @model="$emit('open-model')"
146
+ @effort="$emit('open-effort')"
147
+ @files="$emit('files', $event)"
148
+ @voice="$emit('voice')"
149
+ @hold-start="$emit('hold-start')"
150
+ @hold-end="$emit('hold-end')"
151
+ @open-commands="$emit('open-commands')"
152
+ @open-references="$emit('open-references', $event)"
153
+ @pick-command="$emit('pick-command', $event)"
154
+ @remove-image="$emit('remove-image', $event)"
155
+ />
156
+ </div>
157
+ </Transition>
158
+ </div>
159
+
160
+ <LaresSheet :open="workspaceSheet" :title="t('desktop.workspaces')" @close="workspaceSheet = false">
161
+ <p v-if="state.workspaceError" class="lares-shell__status">{{ state.workspaceError }}</p>
162
+ <p v-else-if="!state.workspacesReady" class="lares-shell__hint">{{ t("agent.loading") }}</p>
163
+ <p v-else-if="!state.workspaces.length" class="lares-shell__hint">{{ t("desktop.noFolders") }}</p>
164
+ <LaresSettingRow
165
+ v-for="workspace in state.workspaces"
166
+ :key="workspace.workspaceId"
167
+ :label="workspace.title"
168
+ :checked="workspace.workspaceId === state.currentWorkspaceId"
169
+ @click="pickWorkspace(workspace.workspaceId)"
170
+ />
171
+ </LaresSheet>
172
+ <LaresSheet :open="state.modelSheet" :title="t('model.menuAria')" @close="$emit('close-model')">
173
+ <p v-if="state.modelError" class="lares-shell__status">{{ state.modelError }}</p>
174
+ <p v-else-if="!state.models" class="lares-shell__hint">{{ t("agent.loading") }}</p>
175
+ <p v-else-if="!state.modelGroups.length" class="lares-shell__hint">{{ t("model.empty") }}</p>
176
+ <template v-for="section in state.modelGroups" :key="section.provider">
177
+ <p class="lares-shell__heading">{{ section.provider }}</p>
178
+ <LaresSettingRow
179
+ v-for="item in section.models"
180
+ :key="modelKey(item)"
181
+ :label="item.name"
182
+ :checked="modelKey(item) === state.modelCurrent"
183
+ :disabled="state.modelBusy"
184
+ @click="$emit('choose-model', item)"
185
+ />
186
+ </template>
187
+ </LaresSheet>
188
+ <LaresSheet :open="state.effortSheet" :title="t('reasoning.title')" @close="$emit('close-effort')">
189
+ <p v-if="!state.effortRows.length" class="lares-shell__hint">{{ t("reasoning.default") }}</p>
190
+ <LaresSettingRow
191
+ v-for="row in state.effortRows"
192
+ :key="row.key"
193
+ :label="effortName(row)"
194
+ :checked="row.id === state.effortId"
195
+ :disabled="state.modelBusy"
196
+ @click="$emit('choose-effort', row.id)"
197
+ />
198
+ </LaresSheet>
199
+ <Transition name="lares-preview">
200
+ <LaresPreview
201
+ v-if="state.previewMode === 'preview' && state.preview.path"
202
+ :path="state.preview.path"
203
+ :session-id="state.sessionId"
204
+ :status="state.preview.status"
205
+ :data="state.preview.data"
206
+ :error="state.preview.error"
207
+ :media-src="state.previewMediaSrc"
208
+ :download-href="state.previewDownloadHref"
209
+ :href-for="previewHref"
210
+ :t="t"
211
+ @close="$emit('close-preview')"
212
+ @retry="$emit('open-file', state.preview.path)"
213
+ @open="$emit('open-file', $event)"
214
+ />
215
+ </Transition>
216
+ </div>
217
+ </template>
218
+
219
+ <script>
220
+ import LaresChatBar from "../chat/ChatBar.vue";
221
+ import LaresHistoryPanel from "../chat/HistoryPanel.vue";
222
+ import LaresTranscript from "../chat/Transcript.vue";
223
+ import LaresComposer from "../chat/Composer.vue";
224
+ import LaresPreview from "../preview/Preview.vue";
225
+ import LaresSheet from "../settings/Sheet.vue";
226
+ import LaresSettingRow from "../settings/SettingRow.vue";
227
+ import LaresMobileDirectorySheet from "./MobileDirectorySheet.vue";
228
+ import LaresMobileNewSession from "./MobileNewSession.vue";
229
+ import { DEFAULT_WORKSPACE_TITLE } from "@olares/lares-core/workspace/constants";
230
+
231
+ export default {
232
+ name: "LaresMobileShell",
233
+ components: {
234
+ LaresChatBar,
235
+ LaresHistoryPanel,
236
+ LaresTranscript,
237
+ LaresComposer,
238
+ LaresPreview,
239
+ LaresSheet,
240
+ LaresSettingRow,
241
+ LaresMobileDirectorySheet,
242
+ LaresMobileNewSession,
243
+ },
244
+ props: {
245
+ state: { type: Object, required: true },
246
+ t: { type: Function, required: true },
247
+ mediaUrl: { type: Function, required: true },
248
+ scrollTop: { type: Function, required: true },
249
+ rememberScroll: { type: Function, required: true },
250
+ sticking: { type: Function, required: true },
251
+ previewHref: { type: Function, required: true },
252
+ modelKey: { type: Function, required: true },
253
+ effortName: { type: Function, required: true },
254
+ },
255
+ emits: [
256
+ "history", "create", "close-history", "pick-session", "retry", "open-file",
257
+ "answer", "update-draft", "send", "open-model", "open-effort", "files",
258
+ "voice", "hold-start", "hold-end", "close-model", "choose-model",
259
+ "close-effort", "choose-effort", "close-preview",
260
+ "open-commands", "open-references", "pick-command", "remove-image",
261
+ "refresh-workspaces", "browse-workspaces", "browse-directory",
262
+ "close-directory", "choose-directory", "create-folder", "toggle-hidden",
263
+ "rename-session", "archive-session",
264
+ "move-session", "pick-workspace",
265
+ ],
266
+ data() {
267
+ return { workspaceSheet: false };
268
+ },
269
+ computed: {
270
+ newSessionHero() {
271
+ if (this.state.previewMode === "preview" && this.state.preview.path) return false;
272
+ return (
273
+ this.state.viewItems.length === 0
274
+ && !this.state.running
275
+ && !this.state.historyLoading
276
+ && !this.state.starting
277
+ );
278
+ },
279
+ barTitle() {
280
+ return this.newSessionHero ? this.t("desktop.newSession") : this.state.sessionTitle;
281
+ },
282
+ workspaceLabel() {
283
+ if (!this.state.workspacesReady) return this.t("agent.loading");
284
+ const current = this.state.workspaces.find(
285
+ (row) => row.workspaceId === this.state.currentWorkspaceId,
286
+ );
287
+ if (current?.title) return current.title;
288
+ if (this.state.workspaces[0]?.title) return this.state.workspaces[0].title;
289
+ return DEFAULT_WORKSPACE_TITLE;
290
+ },
291
+ },
292
+ methods: {
293
+ pickWorkspace(workspaceId) {
294
+ this.workspaceSheet = false;
295
+ if (!workspaceId || workspaceId === this.state.currentWorkspaceId) return;
296
+ this.$emit("pick-workspace", workspaceId);
297
+ },
298
+ forwardAnswer(label, row) {
299
+ this.$emit("answer", label, row);
300
+ },
301
+ restoreLog() {
302
+ this.$refs.transcript?.restoreLog?.();
303
+ },
304
+ captureLog() {
305
+ this.$refs.transcript?.captureLog?.();
306
+ },
307
+ pinLog() {
308
+ this.$refs.transcript?.scheduleLogPin?.();
309
+ },
310
+ focusComposer() {
311
+ this.$refs.composer?.focus?.();
312
+ },
313
+ },
314
+ };
315
+ </script>
316
+
317
+ <style scoped>
318
+ .lares-mobile-shell {
319
+ position: relative;
320
+ display: flex;
321
+ flex: 1;
322
+ min-height: 0;
323
+ flex-direction: column;
324
+ }
325
+
326
+ .lares-shell__conversation[data-hero="true"] {
327
+ justify-content: center;
328
+ }
329
+
330
+ .lares-shell__chat {
331
+ display: flex;
332
+ min-width: 0;
333
+ min-height: 0;
334
+ flex: 1;
335
+ flex-direction: column;
336
+ }
337
+
338
+ .lares-shell__status,
339
+ .lares-shell__hint {
340
+ margin: 0 20px;
341
+ font-size: 15px;
342
+ color: var(--q-ink-3);
343
+ }
344
+
345
+ .lares-shell__status {
346
+ color: var(--q-orange-default);
347
+ }
348
+
349
+ .lares-shell__retry {
350
+ align-self: flex-start;
351
+ margin: 0 20px;
352
+ border: 0;
353
+ border-radius: 8px;
354
+ padding: 8px 12px;
355
+ background: var(--q-blue-alpha);
356
+ color: var(--q-blue-default);
357
+ }
358
+
359
+ .lares-shell__heading {
360
+ margin: 0;
361
+ padding: 12px 16px 4px;
362
+ color: var(--q-ink-3);
363
+ font-size: 14px;
364
+ font-weight: 500;
365
+ line-height: 18px;
366
+ }
367
+ </style>
package/src/motion.css ADDED
@@ -0,0 +1,166 @@
1
+ /* Shared motion tokens — align with Sheet / HistoryPanel easing. */
2
+ .lares-shell {
3
+ --lares-ease-out: cubic-bezier(0.32, 0.72, 0, 1);
4
+ --lares-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
5
+ --lares-duration-fast: 120ms;
6
+ --lares-duration-normal: 220ms;
7
+ --lares-duration-sheet: 280ms;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: reduce) {
11
+ .lares-shell {
12
+ --lares-duration-fast: 0ms;
13
+ --lares-duration-normal: 0ms;
14
+ --lares-duration-sheet: 0ms;
15
+ }
16
+ }
17
+
18
+ /* Mobile full-screen preview — push from the right. */
19
+ .lares-preview-enter-active,
20
+ .lares-preview-leave-active {
21
+ transition: transform var(--lares-duration-sheet) var(--lares-ease-out);
22
+ }
23
+
24
+ .lares-preview-enter-from,
25
+ .lares-preview-leave-to {
26
+ transform: translateX(100%);
27
+ }
28
+
29
+ /* Desktop preview overlay — fade over the conversation. */
30
+ .lares-desk-preview-enter-active,
31
+ .lares-desk-preview-leave-active {
32
+ transition: opacity var(--lares-duration-normal) var(--lares-ease-out);
33
+ }
34
+
35
+ .lares-desk-preview-enter-from,
36
+ .lares-desk-preview-leave-to {
37
+ opacity: 0;
38
+ }
39
+
40
+ /* Modal dialog — backdrop fade + surface scale. */
41
+ .lares-dialog-enter-active,
42
+ .lares-dialog-leave-active {
43
+ transition: opacity var(--lares-duration-normal) var(--lares-ease-out);
44
+ }
45
+
46
+ .lares-dialog-enter-active .lares-pc-dialog__surface,
47
+ .lares-dialog-leave-active .lares-pc-dialog__surface {
48
+ transition: transform var(--lares-duration-normal) var(--lares-ease-out),
49
+ opacity var(--lares-duration-normal) var(--lares-ease-out);
50
+ }
51
+
52
+ .lares-dialog-enter-from,
53
+ .lares-dialog-leave-to {
54
+ opacity: 0;
55
+ }
56
+
57
+ .lares-dialog-enter-from .lares-pc-dialog__surface,
58
+ .lares-dialog-leave-to .lares-pc-dialog__surface {
59
+ opacity: 0;
60
+ transform: scale(0.96) translateY(8px);
61
+ }
62
+
63
+ /* Popover menu — subtle rise. */
64
+ .lares-popover-enter-active,
65
+ .lares-popover-leave-active {
66
+ transition: opacity var(--lares-duration-fast) var(--lares-ease-out),
67
+ transform var(--lares-duration-fast) var(--lares-ease-out);
68
+ }
69
+
70
+ .lares-popover-enter-from,
71
+ .lares-popover-leave-to {
72
+ opacity: 0;
73
+ transform: translateY(-4px) scale(0.98);
74
+ }
75
+
76
+ /* Mobile hero ↔ chat crossfade. */
77
+ .lares-view-enter-active,
78
+ .lares-view-leave-active {
79
+ transition: opacity var(--lares-duration-normal) var(--lares-ease-out),
80
+ transform var(--lares-duration-normal) var(--lares-ease-out);
81
+ }
82
+
83
+ .lares-view-enter-from {
84
+ opacity: 0;
85
+ transform: translateY(12px);
86
+ }
87
+
88
+ .lares-view-leave-to {
89
+ opacity: 0;
90
+ transform: translateY(-8px);
91
+ }
92
+
93
+ /* Composer panels (queue, questions, approval). */
94
+ .lares-panel-enter-active,
95
+ .lares-panel-leave-active {
96
+ transition: opacity var(--lares-duration-normal) var(--lares-ease-out),
97
+ transform var(--lares-duration-normal) var(--lares-ease-out);
98
+ }
99
+
100
+ .lares-panel-enter-from,
101
+ .lares-panel-leave-to {
102
+ opacity: 0;
103
+ transform: translateY(8px);
104
+ }
105
+
106
+ /* Preview tab strip — slide in from the header edge. */
107
+ .lares-tabs-enter-active {
108
+ transition: opacity var(--lares-duration-normal) var(--lares-ease-out),
109
+ transform var(--lares-duration-normal) var(--lares-ease-out);
110
+ }
111
+
112
+ .lares-tabs-enter-from {
113
+ opacity: 0;
114
+ transform: translateX(12px);
115
+ }
116
+
117
+ .lares-tab-enter-active,
118
+ .lares-tab-leave-active {
119
+ transition: opacity var(--lares-duration-fast) var(--lares-ease-out),
120
+ transform var(--lares-duration-fast) var(--lares-ease-out);
121
+ }
122
+
123
+ .lares-tab-enter-from,
124
+ .lares-tab-leave-to {
125
+ opacity: 0;
126
+ transform: scale(0.92);
127
+ }
128
+
129
+ .lares-tab-leave-active {
130
+ position: absolute;
131
+ }
132
+
133
+ @media (prefers-reduced-motion: reduce) {
134
+ .lares-preview-enter-active,
135
+ .lares-preview-leave-active,
136
+ .lares-desk-preview-enter-active,
137
+ .lares-desk-preview-leave-active,
138
+ .lares-dialog-enter-active,
139
+ .lares-dialog-leave-active,
140
+ .lares-dialog-enter-active .lares-pc-dialog__surface,
141
+ .lares-dialog-leave-active .lares-pc-dialog__surface,
142
+ .lares-popover-enter-active,
143
+ .lares-popover-leave-active,
144
+ .lares-view-enter-active,
145
+ .lares-view-leave-active,
146
+ .lares-panel-enter-active,
147
+ .lares-panel-leave-active,
148
+ .lares-tabs-enter-active,
149
+ .lares-tab-enter-active,
150
+ .lares-tab-leave-active {
151
+ transition: none;
152
+ }
153
+
154
+ .lares-preview-enter-from,
155
+ .lares-preview-leave-to,
156
+ .lares-view-enter-from,
157
+ .lares-view-leave-to,
158
+ .lares-panel-enter-from,
159
+ .lares-panel-leave-to,
160
+ .lares-dialog-enter-from .lares-pc-dialog__surface,
161
+ .lares-dialog-leave-to .lares-pc-dialog__surface,
162
+ .lares-popover-enter-from,
163
+ .lares-popover-leave-to {
164
+ transform: none;
165
+ }
166
+ }