feat(ui): mobile mesh tabs, AIUI-style audio player, cloud grid + map fixes
UI (this session): - Global audio player now scales the whole interface into the space above it on desktop (sidebar + main) and docks directly above the tab bar on mobile; it stays visible while navigating. - Mesh mobile redesign: floating Chat / BTC / Dead Man / AI / Map tab strip with a single fixed, internally-scrolling pane (page no longer scrolls); tabs hide while a conversation is open; floating back button; collapsible Device panel (starts collapsed); keyboard-aware conversation sizing via VisualViewport so the chat sits just above the keyboard. - Cloud file grid: uniform 4/3 card heights (folders + images match). - Swipe left/right switches tabs on the Apps and Web5 screens. - Map tool fills its pane (no bottom gap); fix skewed Share Location toggle on mobile (global min-height rule was deforming the switch). - Trim redundant helper copy from the mesh AI tab. Also bundles pre-existing in-progress work that was already in the tree: mesh listener/session + wallet + container + bitcoin-status backend changes, docker UI updates, and assorted other UI tweaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c4855526fe
commit
1bce694ebb
@@ -88,6 +88,22 @@ function addPubkey() {
|
||||
apply({ allowed_contacts: allowedContacts.value })
|
||||
}
|
||||
|
||||
// Radio/peers who recently tried `!ai` and were turned away by the policy.
|
||||
// Surfaced so the operator can one-click allow them instead of digging through
|
||||
// the journal for the firmware key. Hide any we've since allowed.
|
||||
const deniedAskers = computed(() =>
|
||||
(status.value?.denied_askers ?? []).filter(
|
||||
(d) => !d.pubkey_hex || !isAllowed(d.pubkey_hex),
|
||||
),
|
||||
)
|
||||
function allowDenied(pubkey: string | null) {
|
||||
if (!pubkey) return
|
||||
if (!isAllowed(pubkey)) {
|
||||
allowedContacts.value = [...allowedContacts.value, pubkey]
|
||||
apply({ allowed_contacts: allowedContacts.value })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mesh.fetchAssistantStatus()
|
||||
})
|
||||
@@ -138,7 +154,6 @@ function onPolicy() {
|
||||
<template>
|
||||
<div class="glass-card mesh-assistant-panel">
|
||||
<h3 class="mesh-panel-title">AI Assistant</h3>
|
||||
<p class="mesh-panel-sub">Answer questions over the mesh with AI</p>
|
||||
|
||||
<!-- Backend chooser -->
|
||||
<div class="mesh-assistant-field">
|
||||
@@ -206,10 +221,8 @@ function onPolicy() {
|
||||
<option value="trusted">Trusted nodes only</option>
|
||||
<option value="anyone">Anyone on the mesh</option>
|
||||
</select>
|
||||
<p class="text-xs text-white/40 mt-1">
|
||||
{{ policy === 'anyone'
|
||||
? 'Any peer can spend this node\'s AI budget + airtime.'
|
||||
: 'Only federation-trusted peers may ask.' }}
|
||||
<p v-if="policy === 'anyone'" class="text-xs text-white/40 mt-1">
|
||||
Any peer can spend this node's AI budget + airtime.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -217,9 +230,6 @@ function onPolicy() {
|
||||
policy is "trusted only" and they aren't federation-trusted. -->
|
||||
<div class="mesh-assistant-field">
|
||||
<label class="mesh-bitcoin-label">Always allow these contacts</label>
|
||||
<p class="text-xs text-white/40 mb-2">
|
||||
Listed contacts can use <code>!ai</code> regardless of the policy above.
|
||||
</p>
|
||||
<div v-if="contactOptions.length === 0" class="text-xs text-white/40">
|
||||
No contacts yet — they appear here once you have mesh/federation contacts.
|
||||
</div>
|
||||
@@ -256,6 +266,38 @@ function onPolicy() {
|
||||
<p v-if="pubkeyError" class="text-xs mt-1" style="color:#f87171">{{ pubkeyError }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Recently denied askers: someone tried !ai but the policy turned them
|
||||
away. Show who, and offer a one-click Allow when we know their key. -->
|
||||
<div v-if="deniedAskers.length > 0" class="mesh-assistant-field">
|
||||
<label class="mesh-bitcoin-label">Recently denied</label>
|
||||
<p class="text-xs text-white/40 mb-2">
|
||||
These tried <code>!ai</code> but the policy turned them away. Allow one to add its key.
|
||||
</p>
|
||||
<div class="mesh-assistant-allowlist">
|
||||
<div
|
||||
v-for="d in deniedAskers"
|
||||
:key="d.contact_id + (d.pubkey_hex || '')"
|
||||
class="mesh-assistant-allow-row"
|
||||
>
|
||||
<span class="mesh-assistant-allow-name" :title="d.pubkey_hex || ''">
|
||||
{{ d.name || ('#' + d.contact_id) }}
|
||||
<span v-if="d.pubkey_hex" class="text-white/30">· {{ d.pubkey_hex.slice(0, 10) }}…</span>
|
||||
</span>
|
||||
<button
|
||||
v-if="d.pubkey_hex"
|
||||
type="button"
|
||||
class="glass-button mesh-bitcoin-input-sm mesh-assistant-allow-btn"
|
||||
@click="allowDenied(d.pubkey_hex)"
|
||||
>
|
||||
Allow
|
||||
</button>
|
||||
<span v-else class="text-xs text-white/30" title="No archipelago key advertised — switch policy to 'Anyone on the mesh' to admit this device.">
|
||||
no key
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-white/50 mt-2">
|
||||
Ask from any client by sending <code>!ai <question></code> on the mesh channel.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user