Files
wangjia 40760aa884
ci / server (push) Failing after 14s
ci / design-tokens (push) Failing after 11s
dudu MVP:五端语音输入法初始提交
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调
- desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导)
- android:Compose 主 App + IME(键盘内录音直传)
- ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写)
- design/design-pipeline:设计系统 + token 导出 iOS/Android 主题
- doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:38:37 +08:00

125 lines
5.5 KiB
React
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// dudu 桌面端 UI kit · 共享元素:Lucide 图标子集、mac 窗口框、伪二维码
// 通过 window 共享给其余 text/babel 脚本
function DuduIcon({ d, size = 16, sw = 2, children, ...rest }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...rest}>
{children}
</svg>
);
}
const DuIcons = {
Mic: (p) => (
<DuduIcon {...p}>
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" x2="12" y1="19" y2="22"></line>
</DuduIcon>
),
Settings: (p) => (
<DuduIcon {...p}>
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
<circle cx="12" cy="12" r="3"></circle>
</DuduIcon>
),
Check: (p) => <DuduIcon {...p}><path d="M20 6 9 17l-5-5"></path></DuduIcon>,
X: (p) => <DuduIcon {...p}><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></DuduIcon>,
ChevronDown: (p) => <DuduIcon {...p}><path d="m6 9 6 6 6-6"></path></DuduIcon>,
ChevronRight: (p) => <DuduIcon {...p}><path d="m9 18 6-6-6-6"></path></DuduIcon>,
Sun: (p) => (
<DuduIcon {...p}>
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path>
<path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path>
<path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path>
</DuduIcon>
),
Moon: (p) => <DuduIcon {...p}><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></DuduIcon>,
AudioLines: (p) => (
<DuduIcon {...p}>
<path d="M2 10v3"></path><path d="M6 6v11"></path><path d="M10 3v18"></path>
<path d="M14 8v7"></path><path d="M18 5v13"></path><path d="M22 10v3"></path>
</DuduIcon>
),
User: (p) => (
<DuduIcon {...p}>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</DuduIcon>
),
LogOut: (p) => (
<DuduIcon {...p}>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" x2="9" y1="12" y2="12"></line>
</DuduIcon>
),
};
// dudu 圆嘴 logocurrentColor 线条版)
function DuduLogo({ size = 20, sw = 3.5 }) {
return (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none" stroke="currentColor"
strokeWidth={sw} strokeLinecap="round" aria-hidden="true">
<circle cx="24" cy="24" r="18"></circle>
<path d="M17 20.5v7"></path>
<path d="M24 16.5v15"></path>
<path d="M31 20.5v7"></path>
</svg>
);
}
// mac 风格窗口(红绿灯 + 标题)
function MacWindow({ title, width = 460, children, style }) {
return (
<div style={{
width, background: 'var(--surface-card)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--border-1)', boxShadow: 'var(--shadow-window)',
overflow: 'hidden', fontFamily: 'var(--font-sans)', ...style,
}}>
<div style={{
display: 'flex', alignItems: 'center', gap: 7, padding: '10px 14px',
background: 'var(--surface-2)', borderBottom: '1px solid var(--border-1)',
}}>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#FF5F57' }}></span>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#FEBC2E' }}></span>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#28C840' }}></span>
<span style={{ marginLeft: 10, fontSize: 'var(--text-xs)', color: 'var(--text-2)', whiteSpace: 'nowrap' }}>{title}</span>
</div>
{children}
</div>
);
}
// 伪二维码占位(确定性图案,仅作占位 — 接入后端 /v1/auth/qr 后替换)
function FakeQr({ size = 148, seed = 7 }) {
const n = 17;
const cells = [];
let s = seed;
const rnd = () => { s = (s * 16807) % 2147483647; return s / 2147483647; };
for (let y = 0; y < n; y++) {
for (let x = 0; x < n; x++) {
const corner = (x < 5 && y < 5) || (x > n - 6 && y < 5) || (x < 5 && y > n - 6);
let on;
if (corner) {
const cx = x > n - 6 ? x - (n - 5) : x, cy = y > n - 6 ? y - (n - 5) : y;
on = cx === 0 || cx === 4 || cy === 0 || cy === 4 || (cx > 1 && cx < 3 + 1 && cy > 1 && cy < 3 + 1);
} else {
on = rnd() > 0.52;
}
if (on) cells.push(<span key={x + '-' + y} style={{ gridColumn: x + 1, gridRow: y + 1, background: '#1B1E26', borderRadius: 1 }}></span>);
}
}
return (
<div style={{
width: size, height: size, padding: 10, boxSizing: 'border-box', background: '#fff',
borderRadius: 'var(--radius-md)', border: '1px solid var(--border-1)',
display: 'grid', gridTemplateColumns: `repeat(${n}, 1fr)`, gridTemplateRows: `repeat(${n}, 1fr)`, gap: 1,
}} aria-label="二维码占位">{cells}</div>
);
}
Object.assign(window, { DuduIcon, DuIcons, DuduLogo, MacWindow, FakeQr });