Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="../assets/stackchan-favicon-32.png" type="image/png" />
<link rel="apple-touch-icon" href="../assets/stackchan-apple-touch-icon.png" />
<link rel="stylesheet" href="../global.css" />
<link rel="stylesheet" href="preference.css" />
<title>設定 | スタックチャン</title>
<script
src="https://unpkg.com/lucide@1.24.0/dist/umd/lucide.min.js"
integrity="sha384-mooE85Luwgx+AyykX7e90VcN8/QCFTSIwPuHLmvcsLVoA0en7lKYb9XlOzn5G2co"
crossorigin="anonymous"
defer
></script>
<script type="module">
import SimpleBLEClient from './ble-client.mjs'
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('form-preference')
const connectButton = document.getElementById('ble-connect-button')
const disconnectButton = document.getElementById('ble-disconnect-button')
const submitButton = document.getElementById('preference-submit-button')
const connectionStatus = document.getElementById('connection-status')
const saveStatus = document.getElementById('save-status')
const controls = [...form.querySelectorAll('input, select, textarea, button')]
let submitting = false
const currentValues = new Map()
const setStatus = (element, text, state = '') => {
element.textContent = text
element.dataset.state = state
}
const updateView = () => {
const connected = client.isConnected()
connectButton.hidden = connected
disconnectButton.hidden = !connected
controls.forEach((control) => {
control.disabled = !connected
})
setStatus(connectionStatus, connected ? '接続済み' : '未接続', connected ? 'success' : '')
}
connectButton.addEventListener('click', async () => {
connectButton.disabled = true
setStatus(connectionStatus, '接続中…')
try {
await client.connect()
} catch (error) {
console.error(error)
setStatus(connectionStatus, `接続できませんでした: ${error.message ?? error}`, 'error')
} finally {
connectButton.disabled = false
if (client.isConnected()) updateView()
}
})
disconnectButton.addEventListener('click', async () => {
disconnectButton.disabled = true
try {
await client.disconnect()
} catch (error) {
console.error(error)
setStatus(connectionStatus, `切断できませんでした: ${error.message ?? error}`, 'error')
} finally {
disconnectButton.disabled = false
if (!client.isConnected()) updateView()
}
})
form.addEventListener('submit', async (event) => {
event.preventDefault()
if (!client.isConnected() || submitting) return
const props = [
'wifi.ssid',
'wifi.password',
'driver.type',
'driver.offsetPan',
'driver.offsetTilt',
'ui.type',
'tts.type',
'tts.host',
'tts.port',
'tts.token',
'tts.voice',
'tts.volume',
'ai.token',
'ai.context',
]
const payload = {}
for (const prop of props) {
const value = document.getElementById(prop)?.value
if (value != null && value.trimEnd().length > 0 && currentValues.get(prop) !== value) {
payload[prop] = value
}
}
if (Object.keys(payload).length === 0) {
setStatus(saveStatus, '変更する項目がありません。')
return
}
submitButton.disabled = true
setStatus(saveStatus, '保存中…')
submitting = true
try {
await client.send({ _batch: payload })
for (const [prop, value] of Object.entries(payload)) currentValues.set(prop, value)
setStatus(saveStatus, '設定を送信しました。', 'success')
} catch (error) {
console.error(error)
setStatus(saveStatus, `保存できませんでした: ${error.message ?? error}`, 'error')
} finally {
submitting = false
submitButton.disabled = !client.isConnected()
}
})
const client = new SimpleBLEClient({
deviceName: 'STK',
onCharacteristicValueChanged: ({ prop, value }) => {
currentValues.set(prop, String(value))
const element = document.querySelector(`[name="${prop}"]`)
if (element != null) element.value = value
},
})
client.onDisconnected = () => {
submitting = false
currentValues.clear()
setStatus(saveStatus, '接続が切れました。保存されていない項目を確認してください。', 'warning')
updateView()
}
updateView()
globalThis.lucide?.createIcons()
})
</script>
</head>
<body>
<main class="app-shell">
<header class="topbar">
<a class="brand-link" href="../"
><img src="../assets/stackchan-symbol.png" alt="" /><span
>スタックチャン<span class="surface-name"> 設定</span></span
></a
>
<nav class="tool-nav" aria-label="ツール">
<a href="../" title="ホーム" aria-label="ホーム"><i data-lucide="home"></i></a
><a href="../flash/" title="書き込み" aria-label="書き込み"><i data-lucide="cpu"></i></a
><a href="../preference/" aria-current="page" title="設定" aria-label="設定"
><i data-lucide="sliders-horizontal"></i><span class="nav-label">設定</span></a
><a href="../simulator/" title="シミュレーター" aria-label="シミュレーター"><i data-lucide="box"></i></a
><a href="../editor/" title="エディタ" aria-label="エディタ"><i data-lucide="blocks"></i></a>
</nav>
</header>
<div class="preference-layout">
<aside class="connection-pane">
<div class="page-heading">
<h1>本体設定</h1>
<p>BLEでスタックチャンに接続します。</p>
</div>
<button id="ble-connect-button" class="primary-button" type="button">
<i data-lucide="bluetooth"></i><span>BLEで接続</span>
</button>
<button id="ble-disconnect-button" class="danger-button" type="button" hidden>
<i data-lucide="unplug"></i><span>切断</span>
</button>
<p id="connection-status" class="status-line" role="status">未接続</p>
</aside>
<section id="settings-form" class="settings-pane" aria-label="設定項目">
<form id="form-preference">
<section class="section-block">
<h2>Wi-Fi</h2>
<div class="form-grid">
<label class="form-field"
><span>SSID</span><input type="text" name="wifi.ssid" id="wifi.ssid" autocomplete="off" /></label
><label class="form-field"
><span>パスワード</span
><input type="password" name="wifi.password" id="wifi.password" autocomplete="off"
/></label>
</div>
</section>
<section class="section-block">
<h2>外観</h2>
<div class="form-grid">
<label class="form-field"
><span>顔の種類</span
><select name="ui.type" id="ui.type">
<option value="simple">シンプル</option>
<option value="dog">いぬ</option>
</select></label
>
</div>
</section>
<section class="section-block">
<h2>サーボ</h2>
<div class="form-grid">
<label class="form-field form-field-wide"
><span>ドライバー</span
><select name="driver.type" id="driver.type">
<option value="scservo">SCServo</option>
<option value="dynamixel">Dynamixel(Protocol 2)</option>
<option value="rs30x">RS30X</option>
<option value="pwm">PWM(SG-90)</option>
<option value="none">なし</option>
</select></label
><label class="form-field"
><span>パン オフセット</span
><input type="number" name="driver.offsetPan" id="driver.offsetPan" value="0" /></label
><label class="form-field"
><span>チルト オフセット</span
><input type="number" name="driver.offsetTilt" id="driver.offsetTilt" value="0"
/></label>
</div>
</section>
<section class="section-block">
<h2>音声合成</h2>
<div class="form-grid">
<label class="form-field"
><span>サービス</span
><select name="tts.type" id="tts.type">
<option value="voicevox">VOICEVOX</option>
<option value="elevenlabs">ElevenLabs</option>
<option value="google-tts">Google TTS</option>
<option value="openai">OpenAI</option>
<option value="local">ローカル</option>
</select></label
><label class="form-field"
><span>ホスト</span
><input type="text" name="tts.host" id="tts.host" placeholder="my-tts-host.local" /></label
><label class="form-field"
><span>ポート</span><input type="number" name="tts.port" id="tts.port" placeholder="50021" /></label
><label class="form-field"
><span>音声</span><input type="text" name="tts.voice" id="tts.voice" placeholder="ally" /></label
><label class="form-field"
><span>トークン</span><input type="password" name="tts.token" id="tts.token" /></label
><label class="form-field"
><span>音量(0–1)</span
><input type="number" name="tts.volume" id="tts.volume" step="0.1" min="0" max="1"
/></label>
</div>
</section>
<section class="section-block">
<h2>AI</h2>
<div class="form-grid">
<label class="form-field"
><span>トークン</span><input type="password" name="ai.token" id="ai.token" /></label
><label class="form-field form-field-full"
><span>システムロール</span
><textarea
name="ai.context"
id="ai.context"
rows="5"
placeholder="You are Stack-chan(スタックチャン), the palm sized super kawaii companion robot."
></textarea>
</label>
</div>
</section>
<footer class="form-actions">
<p id="save-status" class="status-line" role="status"></p>
<button id="preference-submit-button" class="primary-button" type="submit">
<i data-lucide="save"></i><span>設定を保存</span>
</button>
</footer>
</form>
</section>
</div>
</main>
</body>
</html>
f5c9f52c6This file is pinned to the selected release. Download the original to open it in your design tool.