$def with (resource)
$ path = tx.request.uri.path
$ owner = tx.host.owner
<html lang=en-us class=dark>
<head>
<meta charset=utf-8>
<meta name=viewport
content=initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width>
<meta name=apple-mobile-web-app-capable content=yes>
<meta name=apple-mobile-web-app-status-bar-style content=black-translucent>
$if "photo" in owner:
<link rel=icon href=/media/$owner["photo"][0]>
$if not tx.request.uri.path:
<link rel=manifest href=/manifest.json>
<link rel=stylesheet href=/chats/mediasoup-demo-app.css>
<link rel=stylesheet href=/static/screen.css media=screen>
<title>\
$if "title" in resource:
$:Document(resource.title).doc.text_content() — \
$owner["name"][0]</title>
$if not isinstance(resource, str) and "head" in resource:
$:resource.head()
<link rel=stylesheet href=/static/asciinema-player.css>
<script type=module>
import { _, cookies, go, upgradeLink } from '/static/web.js'
$if "session" in tx.user and tx.user.session.get("uid", None):
let userName = '$tx.user.session["name"][0]'
$else:
let userName = 'Guest'
// TODO $$.load(...)
document.addEventListener('DOMContentLoaded', ev => {
if (cookies.get('rhythm') == 'on') {
document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
}
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key == '.') {
if (cookies.get('rhythm') == 'on') {
document.querySelector('body').style.backgroundImage = 'none'
cookies.set('rhythm', 'off')
} else {
document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
cookies.set('rhythm', 'on')
}
}
})
cookies.set('mediasoup-demo.user', `{"displayName": "$${userName}"}`)
// TODO const owner = "$owner['name'][0]"
_('a:not(.breakout)').each(upgradeLink)
history.pushState({scroll: 0}, 'title', window.location)
window.go = go
})
</script>
$if tx.user.is_owner:
<style>
.h-card img {
border: .2em solid #007ba7;
height: 2.6em; }
</style>
</head>
<body
$if "body_classes" in resource:
class="$' '.join(resource.body_classes)"\
>
<header>
<div class=h-card>
$if "photo" in owner:
<img class=u-photo src=/media/$owner["photo"][0]>
<div class=namedesc>
<a class=p-name href=/><strong>$owner["name"][0]</strong></a><br>
$if "note" in owner:
<span class=p-note>$owner["note"][0]</span><br>
<small class=identifiers>
$if email := get_first(owner, "email"):
<a class=u-email
href=mailto:$owner["email"][0]><code>$owner["email"][0]</code></a> •
$if onion := get_onion():
$ _stripped_onion = onion.lstrip("x")
$ _prefix_length = len(onion) - len(_stripped_onion)
<a class=u-url rel=me
href=http://$onion><code>@$_stripped_onion[:12-_prefix_length]</code></a> •
<code><a class="u-url u-uid" rel=me href=/>@$tx.host.name</code>
$if nickname := get_first(owner, "nickname"):
• <a href=/owner/actor><code>@$nickname@$tx.host.name</code></a>
$if key := get_key():
• <a class=p-key href=/owner/keyring><code>$:"–".join(key.split()[:2])</code></a>
</small>
</div>
</div>
<div>
<form id=search action=/search>
<input class=partial name=q type=text> <button>Query</button>
</form>
<button id=listen>Listen for Queries</button>
</div>
</header>
$def render_breadcrumbs(breadcrumbs, separator="▸", padding=" "):
$ """
$ Render a `separator` delimited list of linkified `breadcrumbs`.
$
$ `breadcrumbs` should be a single tuple that will be read two items at a time:
$
$ ("path", "Name", "subpath", "Subname", ...)
$
$ """
$ remaining = int(len(breadcrumbs) / 2)
$ path = ""
$for crumb_path, crumb_title in zip(*(breadcrumbs[i::2] for i in (0, 1))):
$ crumb_path = str(crumb_path)
$ crumb_icon, crumb_classes = None, None
$if isinstance(crumb_title, tuple):
$ crumb_icon, crumb_classes, crumb_title = crumb_title
$ path = path + "/" + crumb_path
$ ups = " ".join(["up"] * remaining)
$ remaining = remaining - 1
<span>\
$if crumb_icon:
<span class="crumb-icon-$crumb_icon"></span> \
<a href=$path \
$if crumb_classes:
class="$' '.join(crumb_classes)"
rel="$ups">$:crumb_title</a>\
$:padding<span class=crumb-sep>$:separator</span></span>\
<article id=content\
$if "classes" in resource:
class="$' '.join(resource.classes)"\
>
$if path:
$ breadcrumbs = []
$ parts = path.split("/")
$ app = parts[0]
$if len(parts) > 1:
$ breadcrumbs += [app, app.capitalize()]
$if "breadcrumbs" in resource:
$ breadcrumbs += list(resource["breadcrumbs"])
$if breadcrumbs:
<nav class=breadcrumbs>$:render_breadcrumbs(breadcrumbs)</nav>
$if "title" in resource:
$if getattr(resource, "show_title", True):
<header>
<h1
$if "title_classes" in resource:
class="$' '.join(resource.title_classes)"\
>$:resource.title</h1>
</header>
$:resource
$# $if "hide_footer" not in resource:
$# <footer>
$# <p><small>Content licensed <a
$# href=//creativecommons.org/licenses/by-nc-sa/4.0/ rel=license><abbr
$# title="Creative Commons Attribution-NonCommercial-ShareAlike">CC
$# BY-NC-SA</abbr></a> unless otherwise noted.</small></p>
$# <!--p><a href=//indieweb.rocks/$tx.host.name rel=me><img alt="IndieMark score"
$# src=//indieweb.rocks/sites/$tx.host.name/scoreboard.svg style=height:4em></a></p-->
$# </footer>
</article>
<nav>
<a href=/now>Now</a>
$ public_apps = {"code": "Code"}
$# , "media": "Media"}
<ul class=apps>
$for app_prefix, app_name in public_apps.items():
<li>
$# XXX $if path.startswith(app_prefix):
$# XXX <strong>$app_name</strong>
$# XXX $else:
<a href=/$app_prefix>$app_name</a>
</li>
</ul>
<ul class=dates>
$for year, months in get_months().items():
<li><small><a href=/$year>$year</a></small>\
$# <div style=columns:6>
$# $for month in range(1, 13):
$# $ MM = f"{month:02}"
$# $if months[month]:
$# <a href=/$year/$MM>\
$# $MM\
$# $if months[month]:
$# </a>\
$#  \
$# </div>
</li>
</ul>
<p>
$for category in get_categories():
$category
</p>
$if tx.user.is_owner:
<ul class=admin>
$for mount_name, mount_app in sorted(tx.app.mounts):
$if mount_name in public_apps or mount_name == "system":
$continue
<li><a href=/$mount_app.prefix>$mount_name.capitalize()</a></li>
</ul>
$if tx.user.is_owner:
<form id=identity action=/owner/sign-out method=post>
<button>Sign Out</button>
</form>
$elif "session" in tx.user and tx.user.session.get("uid", None):
<p>Signed in as <a
href=$tx.user.session["uid"][0]>$tx.user.session["name"][0]</a>.</p>
<form id=identity action=/guests/sign-out method=post>
<button>Sign Out</button>
</form>
$else:
<form id=identity action=/guests/sign-in style="margin:1em 0">
<!--input name=me type=text--> <button>Sign In</button>
</form>
<p class=system><a class=systemtext href=/system
title=system><span>System</span></a></p>
</nav>
<aside>
$:livestream()
<div style=text-align:right><button id=enter>Enter Room</button></div>
<div id=chat>
<div id=mediasoup-demo-app-media-query-detector></div>
<div id=mediasoup-demo-app-container></div>
</div>
$if "aside" in resource:
$:resource.aside
</aside>
<footer style=display:none id=loading>loading</footer>
<script src=https://cdn.jsdelivr.net/npm/vosk-browser@0.0.5/dist/vosk.js></script>
<script>
async function initAssistant() {
const partialContainer = document.querySelector('#search .partial')
partialContainer.value = 'Loading...'
const channel = new MessageChannel()
const model = await Vosk.createModel('/static/vosk-model-small-en-us-0.15.tar.gz')
model.registerPort(channel.port1)
const sampleRate = 48000
const recognizer = new model.KaldiRecognizer(sampleRate)
recognizer.setWords(true)
let ownerGivenName = '$owner["name"][0].split()[0]'
let wakeWord = 'query plus'
let readyWord = `Say "query plus help".`
let state = 'asleep'
partialContainer.value = readyWord
recognizer.on('result', message => {
let input = message.result.text
if (input.slice(0, wakeWord.length) != wakeWord) {
partialContainer.value = readyWord
return
}
input = input.slice(wakeWord.length + 1)
if (input == 'help') {
go('/help')
return
}
const response = fetch('/ai/assistant', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({request: input}),
})
.then(response => response.blob())
.then(blob => {
const audio = new Audio(URL.createObjectURL(blob));
audio.addEventListener('canplaythrough', () => {
audio.play()
state = 'asleep'
partialContainer.value = readyWord
})
})
partialContainer.value = `$${input}`
})
recognizer.on('partialresult', message => {
const input = message.result.partial
if (input.slice(0, wakeWord.length) != wakeWord)
return
state = 'awake'
partialContainer.value = input.slice(wakeWord.length)
})
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: {
echoCancellation: true,
noiseSuppression: true,
channelCount: 1,
sampleRate
},
})
const audioContext = new AudioContext()
await audioContext.audioWorklet.addModule('/static/recognizer-processor.js')
const recognizerProcessor = new AudioWorkletNode(
audioContext,
'recognizer-processor',
{ channelCount: 1, numberOfInputs: 1, numberOfOutputs: 1 }
)
recognizerProcessor.port.postMessage(
{ action: 'init', recognizerId: recognizer.id },
[ channel.port2 ]
)
recognizerProcessor.connect(audioContext.destination)
const source = audioContext.createMediaStreamSource(mediaStream)
source.connect(recognizerProcessor)
}
</script>
<script src=/chats/resources/js/antiglobal.js></script>
<script>
window.localStorage.setItem('debug', '* -engine* -socket* -RIE* *WARN* *ERROR*')
if (window.antiglobal) {
window.antiglobal('___browserSync___oldSocketIo', 'io', '___browserSync___', '__core-js_shared__')
setInterval(window.antiglobal, 180000)
}
function initChat() {
var tag = document.createElement('script')
tag.src = '/chats/mediasoup-demo-app.js'
document.getElementsByTagName('head')[0].appendChild(tag)
const autoMuter = setInterval(autoMute, 100)
function autoMute() {
if (typeof window.CLIENT !== 'undefined' && window.CLIENT._micProducer) {
window.CLIENT.muteMic()
clearInterval(autoMuter)
}
}
}
</script>
<script>
window.onload = () => {
const listenTrigger = document.querySelector('button#listen')
listenTrigger.onmouseup = () => {
listenTrigger.disabled = true
listenTrigger.style.display = 'none'
initAssistant()
}
const enterTrigger = document.querySelector('button#enter')
enterTrigger.onmouseup = () => {
enterTrigger.disabled = true
enterTrigger.style.display = 'none'
initChat()
}
}
</script>
<script src=https://cdn.jsdelivr.net/npm/webtorrent@latest/webtorrent.min.js></script>
<script src=https://cdn.jsdelivr.net/npm/drag-drop@latest/dragdrop.min.js></script>
<script type=module>
const client = new WebTorrent()
DragDrop('body', (files, pos, fileList, directories) => {
console.log(files[0])
// client.seed(files, torrent => {
// console.log('Client is seeding ' + torrent.magnetURI)
// })
})
</script>
</body>
</html>