2026-03-30 14:23:28 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="ja" data-app-id="posimai-dev">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="robots" content="noindex, nofollow">
|
|
|
|
|
|
<script>
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
var t = localStorage.getItem('posimai-dev-theme') || 'dark';
|
|
|
|
|
|
document.documentElement.setAttribute('data-theme', t === 'light' ? 'light' : 'dark');
|
|
|
|
|
|
document.documentElement.setAttribute('data-theme-pref', t);
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
|
|
|
|
<meta name="description" content="posimai development portal">
|
|
|
|
|
|
<meta name="color-scheme" content="dark light">
|
2026-03-30 15:42:16 +00:00
|
|
|
|
<meta name="theme-color" content="#0C1221">
|
2026-03-30 14:23:28 +00:00
|
|
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
|
|
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
|
|
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
|
|
|
|
<meta name="apple-mobile-web-app-title" content="posimai-dev">
|
|
|
|
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
|
|
|
<link rel="icon" type="image/png" href="/logo.png">
|
|
|
|
|
|
<link rel="apple-touch-icon" href="/logo.png">
|
|
|
|
|
|
<title>posimai-dev</title>
|
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
|
|
|
|
|
<link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css">
|
2026-04-17 09:42:38 +00:00
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/@xterm/xterm@6.0.0/css/xterm.css" integrity="sha384-n2n7twoohnW+d3myBKaUgl7DSiwidw6MkQy9oesGzkPpMjejKRR3XlnD+5yCdtBD" crossorigin="anonymous">
|
2026-04-16 23:12:36 +00:00
|
|
|
|
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js" integrity="sha384-tTkFttkBclaU1cloKwOi9xk3pbao3VZxTjLNBt8iFABWDBQibbAbWpVmO28zMuxq" crossorigin="anonymous"></script>
|
2026-04-17 09:42:38 +00:00
|
|
|
|
<script src="https://unpkg.com/@xterm/xterm@6.0.0/lib/xterm.js" integrity="sha384-f/1U6Z9wM4D71a5eRXEZnyOTMOvjqxr2XLwh+Go1OvIl3L3tOcvUrzudnhbECwl4" crossorigin="anonymous"></script>
|
|
|
|
|
|
<script src="https://unpkg.com/@xterm/addon-fit@0.11.0/lib/addon-fit.js" integrity="sha384-txoiwu4RR2GD3qySbaj+BbzibkLbSJRcfqGYMu6z1EqHil4A2dyBiBW5dlacG6OR" crossorigin="anonymous"></script>
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
:root {
|
|
|
|
|
|
--accent: #A78BFA;
|
|
|
|
|
|
--accent-dim: rgba(167, 139, 250, 0.15);
|
2026-03-30 15:25:44 +00:00
|
|
|
|
--dev-bg: #0C1221;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
[data-theme="light"] { --accent: #7C3AED; }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
|
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100dvh;
|
|
|
|
|
|
overflow: hidden;
|
2026-03-30 15:25:44 +00:00
|
|
|
|
background: var(--dev-bg);
|
2026-03-30 15:42:16 +00:00
|
|
|
|
font-family: Inter, sans-serif;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
/* ── Header ── */
|
2026-03-30 14:23:28 +00:00
|
|
|
|
.header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
height: 48px;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
border-bottom: 1px solid rgba(255,255,255,0.06);
|
2026-03-30 14:23:28 +00:00
|
|
|
|
flex-shrink: 0;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
background: rgba(12, 18, 33, 0.8);
|
|
|
|
|
|
backdrop-filter: blur(12px);
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.header-left { display: flex; align-items: center; gap: 10px; }
|
2026-03-30 16:13:59 +00:00
|
|
|
|
.header-dot {
|
|
|
|
|
|
width: 8px; height: 8px; border-radius: 50%;
|
|
|
|
|
|
background: #4B5563;
|
|
|
|
|
|
transition: background 0.4s;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.header-dot.connected {
|
|
|
|
|
|
background: #6EE7B7;
|
|
|
|
|
|
animation: dot-pulse 2.5s ease-in-out infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
.header-dot.disconnected { background: #F87171; animation: none; }
|
|
|
|
|
|
@keyframes dot-pulse {
|
|
|
|
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(110,231,183,0.5); }
|
|
|
|
|
|
50% { box-shadow: 0 0 0 5px rgba(110,231,183,0); }
|
|
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.header-title { font-size: 14px; font-weight: 600; color: #F3F4F6; letter-spacing: -0.01em; }
|
|
|
|
|
|
.header-right { display: flex; align-items: center; gap: 4px; }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:13:59 +00:00
|
|
|
|
/* status-badge は dot に統合したため非表示 */
|
|
|
|
|
|
.status-badge { display: none; }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
/* Claude 開始ボタン */
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.claude-btn {
|
2026-03-30 15:55:12 +00:00
|
|
|
|
width: 36px; height: 36px; border-radius: 8px; border: none; cursor: pointer;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
background: var(--accent-dim); color: var(--accent);
|
2026-03-30 15:48:41 +00:00
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
|
flex-shrink: 0; transition: background 0.15s;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.claude-btn:hover { background: rgba(167,139,250,0.25); }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
/* icon-btn */
|
|
|
|
|
|
.icon-btn { color: rgba(255,255,255,0.5); }
|
|
|
|
|
|
.icon-btn:hover { color: #F3F4F6; background: rgba(255,255,255,0.06); }
|
2026-03-30 15:55:12 +00:00
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
/* ── Terminal ── */
|
2026-03-30 14:23:28 +00:00
|
|
|
|
#terminal-container {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: hidden;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
padding: 12px 12px 0;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
min-height: 0;
|
2026-03-30 15:25:44 +00:00
|
|
|
|
background:
|
2026-03-30 15:55:12 +00:00
|
|
|
|
radial-gradient(ellipse at 15% 60%, rgba(34,211,238,0.14) 0%, transparent 55%),
|
|
|
|
|
|
radial-gradient(ellipse at 85% 25%, rgba(167,139,250,0.14) 0%, transparent 55%),
|
2026-03-30 15:25:44 +00:00
|
|
|
|
var(--dev-bg);
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
#terminal-container .xterm { height: 100%; }
|
|
|
|
|
|
#terminal-container .xterm-viewport { border-radius: 8px 8px 0 0; }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 22:26:06 +00:00
|
|
|
|
/* xterm 内部の不透明レイヤーを透明化 → aurora が透ける */
|
|
|
|
|
|
#terminal-container .xterm-viewport,
|
|
|
|
|
|
#terminal-container .xterm-screen,
|
|
|
|
|
|
#terminal-container .xterm-screen canvas { background: transparent !important; }
|
|
|
|
|
|
|
|
|
|
|
|
/* スクロールバー — 右端に固定、細く鮮明に */
|
|
|
|
|
|
.xterm-viewport { scrollbar-width: thin; scrollbar-color: rgba(167,139,250,0.35) transparent; }
|
|
|
|
|
|
.xterm-viewport::-webkit-scrollbar { width: 5px; }
|
|
|
|
|
|
.xterm-viewport::-webkit-scrollbar-track { background: rgba(255,255,255,0.03); border-radius: 3px; }
|
|
|
|
|
|
.xterm-viewport::-webkit-scrollbar-thumb { background: rgba(167,139,250,0.4); border-radius: 3px; }
|
|
|
|
|
|
.xterm-viewport::-webkit-scrollbar-thumb:hover { background: rgba(167,139,250,0.7); }
|
2026-03-30 15:48:41 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
/* ── Input wrapper (relative anchor for slash popup) ── */
|
|
|
|
|
|
.input-wrapper {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
background: rgba(12, 18, 33, 0.9);
|
|
|
|
|
|
border-top: 1px solid rgba(255,255,255,0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Quick chips ── */
|
|
|
|
|
|
.chips-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 8px 12px 4px;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chips-bar::-webkit-scrollbar { display: none; }
|
|
|
|
|
|
|
|
|
|
|
|
.chip {
|
|
|
|
|
|
display: flex; align-items: center; gap: 5px;
|
|
|
|
|
|
padding: 4px 10px; border-radius: 20px;
|
|
|
|
|
|
border: 1px solid rgba(255,255,255,0.07);
|
|
|
|
|
|
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.45);
|
|
|
|
|
|
font-size: 11px; font-weight: 500; font-family: Inter, sans-serif;
|
|
|
|
|
|
cursor: pointer; white-space: nowrap; flex-shrink: 0;
|
|
|
|
|
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip:hover { background: var(--accent-dim); color: var(--accent); border-color: rgba(167,139,250,0.2); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Slash command popup ── */
|
|
|
|
|
|
.slash-popup {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: 100%;
|
|
|
|
|
|
left: 12px; right: 12px;
|
|
|
|
|
|
background: rgba(10, 14, 28, 0.98);
|
|
|
|
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
box-shadow: 0 -8px 32px rgba(0,0,0,0.5);
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
z-index: 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
.slash-popup.open { display: block; }
|
|
|
|
|
|
|
|
|
|
|
|
.slash-item {
|
|
|
|
|
|
display: flex; align-items: center; gap: 12px;
|
|
|
|
|
|
padding: 10px 14px; cursor: pointer;
|
|
|
|
|
|
transition: background 0.1s;
|
|
|
|
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
.slash-item:last-child { border-bottom: none; }
|
|
|
|
|
|
.slash-item:hover, .slash-item.active { background: rgba(167,139,250,0.1); }
|
|
|
|
|
|
.slash-cmd {
|
|
|
|
|
|
font-family: monospace; font-size: 12px; font-weight: 600;
|
|
|
|
|
|
color: var(--accent); min-width: 82px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.slash-label { font-size: 12px; color: rgba(255,255,255,0.35); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Chat bar ── */
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.chat-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2026-03-30 16:04:47 +00:00
|
|
|
|
padding: 6px 12px;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 16:04:47 +00:00
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.chat-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
background: rgba(255,255,255,0.05);
|
|
|
|
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 9px 14px;
|
|
|
|
|
|
color: #F3F4F6;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-family: Inter, sans-serif;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
transition: border-color 0.15s;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.chat-input:focus { border-color: rgba(167,139,250,0.4); }
|
2026-03-30 16:04:47 +00:00
|
|
|
|
|
|
|
|
|
|
.mic-btn {
|
|
|
|
|
|
width: 38px; height: 38px; border-radius: 10px; border: none;
|
|
|
|
|
|
background: rgba(255,255,255,0.05); color: rgba(255,255,255,0.35); cursor: pointer;
|
|
|
|
|
|
display: none; align-items: center; justify-content: center;
|
|
|
|
|
|
flex-shrink: 0; transition: background 0.15s, color 0.15s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.mic-btn.available { display: flex; }
|
|
|
|
|
|
.mic-btn:hover { background: rgba(255,255,255,0.1); color: #F3F4F6; }
|
|
|
|
|
|
.mic-btn.listening {
|
|
|
|
|
|
background: rgba(239,68,68,0.15); color: #F87171;
|
|
|
|
|
|
animation: mic-pulse 1s ease-in-out infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes mic-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.send-btn {
|
|
|
|
|
|
width: 38px; height: 38px; border-radius: 10px; border: none;
|
|
|
|
|
|
background: var(--accent); color: #0C1221; cursor: pointer;
|
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
|
flex-shrink: 0; transition: opacity 0.15s;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
2026-03-30 15:42:16 +00:00
|
|
|
|
.send-btn:hover { opacity: 0.85; }
|
|
|
|
|
|
.send-btn:disabled { opacity: 0.3; cursor: default; }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
/* ── Settings panel extras ── */
|
2026-03-30 14:23:28 +00:00
|
|
|
|
.overlay { display: none; }
|
|
|
|
|
|
.settings-panel { z-index: 1000; }
|
2026-03-30 15:42:16 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
.session-info-row {
|
|
|
|
|
|
display: flex; align-items: center; gap: 8px; margin-top: 8px;
|
|
|
|
|
|
font-size: 11px; color: rgba(255,255,255,0.35); font-family: monospace;
|
|
|
|
|
|
background: rgba(255,255,255,0.04); border-radius: 8px; padding: 8px 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.session-info-row.hidden { display: none; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Toggle switch */
|
|
|
|
|
|
.toggle-row {
|
|
|
|
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
|
|
|
|
margin-top: 10px; cursor: pointer; user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.toggle-label { font-size: 13px; color: rgba(255,255,255,0.55); }
|
|
|
|
|
|
.toggle-wrap { position: relative; display: inline-block; width: 38px; height: 22px; flex-shrink: 0; }
|
|
|
|
|
|
.toggle-wrap input { position: absolute; opacity: 0; width: 0; height: 0; }
|
|
|
|
|
|
.toggle-track {
|
|
|
|
|
|
position: absolute; inset: 0; border-radius: 11px;
|
|
|
|
|
|
background: rgba(255,255,255,0.1); transition: background 0.2s; cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.toggle-track::after {
|
|
|
|
|
|
content: ''; position: absolute; top: 3px; left: 3px;
|
|
|
|
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
|
|
|
|
background: rgba(255,255,255,0.35); transition: transform 0.2s, background 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.toggle-wrap input:checked + .toggle-track { background: rgba(167,139,250,0.3); }
|
|
|
|
|
|
.toggle-wrap input:checked + .toggle-track::after { transform: translateX(16px); background: var(--accent); }
|
2026-03-30 14:23:28 +00:00
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
|
|
<aside class="settings-panel" id="settingsPanel" role="complementary">
|
|
|
|
|
|
<div class="settings-panel-header">
|
|
|
|
|
|
<span class="settings-panel-title">設定</span>
|
|
|
|
|
|
<button class="icon-btn" id="settingsCloseBtn" aria-label="設定を閉じる">
|
|
|
|
|
|
<i data-lucide="x" style="width:18px;height:18px;stroke-width:1.75"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="settings-panel-body">
|
|
|
|
|
|
<div>
|
2026-03-30 14:29:39 +00:00
|
|
|
|
<div class="settings-group-label">テーマ</div>
|
|
|
|
|
|
<div class="theme-selector">
|
|
|
|
|
|
<button class="theme-btn" data-theme-val="dark"><i data-lucide="moon" style="width:12px;height:12px;stroke-width:1.75"></i>ダーク</button>
|
|
|
|
|
|
<button class="theme-btn" data-theme-val="light"><i data-lucide="sun" style="width:12px;height:12px;stroke-width:1.75"></i>ライト</button>
|
|
|
|
|
|
<button class="theme-btn" data-theme-val="system"><i data-lucide="monitor" style="width:12px;height:12px;stroke-width:1.75"></i>自動</button>
|
2026-03-30 14:23:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-30 16:04:47 +00:00
|
|
|
|
<div style="margin-top:20px">
|
|
|
|
|
|
<div class="settings-group-label">音声入力</div>
|
|
|
|
|
|
<label class="toggle-row">
|
|
|
|
|
|
<span class="toggle-label">認識後に自動送信</span>
|
|
|
|
|
|
<span class="toggle-wrap">
|
|
|
|
|
|
<input type="checkbox" id="voiceAutosend">
|
|
|
|
|
|
<span class="toggle-track"></span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
2026-03-30 15:42:16 +00:00
|
|
|
|
<div style="margin-top:20px">
|
|
|
|
|
|
<div class="settings-group-label">セッション</div>
|
2026-03-30 15:55:12 +00:00
|
|
|
|
<div class="session-info-row hidden" id="settingsSessionBadge">
|
|
|
|
|
|
<i data-lucide="terminal" style="width:12px;height:12px;stroke-width:1.75;color:var(--accent);flex-shrink:0"></i>
|
|
|
|
|
|
<span id="settingsSessionId">—</span>
|
|
|
|
|
|
</div>
|
2026-04-17 09:42:38 +00:00
|
|
|
|
<a href="/sessions.html" style="font-size:13px;color:var(--accent);text-decoration:none;display:flex;align-items:center;gap:6px;margin-top:10px" rel="noopener noreferrer">
|
2026-03-30 15:42:16 +00:00
|
|
|
|
<i data-lucide="history" style="width:14px;height:14px;stroke-width:1.75"></i>
|
|
|
|
|
|
過去のセッションを見る
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</div>
|
2026-03-30 14:23:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<header class="header">
|
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
|
<div class="header-dot" aria-hidden="true"></div>
|
|
|
|
|
|
<span class="header-title">posimai-dev</span>
|
|
|
|
|
|
<span class="status-badge disconnected" id="statusBadge">接続中...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="header-right">
|
2026-03-30 15:48:41 +00:00
|
|
|
|
<button class="claude-btn" id="claudeBtn" aria-label="Claude 開始">
|
2026-03-30 15:55:12 +00:00
|
|
|
|
<i data-lucide="bot" style="width:20px;height:20px;stroke-width:1.75"></i>
|
2026-03-30 14:23:28 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
<button class="icon-btn" id="settingsBtn" aria-label="設定" aria-expanded="false">
|
|
|
|
|
|
<i data-lucide="settings" style="width:18px;height:18px;stroke-width:1.5"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="terminal-container"></div>
|
|
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
<div class="input-wrapper">
|
|
|
|
|
|
<!-- スラッシュコマンドポップアップ -->
|
|
|
|
|
|
<div class="slash-popup" id="slashPopup" role="listbox"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- クイックチップス -->
|
|
|
|
|
|
<div class="chips-bar" id="chipsBar"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- チャットバー -->
|
|
|
|
|
|
<div class="chat-bar">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="chat-input"
|
|
|
|
|
|
id="chatInput"
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
autocorrect="off"
|
|
|
|
|
|
autocapitalize="off"
|
|
|
|
|
|
spellcheck="false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<button class="mic-btn" id="micBtn" aria-label="音声入力">
|
|
|
|
|
|
<i data-lucide="mic" style="width:16px;height:16px;stroke-width:1.75"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="send-btn" id="sendBtn" disabled aria-label="送信">
|
|
|
|
|
|
<i data-lucide="arrow-up" style="width:16px;height:16px;stroke-width:2.5"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-03-30 15:42:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-30 14:23:28 +00:00
|
|
|
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
(function () {
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// ── Elements ──
|
2026-03-30 16:13:59 +00:00
|
|
|
|
const statusBadge = document.getElementById('statusBadge'); // hidden, kept for compat
|
|
|
|
|
|
const headerDot = document.querySelector('.header-dot');
|
2026-03-30 15:55:12 +00:00
|
|
|
|
const settingsSessionRow = document.getElementById('settingsSessionBadge');
|
|
|
|
|
|
const settingsSessionId = document.getElementById('settingsSessionId');
|
|
|
|
|
|
const container = document.getElementById('terminal-container');
|
|
|
|
|
|
const chatInput = document.getElementById('chatInput');
|
|
|
|
|
|
const sendBtn = document.getElementById('sendBtn');
|
|
|
|
|
|
const claudeBtn = document.getElementById('claudeBtn');
|
|
|
|
|
|
const micBtn = document.getElementById('micBtn');
|
2026-03-30 16:04:47 +00:00
|
|
|
|
const slashPopup = document.getElementById('slashPopup');
|
|
|
|
|
|
const chipsBar = document.getElementById('chipsBar');
|
|
|
|
|
|
const voiceAutosend = document.getElementById('voiceAutosend');
|
|
|
|
|
|
|
|
|
|
|
|
// ── Slash commands & chips ──
|
|
|
|
|
|
const COMMANDS = [
|
2026-03-30 16:13:59 +00:00
|
|
|
|
{ cmd: '/todo', label: '今日のタスク', prompt: 'おはよう。git logと変更ファイルを確認して、今日やるべき作業を整理して提案して' },
|
|
|
|
|
|
{ cmd: '/status', label: '状況確認', prompt: 'git statusと直近のコミットを確認して、今の作業状態をわかりやすく教えて' },
|
|
|
|
|
|
{ cmd: '/commit', label: 'コミット', prompt: '変更内容を確認してコミットメッセージの案を出して、問題なければコミットして' },
|
|
|
|
|
|
{ cmd: '/deploy', label: 'デプロイ', prompt: 'npm run deployを実行してデプロイして' },
|
|
|
|
|
|
{ cmd: '/fix', label: 'エラー修正', prompt: '直前のエラーを確認して原因と修正方法を教えて、修正できるなら直して' },
|
|
|
|
|
|
{ cmd: '/explain', label: 'コード説明', prompt: '今の変更内容またはエラーをわかりやすく説明して' },
|
|
|
|
|
|
{ cmd: '/test', label: 'テスト', prompt: 'テストを実行して結果を教えて' },
|
2026-03-30 16:04:47 +00:00
|
|
|
|
{ cmd: '/ls', label: 'ファイル確認', prompt: 'カレントディレクトリの構造を確認して教えて' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// クイックチップス(最初の5件を常時表示)
|
|
|
|
|
|
const CHIPS = COMMANDS.slice(0, 5);
|
|
|
|
|
|
CHIPS.forEach((c) => {
|
|
|
|
|
|
const btn = document.createElement('button');
|
|
|
|
|
|
btn.className = 'chip';
|
|
|
|
|
|
btn.textContent = c.label;
|
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
|
sendInput(c.prompt + '\n');
|
|
|
|
|
|
});
|
|
|
|
|
|
chipsBar.appendChild(btn);
|
|
|
|
|
|
});
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// スラッシュポップアップ描画
|
|
|
|
|
|
let slashActiveIndex = -1;
|
|
|
|
|
|
|
|
|
|
|
|
function renderSlashPopup(query) {
|
|
|
|
|
|
const q = query.slice(1).toLowerCase();
|
|
|
|
|
|
const matches = COMMANDS.filter((c) => c.cmd.includes(q) || c.label.includes(q));
|
|
|
|
|
|
if (!matches.length) { closeSlash(); return; }
|
|
|
|
|
|
|
|
|
|
|
|
slashPopup.innerHTML = '';
|
|
|
|
|
|
slashActiveIndex = -1;
|
|
|
|
|
|
matches.forEach((c, i) => {
|
|
|
|
|
|
const item = document.createElement('div');
|
|
|
|
|
|
item.className = 'slash-item';
|
|
|
|
|
|
item.setAttribute('role', 'option');
|
|
|
|
|
|
item.innerHTML = `<span class="slash-cmd">${c.cmd}</span><span class="slash-label">${c.label}</span>`;
|
|
|
|
|
|
item.addEventListener('mousedown', (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
selectSlash(c);
|
|
|
|
|
|
});
|
|
|
|
|
|
slashPopup.appendChild(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
slashPopup.classList.add('open');
|
|
|
|
|
|
return matches;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSlash() {
|
|
|
|
|
|
slashPopup.classList.remove('open');
|
|
|
|
|
|
slashPopup.innerHTML = '';
|
|
|
|
|
|
slashActiveIndex = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function selectSlash(cmd) {
|
|
|
|
|
|
chatInput.value = cmd.prompt;
|
|
|
|
|
|
closeSlash();
|
|
|
|
|
|
chatInput.focus();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── xterm.js ──
|
2026-03-30 14:23:28 +00:00
|
|
|
|
const term = new Terminal({
|
|
|
|
|
|
fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
lineHeight: 1.4,
|
|
|
|
|
|
cursorBlink: true,
|
2026-03-30 15:25:44 +00:00
|
|
|
|
allowTransparency: true,
|
2026-03-30 14:23:28 +00:00
|
|
|
|
theme: {
|
2026-03-30 22:26:06 +00:00
|
|
|
|
background: 'transparent',
|
2026-03-30 14:23:28 +00:00
|
|
|
|
foreground: '#F3F4F6',
|
|
|
|
|
|
cursor: '#A78BFA',
|
2026-03-30 15:42:16 +00:00
|
|
|
|
selectionBackground: 'rgba(167,139,250,0.3)',
|
|
|
|
|
|
black: '#1A1A1A', brightBlack: '#4B5563',
|
|
|
|
|
|
red: '#F87171', brightRed: '#FCA5A5',
|
|
|
|
|
|
green: '#6EE7B7', brightGreen: '#A7F3D0',
|
|
|
|
|
|
yellow: '#FCD34D',brightYellow: '#FDE68A',
|
|
|
|
|
|
blue: '#60A5FA', brightBlue: '#93C5FD',
|
|
|
|
|
|
magenta: '#A78BFA', brightMagenta: '#C4B5FD',
|
|
|
|
|
|
cyan: '#22D3EE', brightCyan: '#67E8F9',
|
|
|
|
|
|
white: '#F3F4F6', brightWhite: '#FFFFFF'
|
2026-03-30 14:23:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
const fitAddon = new FitAddon.FitAddon();
|
|
|
|
|
|
term.loadAddon(fitAddon);
|
|
|
|
|
|
term.open(container);
|
|
|
|
|
|
fitAddon.fit();
|
|
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// 右クリックでターミナルにクリップボード貼り付け
|
|
|
|
|
|
container.addEventListener('contextmenu', async (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
try {
|
|
|
|
|
|
const text = await navigator.clipboard.readText();
|
|
|
|
|
|
if (text) sendInput(text);
|
|
|
|
|
|
} catch {}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── WebSocket ──
|
2026-03-30 14:23:28 +00:00
|
|
|
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
|
|
|
|
let ws;
|
|
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
function sendInput(text) {
|
|
|
|
|
|
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'input', data: text }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-30 14:23:28 +00:00
|
|
|
|
function connect() {
|
|
|
|
|
|
ws = new WebSocket(`${proto}//${location.host}/terminal`);
|
|
|
|
|
|
|
|
|
|
|
|
ws.onopen = () => {
|
2026-03-30 16:13:59 +00:00
|
|
|
|
headerDot.classList.add('connected');
|
|
|
|
|
|
headerDot.classList.remove('disconnected');
|
|
|
|
|
|
headerDot.title = '接続済み';
|
2026-03-30 15:42:16 +00:00
|
|
|
|
sendBtn.disabled = false;
|
|
|
|
|
|
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
2026-03-30 14:23:28 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ws.onmessage = (e) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const msg = JSON.parse(e.data);
|
|
|
|
|
|
if (msg.type === 'output') term.write(msg.data);
|
2026-03-30 15:42:16 +00:00
|
|
|
|
if (msg.type === 'session') {
|
2026-03-30 15:55:12 +00:00
|
|
|
|
settingsSessionId.textContent = msg.id;
|
|
|
|
|
|
settingsSessionRow.classList.remove('hidden');
|
2026-03-30 15:42:16 +00:00
|
|
|
|
}
|
2026-03-30 14:23:28 +00:00
|
|
|
|
} catch {}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ws.onclose = () => {
|
2026-03-30 16:13:59 +00:00
|
|
|
|
headerDot.classList.remove('connected');
|
|
|
|
|
|
headerDot.classList.add('disconnected');
|
|
|
|
|
|
headerDot.title = '切断';
|
2026-03-30 15:42:16 +00:00
|
|
|
|
sendBtn.disabled = true;
|
2026-03-30 14:23:28 +00:00
|
|
|
|
term.write('\r\n\x1b[31m[切断されました。再接続中...]\x1b[0m\r\n');
|
|
|
|
|
|
setTimeout(connect, 3000);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ws.onerror = () => ws.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
connect();
|
2026-03-30 15:42:16 +00:00
|
|
|
|
term.onData((data) => sendInput(data));
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// ── 送信(フォーカスをチャット欄に保持) ──
|
|
|
|
|
|
const inputHistory = [];
|
|
|
|
|
|
let historyIndex = -1;
|
|
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
function submitChat() {
|
|
|
|
|
|
const text = chatInput.value.trim();
|
|
|
|
|
|
if (!text) return;
|
2026-03-30 16:04:47 +00:00
|
|
|
|
closeSlash();
|
2026-03-30 15:42:16 +00:00
|
|
|
|
sendInput(text + '\n');
|
2026-03-30 16:04:47 +00:00
|
|
|
|
inputHistory.unshift(text);
|
|
|
|
|
|
if (inputHistory.length > 50) inputHistory.pop();
|
|
|
|
|
|
historyIndex = -1;
|
2026-03-30 15:42:16 +00:00
|
|
|
|
chatInput.value = '';
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// フォーカスをチャット欄に残す(Ctrl+V がそのまま使える)
|
|
|
|
|
|
chatInput.focus();
|
2026-03-30 15:42:16 +00:00
|
|
|
|
}
|
2026-03-30 14:23:28 +00:00
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
sendBtn.addEventListener('click', submitChat);
|
2026-03-30 16:04:47 +00:00
|
|
|
|
|
2026-03-30 15:42:16 +00:00
|
|
|
|
chatInput.addEventListener('keydown', (e) => {
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// 送信
|
|
|
|
|
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submitChat(); return; }
|
|
|
|
|
|
|
|
|
|
|
|
// スラッシュポップアップのキーボード操作
|
|
|
|
|
|
if (slashPopup.classList.contains('open')) {
|
|
|
|
|
|
const items = slashPopup.querySelectorAll('.slash-item');
|
|
|
|
|
|
if (e.key === 'ArrowDown') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
slashActiveIndex = Math.min(slashActiveIndex + 1, items.length - 1);
|
|
|
|
|
|
items.forEach((el, i) => el.classList.toggle('active', i === slashActiveIndex));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.key === 'ArrowUp') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
slashActiveIndex = Math.max(slashActiveIndex - 1, 0);
|
|
|
|
|
|
items.forEach((el, i) => el.classList.toggle('active', i === slashActiveIndex));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.key === 'Enter' && slashActiveIndex >= 0) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const match = COMMANDS.filter((c) =>
|
|
|
|
|
|
c.cmd.includes(chatInput.value.slice(1).toLowerCase()) ||
|
|
|
|
|
|
c.label.includes(chatInput.value.slice(1).toLowerCase())
|
|
|
|
|
|
);
|
|
|
|
|
|
if (match[slashActiveIndex]) selectSlash(match[slashActiveIndex]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.key === 'Escape') { closeSlash(); return; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 入力履歴ナビ(スラッシュポップアップが閉じている時)
|
|
|
|
|
|
if (e.key === 'ArrowUp' && !slashPopup.classList.contains('open')) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (historyIndex < inputHistory.length - 1) {
|
|
|
|
|
|
historyIndex++;
|
|
|
|
|
|
chatInput.value = inputHistory[historyIndex];
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.key === 'ArrowDown' && !slashPopup.classList.contains('open')) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (historyIndex > 0) { historyIndex--; chatInput.value = inputHistory[historyIndex]; }
|
|
|
|
|
|
else { historyIndex = -1; chatInput.value = ''; }
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
chatInput.addEventListener('input', () => {
|
|
|
|
|
|
const val = chatInput.value;
|
|
|
|
|
|
if (val.startsWith('/')) {
|
|
|
|
|
|
renderSlashPopup(val);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
closeSlash();
|
|
|
|
|
|
}
|
2026-03-30 14:23:28 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// ── Claude 開始ボタン ──
|
2026-03-30 15:42:16 +00:00
|
|
|
|
claudeBtn.addEventListener('click', () => {
|
|
|
|
|
|
sendInput('claude\n');
|
2026-03-30 16:04:47 +00:00
|
|
|
|
chatInput.focus();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 音声入力 ──
|
|
|
|
|
|
voiceAutosend.checked = localStorage.getItem('posimai-dev-voice-autosend') === '1';
|
|
|
|
|
|
voiceAutosend.addEventListener('change', () => {
|
|
|
|
|
|
localStorage.setItem('posimai-dev-voice-autosend', voiceAutosend.checked ? '1' : '0');
|
2026-03-30 14:23:28 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-30 15:55:12 +00:00
|
|
|
|
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
|
|
|
if (SR) {
|
|
|
|
|
|
micBtn.classList.add('available');
|
|
|
|
|
|
const recognition = new SR();
|
|
|
|
|
|
recognition.lang = 'ja-JP';
|
|
|
|
|
|
recognition.continuous = false;
|
|
|
|
|
|
recognition.interimResults = false;
|
|
|
|
|
|
let listening = false;
|
|
|
|
|
|
|
|
|
|
|
|
micBtn.addEventListener('click', () => {
|
|
|
|
|
|
if (listening) { recognition.stop(); return; }
|
|
|
|
|
|
recognition.start();
|
|
|
|
|
|
});
|
2026-03-30 16:04:47 +00:00
|
|
|
|
recognition.onstart = () => { listening = true; micBtn.classList.add('listening'); };
|
|
|
|
|
|
recognition.onend = () => { listening = false; micBtn.classList.remove('listening'); };
|
|
|
|
|
|
recognition.onerror = () => { listening = false; micBtn.classList.remove('listening'); };
|
2026-03-30 15:55:12 +00:00
|
|
|
|
recognition.onresult = (e) => {
|
|
|
|
|
|
const transcript = e.results[0][0].transcript;
|
|
|
|
|
|
chatInput.value = transcript;
|
2026-03-30 16:04:47 +00:00
|
|
|
|
if (voiceAutosend.checked) {
|
|
|
|
|
|
submitChat();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
chatInput.focus();
|
|
|
|
|
|
}
|
2026-03-30 15:55:12 +00:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// ── Resize ──
|
2026-03-30 15:42:16 +00:00
|
|
|
|
const ro = new ResizeObserver(() => {
|
|
|
|
|
|
fitAddon.fit();
|
|
|
|
|
|
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
|
|
|
|
|
});
|
|
|
|
|
|
ro.observe(container);
|
|
|
|
|
|
window.addEventListener('resize', () => fitAddon.fit());
|
|
|
|
|
|
|
2026-03-30 16:04:47 +00:00
|
|
|
|
// ── Settings panel ──
|
2026-03-30 15:42:16 +00:00
|
|
|
|
const settingsBtn = document.getElementById('settingsBtn');
|
2026-03-30 14:23:28 +00:00
|
|
|
|
const settingsPanel = document.getElementById('settingsPanel');
|
|
|
|
|
|
settingsBtn.addEventListener('click', () => {
|
|
|
|
|
|
settingsPanel.classList.toggle('open');
|
|
|
|
|
|
settingsBtn.setAttribute('aria-expanded', settingsPanel.classList.contains('open'));
|
|
|
|
|
|
});
|
2026-03-30 15:42:16 +00:00
|
|
|
|
document.getElementById('settingsCloseBtn').addEventListener('click', () => {
|
2026-03-30 14:23:28 +00:00
|
|
|
|
settingsPanel.classList.remove('open');
|
|
|
|
|
|
settingsBtn.setAttribute('aria-expanded', 'false');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
lucide.createIcons();
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|