docs(pages): match keyboard mockups to the real assistant UI
- Rebuild the story phone keyboard from AIKeyboardView: logo, AI/中文/EN tabs, translation and clipboard chips, mic capsule states (dictation red, hold-for-AI teal, busy spinner, inserted check), delete/space circles, return capsule, and undo/edit after insertion - Replace invented tone chips with the real three reply cards (ordinary, formal, relaxed & playful) using the in-app demo copy - Show the real copy flow: Paste plus ranked skill capsules in the top bar, "Finding events…" and "Adding to Calendar…" status text - Replace the outdated clipboard-skills video (retired circular chips) with a live replica of the current top-bar skill flow - Rework the hero capsule around the real mic capsule states - Align FAQ, JSON-LD, and llms.txt wording with the shipped UI
This commit is contained in:
+630
-203
File diff suppressed because it is too large
Load Diff
+171
-71
@@ -368,19 +368,23 @@
|
|||||||
|
|
||||||
/* ---------------- Hero 语音胶囊 ---------------- */
|
/* ---------------- Hero 语音胶囊 ---------------- */
|
||||||
|
|
||||||
|
// 复刻键盘真实的麦克风状态流:轻点听写(红色声波 → 打勾)、长按问 AI(青色声波 → 转圈 → 回答)、
|
||||||
|
// 复制消息后的嘴替回复(转圈 → 回复)。文案取自 App 的实际界面字符串与演示素材。
|
||||||
const CAPSULE_SCRIPT = {
|
const CAPSULE_SCRIPT = {
|
||||||
zh: [
|
zh: [
|
||||||
{ tag: "听写", text: "帮我订周五晚上七点的位子,四个人" },
|
{ tag: "轻点听写", mode: "rec", text: "帮我订周五晚上七点的位子,四个人" },
|
||||||
{ tag: "嘴替 · 轻松", text: "这周有点累,先不去啦,下次我请!" },
|
{ tag: "长按问 AI", mode: "ai", text: "周末去哪儿玩比较合适?", busy: "AI 正在思考…", answer: "可以去近郊走走:上午逛古镇,下午找家咖啡馆。" },
|
||||||
{ tag: "替你做", text: "已建日程:周五 19:00 · 四人晚餐", agent: true }
|
{ tag: "复制 · 嘴替回复", busy: "AI 正在思考…", answer: "好的,我整理一下预算版,明天上午发你。" }
|
||||||
],
|
],
|
||||||
en: [
|
en: [
|
||||||
{ tag: "Dictate", text: "Book a table for four, Friday at seven" },
|
{ tag: "Tap to dictate", mode: "rec", text: "Book a table for four, Friday at seven" },
|
||||||
{ tag: "Your voice", text: "Bit wiped this week — rain check? Next one’s on me!" },
|
{ tag: "Hold to ask AI", mode: "ai", text: "Where should we go this weekend?", busy: "AI is thinking…", answer: "Try a day trip: an old town in the morning, a café in the afternoon." },
|
||||||
{ tag: "Done for you", text: "Event added: Fri 7:00 PM · Dinner for four", agent: true }
|
{ tag: "Copy · Reply in your voice", busy: "AI is thinking…", answer: "Sure, I’ll put the budget version together and send it over tomorrow morning." }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CAPSULE_STATES = ["is-rec", "is-ai", "is-busy", "is-ai-busy", "is-done"];
|
||||||
|
|
||||||
const hero = document.getElementById("top");
|
const hero = document.getElementById("top");
|
||||||
const capsule = document.querySelector(".voice-capsule");
|
const capsule = document.querySelector(".voice-capsule");
|
||||||
const capsuleTag = capsule?.querySelector(".capsule-tag");
|
const capsuleTag = capsule?.querySelector(".capsule-tag");
|
||||||
@@ -400,6 +404,24 @@
|
|||||||
return run === capsuleRun;
|
return run === capsuleRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setCapsuleState(...states) {
|
||||||
|
CAPSULE_STATES.forEach((name) => capsule.classList.toggle(name, states.includes(name)));
|
||||||
|
const listening = states.includes("is-rec") || states.includes("is-ai");
|
||||||
|
if (capsuleWave) capsuleWave.level = listening ? 1 : 0;
|
||||||
|
if (listening) wakeWaves();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐字写入;返回 false 表示本轮已被新一轮(如切换语言)取代
|
||||||
|
async function typeLine(run, text, perChar) {
|
||||||
|
const characters = Array.from(text);
|
||||||
|
for (let i = 1; i <= characters.length; i += 1) {
|
||||||
|
if (!(await whenActive(run))) return false;
|
||||||
|
capsuleLine.textContent = characters.slice(0, i).join("");
|
||||||
|
await wait(perChar());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function playCapsule() {
|
async function playCapsule() {
|
||||||
if (!capsule || !capsuleLine || !capsuleTag) return;
|
if (!capsule || !capsuleLine || !capsuleTag) return;
|
||||||
const run = ++capsuleRun;
|
const run = ++capsuleRun;
|
||||||
@@ -407,39 +429,50 @@
|
|||||||
const script = CAPSULE_SCRIPT[lang];
|
const script = CAPSULE_SCRIPT[lang];
|
||||||
|
|
||||||
if (reduceMotion) {
|
if (reduceMotion) {
|
||||||
|
setCapsuleState();
|
||||||
capsuleTag.textContent = script[0].tag;
|
capsuleTag.textContent = script[0].tag;
|
||||||
capsuleLine.textContent = script[0].text;
|
capsuleLine.textContent = script[0].text;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base = lang === "zh" ? 72 : 34;
|
||||||
|
const speech = () => base + Math.random() * base * 0.8;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
while (run === capsuleRun) {
|
while (run === capsuleRun) {
|
||||||
const item = script[index % script.length];
|
const item = script[index % script.length];
|
||||||
|
setCapsuleState();
|
||||||
capsuleTag.textContent = item.tag;
|
capsuleTag.textContent = item.tag;
|
||||||
capsuleTag.classList.toggle("is-agent", Boolean(item.agent));
|
|
||||||
capsuleLine.textContent = "";
|
capsuleLine.textContent = "";
|
||||||
await wait(260);
|
await wait(650);
|
||||||
|
|
||||||
// 听写与嘴替模拟「边说边出字」,Agentic 结果直接以完成态弹出
|
// 1. 说话:红色(听写)或青色(问 AI)胶囊 + 声波,边说边出字
|
||||||
const listening = !item.agent;
|
if (item.text) {
|
||||||
capsule.classList.toggle("is-listening", listening);
|
setCapsuleState(item.mode === "ai" ? "is-ai" : "is-rec");
|
||||||
if (capsuleWave) capsuleWave.level = listening ? 1 : 0.15;
|
if (!(await typeLine(run, item.text, speech))) return;
|
||||||
wakeWaves();
|
await wait(420);
|
||||||
|
|
||||||
const characters = Array.from(item.text);
|
|
||||||
const base = lang === "zh" ? 70 : 34;
|
|
||||||
for (let i = 1; i <= characters.length; i += 1) {
|
|
||||||
if (!(await whenActive(run))) return;
|
|
||||||
capsuleLine.textContent = characters.slice(0, i).join("");
|
|
||||||
await wait(item.agent ? 14 : base + Math.random() * base * 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
capsule.classList.remove("is-listening");
|
// 2. 处理中:胶囊转圈,显示真实界面的状态文案
|
||||||
if (capsuleWave) capsuleWave.level = 0.12;
|
if (item.busy) {
|
||||||
await wait(item.agent ? 2400 : 1900);
|
setCapsuleState("is-busy", item.mode === "ai" ? "is-ai-busy" : "");
|
||||||
|
capsuleLine.textContent = item.busy;
|
||||||
|
await wait(1300);
|
||||||
|
if (!(await whenActive(run))) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 结果:回答快速流出
|
||||||
|
if (item.answer) {
|
||||||
|
capsuleLine.textContent = "";
|
||||||
|
if (!(await typeLine(run, item.answer, () => 16))) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 插入成功:胶囊打勾
|
||||||
|
setCapsuleState("is-done");
|
||||||
|
await wait(2100);
|
||||||
if (!(await whenActive(run))) return;
|
if (!(await whenActive(run))) return;
|
||||||
|
|
||||||
// 快速回删,像是把这一句交给下一个能力
|
// 快速回删,交给下一个能力
|
||||||
|
const characters = Array.from(capsuleLine.textContent);
|
||||||
for (let i = characters.length; i >= 0; i -= 2) {
|
for (let i = characters.length; i >= 0; i -= 2) {
|
||||||
if (run !== capsuleRun) return;
|
if (run !== capsuleRun) return;
|
||||||
capsuleLine.textContent = characters.slice(0, i).join("");
|
capsuleLine.textContent = characters.slice(0, i).join("");
|
||||||
@@ -451,43 +484,69 @@
|
|||||||
|
|
||||||
languageHooks.push(() => playCapsule());
|
languageHooks.push(() => playCapsule());
|
||||||
|
|
||||||
|
/* ---------------- 键盘复刻:共用状态写入 ---------------- */
|
||||||
|
|
||||||
|
const KB_DEFAULTS = { bar: "tabs", mic: "idle", ctx: "hint", surface: "main", pick: "", undo: "0", action: "off" };
|
||||||
|
|
||||||
|
// 把键盘状态写到容器的 data-* 上(只写变化的值,避免无谓的样式重算)
|
||||||
|
function applyKeyboardState(element, state) {
|
||||||
|
Object.keys(KB_DEFAULTS).forEach((key) => {
|
||||||
|
const value = state[key] ?? KB_DEFAULTS[key];
|
||||||
|
if (element.dataset[key] !== value) element.dataset[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- 三幕滚动叙事 ---------------- */
|
/* ---------------- 三幕滚动叙事 ---------------- */
|
||||||
|
|
||||||
|
// 与气泡文案保持一致:听写结果、选中的「普通」回复
|
||||||
const STORY_TEXT = {
|
const STORY_TEXT = {
|
||||||
zh: {
|
zh: {
|
||||||
draft: "可以啊 周五七点 位子我来订",
|
dictation: "收到了,我下午看完给你反馈。",
|
||||||
tones: [
|
reply: "好的,我整理一下预算版,明天上午发你。"
|
||||||
"可以,周五七点,位子我来订。",
|
|
||||||
"好的,周五晚七点见,座位由我来预订。",
|
|
||||||
"好呀!周五七点不见不散,位子包在我身上~"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
draft: "sure friday at seven ill book a table",
|
dictation: "Got it — I’ll review it this afternoon and get back to you.",
|
||||||
tones: [
|
reply: "Sure, I’ll put the budget version together and send it over tomorrow morning."
|
||||||
"Sure — Friday at seven. I’ll book a table.",
|
|
||||||
"Sounds good. Friday at 7 p.m.; I’ll make the reservation.",
|
|
||||||
"Yes! Friday at 7 — leave the table to me."
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 进度阈值(0 → 1):s1 好友来信 · s2 开始聆听 · s3 嘴替改写 · s4 发送
|
// 键盘时间轴(滚动进度 0 → 1),每段是完整状态,取最后一个 at ≤ p 的段
|
||||||
// s5 好友回地址 · s6 复制 · s7 技能弹出 · s8 写入日历
|
// 第一幕 听写:录音 → 打勾插入 → 发送
|
||||||
const STORY_STEPS = [0.03, 0.08, 0.31, 0.585, 0.66, 0.735, 0.805, 0.885];
|
// 第二幕 嘴替:复制消息 → AI 思考 → 三条回复 → 点「普通」插入 → 发送
|
||||||
const TYPE_START = 0.08;
|
// 第三幕 Agentic:复制会议通知 → 顶栏技能 → 点「日程」→ 提取 → 写入日历
|
||||||
const TYPE_END = 0.29;
|
const STORY_PHASES = [
|
||||||
const TONE_STEPS = [0.36, 0.44, 0.515];
|
{ at: 0 },
|
||||||
const ACT_RANGES = [[0, 0.31], [0.31, 0.66], [0.66, 1]];
|
{ at: 0.065, bar: "cancel", mic: "rec", ctx: "live" },
|
||||||
|
{ at: 0.235, mic: "done", ctx: "none", undo: "1", action: "on", compose: "dictation" },
|
||||||
|
{ at: 0.27, undo: "1", action: "on", compose: "dictation", send: true },
|
||||||
|
{ at: 0.29 },
|
||||||
|
{ at: 0.375, bar: "paste", mic: "busy", ctx: "think" },
|
||||||
|
{ at: 0.44, bar: "paste", surface: "variants", ctx: "none" },
|
||||||
|
{ at: 0.545, bar: "paste", surface: "variants", ctx: "none", pick: "0" },
|
||||||
|
{ at: 0.575, mic: "done", ctx: "none", undo: "1", action: "on", compose: "reply" },
|
||||||
|
{ at: 0.6, undo: "1", action: "on", compose: "reply", send: true },
|
||||||
|
{ at: 0.62 },
|
||||||
|
{ at: 0.715, bar: "skills", ctx: "none" },
|
||||||
|
{ at: 0.775, bar: "skills", ctx: "extract", mic: "busy", pick: "event" },
|
||||||
|
{ at: 0.84, ctx: "tip" },
|
||||||
|
{ at: 0.93 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 对话气泡:到达阈值即出现;复制提示只在区间内显示
|
||||||
|
const STORY_MESSAGES = { m1: 0.02, o1: 0.29, m2: 0.33, o2: 0.62, m3: 0.67, ev: 0.885 };
|
||||||
|
const STORY_COPIES = { c2: [0.36, 0.44], c3: [0.705, 0.775] };
|
||||||
|
const LIVE_RANGE = [0.075, 0.225];
|
||||||
|
const ACT_RANGES = [[0, 0.31], [0.31, 0.655], [0.655, 1]];
|
||||||
|
|
||||||
const story = document.getElementById("storyStage");
|
const story = document.getElementById("storyStage");
|
||||||
const storyText = document.getElementById("storyText");
|
const storyText = document.getElementById("storyText");
|
||||||
|
const storyLive = document.getElementById("storyLive");
|
||||||
const storyActs = story ? Array.from(story.querySelectorAll(".story-act")) : [];
|
const storyActs = story ? Array.from(story.querySelectorAll(".story-act")) : [];
|
||||||
const storyWaves = story
|
const storyWaves = story
|
||||||
? Array.from(story.querySelectorAll(".kb-wave")).map((host, i) => createWave(host, { mirror: i === 0 }))
|
? Array.from(story.querySelectorAll(".mic-wave")).map((host) => createWave(host))
|
||||||
: [];
|
: [];
|
||||||
let storyLastText = null;
|
let storyLastText = null;
|
||||||
let storyLastTone = -1;
|
let storyLastLive = null;
|
||||||
let storyLastAct = -1;
|
let storyLastAct = -1;
|
||||||
|
|
||||||
function updateStory(force) {
|
function updateStory(force) {
|
||||||
@@ -500,43 +559,39 @@
|
|||||||
|
|
||||||
const total = Math.max(1, story.offsetHeight - viewport);
|
const total = Math.max(1, story.offsetHeight - viewport);
|
||||||
const p = clamp(-rect.top / total);
|
const p = clamp(-rect.top / total);
|
||||||
const lang = root.dataset.lang === "en" ? "en" : "zh";
|
const copy = STORY_TEXT[root.dataset.lang === "en" ? "en" : "zh"];
|
||||||
const copy = STORY_TEXT[lang];
|
|
||||||
|
|
||||||
const reached = STORY_STEPS.map((step) => p >= step);
|
let phase = STORY_PHASES[0];
|
||||||
reached.forEach((on, i) => story.classList.toggle(`s${i + 1}`, on));
|
STORY_PHASES.forEach((item) => { if (p >= item.at) phase = item; });
|
||||||
const [, listening, rewriting, sent] = reached;
|
applyKeyboardState(story, phase);
|
||||||
|
story.classList.toggle("press-send", Boolean(phase.send));
|
||||||
|
|
||||||
// 语气:仅在「改写中」阶段可见,按进度依次切到 普通 → 正式 → 轻松
|
Object.entries(STORY_MESSAGES).forEach(([name, at]) => story.classList.toggle(name, p >= at));
|
||||||
let tone = -1;
|
Object.entries(STORY_COPIES).forEach(([name, [from, to]]) => story.classList.toggle(name, p >= from && p < to));
|
||||||
if (rewriting && !sent) {
|
|
||||||
TONE_STEPS.forEach((step, i) => { if (p >= step) tone = i; });
|
// 实时转写:录音阶段按滚动进度逐字出现
|
||||||
|
let live = "";
|
||||||
|
if (phase.ctx === "live") {
|
||||||
|
const characters = Array.from(copy.dictation);
|
||||||
|
const count = Math.round(clamp((p - LIVE_RANGE[0]) / (LIVE_RANGE[1] - LIVE_RANGE[0])) * characters.length);
|
||||||
|
live = characters.slice(0, count).join("");
|
||||||
}
|
}
|
||||||
if (tone !== storyLastTone) {
|
if (storyLive && live !== storyLastLive) {
|
||||||
[0, 1, 2].forEach((i) => story.classList.toggle(`tone-${i}`, i === tone));
|
storyLive.textContent = live;
|
||||||
}
|
storyLastLive = live;
|
||||||
|
|
||||||
// 输入框:聆听阶段按滚动进度逐字「听写」,改写阶段显示当前语气的版本
|
|
||||||
let text = "";
|
|
||||||
if (listening && !rewriting) {
|
|
||||||
const characters = Array.from(copy.draft);
|
|
||||||
const count = Math.round(clamp((p - TYPE_START) / (TYPE_END - TYPE_START)) * characters.length);
|
|
||||||
text = characters.slice(0, count).join("");
|
|
||||||
} else if (rewriting && !sent) {
|
|
||||||
text = tone >= 0 ? copy.tones[tone] : copy.draft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 输入框:插入时整句落入(与真实插入行为一致),发送后清空
|
||||||
|
const text = phase.compose ? copy[phase.compose] : "";
|
||||||
if (storyText && text !== storyLastText) {
|
if (storyText && text !== storyLastText) {
|
||||||
const isRewrite = tone !== storyLastTone && text && storyLastText;
|
|
||||||
storyText.textContent = text;
|
storyText.textContent = text;
|
||||||
if (isRewrite && !reduceMotion) {
|
if (text && !storyLastText && !reduceMotion) {
|
||||||
storyText.classList.remove("is-swapping");
|
storyText.classList.remove("is-swapping");
|
||||||
void storyText.offsetWidth;
|
void storyText.offsetWidth;
|
||||||
storyText.classList.add("is-swapping");
|
storyText.classList.add("is-swapping");
|
||||||
}
|
}
|
||||||
storyLastText = text;
|
storyLastText = text;
|
||||||
}
|
}
|
||||||
storyLastTone = tone;
|
|
||||||
story.classList.toggle("has-text", text.length > 0);
|
story.classList.toggle("has-text", text.length > 0);
|
||||||
|
|
||||||
// 左侧三幕:高亮当前幕,幕内进度条随滚动填充
|
// 左侧三幕:高亮当前幕,幕内进度条随滚动填充
|
||||||
@@ -551,7 +606,7 @@
|
|||||||
element.style.setProperty("--ap", clamp((p - start) / (end - start)).toFixed(3));
|
element.style.setProperty("--ap", clamp((p - start) / (end - start)).toFixed(3));
|
||||||
});
|
});
|
||||||
|
|
||||||
storyWaves.forEach((wave) => { wave.level = listening && !rewriting ? 1 : 0.06; });
|
storyWaves.forEach((wave) => { wave.level = phase.mic === "rec" ? 1 : 0; });
|
||||||
if (inView) wakeWaves();
|
if (inView) wakeWaves();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,11 +614,56 @@
|
|||||||
story.classList.add("is-live");
|
story.classList.add("is-live");
|
||||||
languageHooks.push(() => {
|
languageHooks.push(() => {
|
||||||
storyLastText = null;
|
storyLastText = null;
|
||||||
storyLastTone = -2;
|
storyLastLive = null;
|
||||||
updateStory(true);
|
updateStory(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- 技能区循环演示 ---------------- */
|
||||||
|
|
||||||
|
// 复制 → 顶栏换成技能胶囊 → 点「日程」→ 提取 → 加入日历 → 横幅确认,然后重来
|
||||||
|
const SKILL_DEMO_STEPS = [
|
||||||
|
{ ms: 1300 },
|
||||||
|
{ ms: 1900, bar: "skills", ctx: "none", copied: true },
|
||||||
|
{ ms: 550, bar: "skills", ctx: "none", pick: "event", copied: true },
|
||||||
|
{ ms: 1500, bar: "skills", ctx: "extract", mic: "busy", pick: "event" },
|
||||||
|
{ ms: 1200, ctx: "tip" },
|
||||||
|
{ ms: 2800, done: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
const skillsDemo = document.getElementById("skillsDemo");
|
||||||
|
let skillsDemoVisible = false;
|
||||||
|
let skillsDemoTimer = 0;
|
||||||
|
let skillsDemoIndex = 0;
|
||||||
|
|
||||||
|
function showSkillsStep(step) {
|
||||||
|
applyKeyboardState(skillsDemo, step);
|
||||||
|
skillsDemo.classList.toggle("is-copied", Boolean(step.copied));
|
||||||
|
skillsDemo.classList.toggle("is-done", Boolean(step.done));
|
||||||
|
}
|
||||||
|
|
||||||
|
function tickSkillsDemo() {
|
||||||
|
clearTimeout(skillsDemoTimer);
|
||||||
|
if (!skillsDemoVisible || document.hidden) return;
|
||||||
|
const step = SKILL_DEMO_STEPS[skillsDemoIndex];
|
||||||
|
showSkillsStep(step);
|
||||||
|
skillsDemoIndex = (skillsDemoIndex + 1) % SKILL_DEMO_STEPS.length;
|
||||||
|
skillsDemoTimer = setTimeout(tickSkillsDemo, step.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillsDemo) {
|
||||||
|
if (reduceMotion) {
|
||||||
|
// 减弱动态效果:停在「技能胶囊已出现」这一帧
|
||||||
|
showSkillsStep(SKILL_DEMO_STEPS[1]);
|
||||||
|
} else {
|
||||||
|
new IntersectionObserver((entries) => {
|
||||||
|
skillsDemoVisible = entries[0].isIntersecting;
|
||||||
|
tickSkillsDemo();
|
||||||
|
}, { threshold: 0.35 }).observe(skillsDemo);
|
||||||
|
document.addEventListener("visibilitychange", tickSkillsDemo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- 滚动引擎 ---------------- */
|
/* ---------------- 滚动引擎 ---------------- */
|
||||||
|
|
||||||
const progressBar = document.querySelector(".scroll-progress");
|
const progressBar = document.querySelector(".scroll-progress");
|
||||||
|
|||||||
+206
-60
@@ -50,7 +50,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<!-- v= 查询串用于缓存刷新:GitHub Pages 对静态资源有 10 分钟缓存窗口,改版本号强制浏览器取新文件 -->
|
<!-- v= 查询串用于缓存刷新:GitHub Pages 对静态资源有 10 分钟缓存窗口,改版本号强制浏览器取新文件 -->
|
||||||
<link rel="stylesheet" href="assets/site.css?v=20261001">
|
<link rel="stylesheet" href="assets/site.css?v=20261001b">
|
||||||
<script>
|
<script>
|
||||||
// 首屏前确定主题,避免闪烁;无保存偏好时跟随系统。
|
// 首屏前确定主题,避免闪烁;无保存偏好时跟随系统。
|
||||||
// .js 类只在脚本可用时出现:所有「先隐藏、再动效显现」的初始态都挂在它下面,
|
// .js 类只在脚本可用时出现:所有「先隐藏、再动效显现」的初始态都挂在它下面,
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"name": "什么是嘴替输入法?",
|
"name": "什么是嘴替输入法?",
|
||||||
"acceptedAnswer": {
|
"acceptedAnswer": {
|
||||||
"@type": "Answer",
|
"@type": "Answer",
|
||||||
"text": "嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,并提供普通、正式、轻松三种回复语气,结果先预览、再插入。"
|
"text": "嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,复制对方的消息后,会按你的口吻给出普通、正式、轻松趣味三条回复,点选即插入。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
"name": "什么是 Agentic 键盘(Agentic keyboard)?",
|
"name": "什么是 Agentic 键盘(Agentic keyboard)?",
|
||||||
"acceptedAnswer": {
|
"acceptedAnswer": {
|
||||||
"@type": "Answer",
|
"@type": "Answer",
|
||||||
"text": "Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会推荐最多 5 个相关操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。"
|
"text": "Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会在键盘顶栏按相关度列出可用操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,6 +201,18 @@
|
|||||||
<symbol id="i-arrow-up" viewBox="0 0 24 24"><path d="M12 19V5.5M6.5 11 12 5.5l5.5 5.5"/></symbol>
|
<symbol id="i-arrow-up" viewBox="0 0 24 24"><path d="M12 19V5.5M6.5 11 12 5.5l5.5 5.5"/></symbol>
|
||||||
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
|
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
|
||||||
<symbol id="i-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.4 3.6 5.2 3.6 8.5s-1.2 6.1-3.6 8.5c-2.4-2.4-3.6-5.2-3.6-8.5S9.6 5.9 12 3.5Z"/></symbol>
|
<symbol id="i-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.4 3.6 5.2 3.6 8.5s-1.2 6.1-3.6 8.5c-2.4-2.4-3.6-5.2-3.6-8.5S9.6 5.9 12 3.5Z"/></symbol>
|
||||||
|
<!-- 以下图标对应键盘真实界面里的 SF Symbols(delete.left / space / return / arrow.uturn.backward 等) -->
|
||||||
|
<symbol id="i-delete" viewBox="0 0 24 24"><path d="M9 5.5h10a1.8 1.8 0 0 1 1.8 1.8v9.4a1.8 1.8 0 0 1-1.8 1.8H9L3.2 12 9 5.5Z"/><path d="M11.8 9.5l5 5M16.8 9.5l-5 5"/></symbol>
|
||||||
|
<symbol id="i-space" viewBox="0 0 24 24"><path d="M4 10v4.5h16V10"/></symbol>
|
||||||
|
<symbol id="i-return" viewBox="0 0 24 24"><path d="M19 5.5v5.5a3 3 0 0 1-3 3H5.5"/><path d="M9.5 10 5.5 14l4 4"/></symbol>
|
||||||
|
<symbol id="i-undo" viewBox="0 0 24 24"><path d="M8.5 14 4 9.5 8.5 5"/><path d="M4 9.5h10.5a5 5 0 0 1 0 10H11"/></symbol>
|
||||||
|
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M4 20.5h16"/><path d="M14.6 4.8l3.1 3.1-8.6 8.6H6v-3.1l8.6-8.6Z"/></symbol>
|
||||||
|
<symbol id="i-clipboard" viewBox="0 0 24 24"><rect x="5.5" y="4.5" width="13" height="16" rx="2.5"/><path d="M9.2 3.5h5.6v2.6H9.2z"/></symbol>
|
||||||
|
<symbol id="i-charbubble" viewBox="0 0 24 24"><path d="M5.5 4h13A2.5 2.5 0 0 1 21 6.5v8a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 3.5V17h-1A2.5 2.5 0 0 1 3 14.5v-8A2.5 2.5 0 0 1 5.5 4Z"/><path d="M8.5 8.2h7M12 7v6.5M9.2 11c1.2 1.4 3.6 2.4 5.6 2.5"/></symbol>
|
||||||
|
<!-- 实心图标:回复候选卡片左侧的 bubble.left.fill / briefcase.fill / theatermasks.fill -->
|
||||||
|
<symbol id="i-bubble-fill" viewBox="0 0 24 24"><path d="M6 3.5h12A2.5 2.5 0 0 1 20.5 6v8.5A2.5 2.5 0 0 1 18 17h-7.3L6.4 20.6c-.5.4-1.2 0-1.2-.6V17A2.5 2.5 0 0 1 3.5 14.5V6A2.5 2.5 0 0 1 6 3.5Z"/></symbol>
|
||||||
|
<symbol id="i-briefcase-fill" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M9.8 3.5h4.4a1.8 1.8 0 0 1 1.8 1.8V7h3a2.5 2.5 0 0 1 2.5 2.5v8.5a2.5 2.5 0 0 1-2.5 2.5H5A2.5 2.5 0 0 1 2.5 18V9.5A2.5 2.5 0 0 1 5 7h3V5.3a1.8 1.8 0 0 1 1.8-1.8Zm.1 1.8V7h4.2V5.3H9.9Z"/></symbol>
|
||||||
|
<symbol id="i-masks-fill" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M2.5 4.6c3-1.5 6.6-1.5 9.6 0v5.2c0 3.2-2.1 5.9-4.8 5.9S2.5 13 2.5 9.8V4.6Zm2.2 2.8a1.2.9 0 1 0 2.4 0a1.2.9 0 1 0-2.4 0Zm3.2 0a1.2.9 0 1 0 2.4 0a1.2.9 0 1 0-2.4 0Zm-2.6 3.4c1.1 1.3 3 1.3 4.1 0Z"/><path fill-rule="evenodd" d="M12.6 9.3c3-1.5 6.1-1.5 8.9 0v4.9c0 3.1-2 5.6-4.45 5.6s-4.45-2.5-4.45-5.6V9.3Zm2 2.7a1.1.85 0 1 0 2.2 0a1.1.85 0 1 0-2.2 0Zm2.9 0a1.1.85 0 1 0 2.2 0a1.1.85 0 1 0-2.2 0Zm-1.9 4.8c.9-1 2.6-1 3.5 0Z"/></symbol>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@@ -347,12 +359,20 @@
|
|||||||
fetchpriority="high"
|
fetchpriority="high"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- 语音胶囊:模拟键盘的录音态,声波 + 流式转写循环演示三种能力 -->
|
<!--
|
||||||
|
语音胶囊:复刻键盘里真实的麦克风胶囊状态——
|
||||||
|
待机绿色麦克风 → 轻点听写变红并显示 7 条声波 → 插入成功打勾;
|
||||||
|
长按问 AI 变青色 → 思考中转圈 → 给出回答。
|
||||||
|
-->
|
||||||
<div class="voice-capsule" aria-hidden="true">
|
<div class="voice-capsule" aria-hidden="true">
|
||||||
<span class="capsule-orb"><svg class="i"><use href="#i-mic"/></svg></span>
|
<span class="capsule-mic">
|
||||||
<span class="capsule-wave" data-wave="22"></span>
|
<svg class="i cap-mic"><use href="#i-mic"/></svg>
|
||||||
|
<span class="capsule-wave" data-wave="7"></span>
|
||||||
|
<span class="capsule-spin"></span>
|
||||||
|
<svg class="i cap-check"><use href="#i-check"/></svg>
|
||||||
|
</span>
|
||||||
<span class="capsule-body">
|
<span class="capsule-body">
|
||||||
<span class="capsule-tag">听写</span>
|
<span class="capsule-tag">轻点听写</span>
|
||||||
<span class="capsule-line">帮我订周五晚上七点的位子,四个人</span><span class="caret"></span>
|
<span class="capsule-line">帮我订周五晚上七点的位子,四个人</span><span class="caret"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -432,7 +452,7 @@
|
|||||||
data-en="One real conversation, three moves: the voice keyboard listens, the reply engine says it in your voice, and the agentic keyboard takes the next step. Scroll to watch it work."
|
data-en="One real conversation, three moves: the voice keyboard listens, the reply engine says it in your voice, and the agentic keyboard takes the next step. Scroll to watch it work."
|
||||||
>一次真实的聊天,三个动作完成:语音输入法负责听写,嘴替输入法负责把话说好,Agentic 键盘负责下一步。向下滚动,看它怎样工作。</p>
|
>一次真实的聊天,三个动作完成:语音输入法负责听写,嘴替输入法负责把话说好,Agentic 键盘负责下一步。向下滚动,看它怎样工作。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="story" id="storyStage">
|
<div class="story" id="storyStage" data-bar="tabs" data-mic="idle" data-ctx="hint" data-surface="main">
|
||||||
<div class="story-sticky">
|
<div class="story-sticky">
|
||||||
<div class="story-layout">
|
<div class="story-layout">
|
||||||
<ol class="story-acts">
|
<ol class="story-acts">
|
||||||
@@ -450,7 +470,7 @@
|
|||||||
<div class="act-body">
|
<div class="act-body">
|
||||||
<p class="act-kicker" data-zh="嘴替输入法" data-en="Replies in your voice">嘴替输入法</p>
|
<p class="act-kicker" data-zh="嘴替输入法" data-en="Replies in your voice">嘴替输入法</p>
|
||||||
<h3 data-zh="随口一说,发出去很得体。" data-en="Say it casually. Send it well.">随口一说,发出去很得体。</h3>
|
<h3 data-zh="随口一说,发出去很得体。" data-en="Say it casually. Send it well.">随口一说,发出去很得体。</h3>
|
||||||
<p data-zh="嘴替输入法学习你确认过的真实表达,再用普通、正式或轻松的语气替你把话说好。先预览、再插入,口吻始终是你的。" data-en="It learns from writing you have confirmed, then says it better — normal, formal, or playful. Preview first, insert when ready; it still sounds like you.">嘴替输入法学习你确认过的真实表达,再用普通、正式或轻松的语气替你把话说好。先预览、再插入,口吻始终是你的。</p>
|
<p data-zh="复制对方的消息,嘴替输入法就按你的口吻备好普通、正式、轻松趣味三条回复,点一下即插入。回复偏好学自你确认过的真实表达。" data-en="Copy their message and it drafts three replies in your voice — ordinary, formal, and relaxed & playful. Tap one to insert. It learns from writing you have confirmed.">复制对方的消息,嘴替输入法就按你的口吻备好普通、正式、轻松趣味三条回复,点一下即插入。回复偏好学自你确认过的真实表达。</p>
|
||||||
<span class="act-bar" aria-hidden="true"><i></i></span>
|
<span class="act-bar" aria-hidden="true"><i></i></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -459,7 +479,7 @@
|
|||||||
<div class="act-body">
|
<div class="act-body">
|
||||||
<p class="act-kicker" data-zh="Agentic 键盘" data-en="Agentic keyboard">Agentic 键盘</p>
|
<p class="act-kicker" data-zh="Agentic 键盘" data-en="Agentic keyboard">Agentic 键盘</p>
|
||||||
<h3 data-zh="看懂内容,替你行动。" data-en="It reads the room, then acts.">看懂内容,替你行动。</h3>
|
<h3 data-zh="看懂内容,替你行动。" data-en="It reads the room, then acts.">看懂内容,替你行动。</h3>
|
||||||
<p data-zh="复制一段地址或时间,本地语义分析就知道下一步:导航、建日程、写待办或回复,最多推荐 5 个相关操作,日程经 EventKit 直接写入。" data-en="Copy an address or a time and on-device analysis knows what comes next — navigate, add an event, create a to-do, or reply. Up to five relevant actions; events are written natively through EventKit.">复制一段地址或时间,本地语义分析就知道下一步:导航、建日程、写待办或回复,最多推荐 5 个相关操作,日程经 EventKit 直接写入。</p>
|
<p data-zh="复制一段带时间或地址的文字,本地语义分析判断下一步,键盘顶栏随即换成相关操作:回复、日程、待办、导航……点「日程」,即经 EventKit 写入日历。" data-en="Copy text with a time or an address and on-device analysis works out the next step — the top bar turns into relevant actions: Reply, Events, Tasks, Navigate… Tap Events and it lands in Calendar through EventKit.">复制一段带时间或地址的文字,本地语义分析判断下一步,键盘顶栏随即换成相关操作:回复、日程、待办、导航……点「日程」,即经 EventKit 写入日历。</p>
|
||||||
<span class="act-bar" aria-hidden="true"><i></i></span>
|
<span class="act-bar" aria-hidden="true"><i></i></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -478,8 +498,8 @@
|
|||||||
<span class="event-icon"><svg class="i"><use href="#i-event"/></svg></span>
|
<span class="event-icon"><svg class="i"><use href="#i-event"/></svg></span>
|
||||||
<span class="event-copy">
|
<span class="event-copy">
|
||||||
<small data-zh="已添加到日历" data-en="Added to Calendar">已添加到日历</small>
|
<small data-zh="已添加到日历" data-en="Added to Calendar">已添加到日历</small>
|
||||||
<b data-zh="晚餐 · 小林" data-en="Dinner with Alex">晚餐 · 小林</b>
|
<b data-zh="开会 · 小林" data-en="Meeting with Alex">开会 · 小林</b>
|
||||||
<span data-zh="周五 19:00 · 南京西路 1266 号" data-en="Fri 7:00 PM · 1266 W Nanjing Rd">周五 19:00 · 南京西路 1266 号</span>
|
<span data-zh="明天 15:00 · 3 楼会议室" data-en="Tomorrow 3:00 PM · Room 3F">明天 15:00 · 3 楼会议室</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="event-check"><svg class="i"><use href="#i-check"/></svg></span>
|
<span class="event-check"><svg class="i"><use href="#i-check"/></svg></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -490,11 +510,17 @@
|
|||||||
<small>iMessage</small>
|
<small>iMessage</small>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 对话:消息按滚动进度依次出现,旧消息随新消息上移(底部对齐) -->
|
||||||
<div class="chat-body">
|
<div class="chat-body">
|
||||||
<div class="bubble in b-ask" data-zh="周五晚上一起吃饭吗?" data-en="Dinner on Friday night?">周五晚上一起吃饭吗?</div>
|
<div class="bubble in b-m1" data-zh="方案初稿收到没?" data-en="Did you get the first draft?">方案初稿收到没?</div>
|
||||||
<div class="bubble out b-sent" data-zh="好呀!周五七点不见不散,位子包在我身上~" data-en="Yes! Friday at 7 — leave the table to me.">好呀!周五七点不见不散,位子包在我身上~</div>
|
<div class="bubble out b-o1" data-zh="收到了,我下午看完给你反馈。" data-en="Got it — I’ll review it this afternoon and get back to you.">收到了,我下午看完给你反馈。</div>
|
||||||
<div class="bubble in b-addr">
|
<div class="bubble in b-m2">
|
||||||
<span data-zh="好!地址发你:南京西路 1266 号 3 楼" data-en="Great — 1266 W Nanjing Rd, 3F">好!地址发你:南京西路 1266 号 3 楼</span>
|
<span data-zh="方案我看了,明天能不能把预算那一版也发我?" data-en="Read the proposal. Can you send the budget version tomorrow?">方案我看了,明天能不能把预算那一版也发我?</span>
|
||||||
|
<span class="copy-toast"><svg class="i"><use href="#i-check"/></svg><span data-zh="已复制" data-en="Copied">已复制</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="bubble out b-o2" data-zh="好的,我整理一下预算版,明天上午发你。" data-en="Sure, I’ll put the budget version together and send it over tomorrow morning.">好的,我整理一下预算版,明天上午发你。</div>
|
||||||
|
<div class="bubble in b-m3">
|
||||||
|
<span data-zh="好,那明天下午三点开会,3 楼会议室见。" data-en="Great — meeting tomorrow at 3 PM, Room 3F.">好,那明天下午三点开会,3 楼会议室见。</span>
|
||||||
<span class="copy-toast"><svg class="i"><use href="#i-check"/></svg><span data-zh="已复制" data-en="Copied">已复制</span></span>
|
<span class="copy-toast"><svg class="i"><use href="#i-check"/></svg><span data-zh="已复制" data-en="Copied">已复制</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,31 +531,86 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="compose-send"><svg class="i"><use href="#i-arrow-up"/></svg></span>
|
<span class="compose-send"><svg class="i"><use href="#i-arrow-up"/></svg></span>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
|
键盘:按 OSGKeyboard 真实的「AI 助手」界面复刻(AIKeyboardView)。
|
||||||
|
顶栏四种状态:常规(标志 + AI/中文/EN 切换 + 翻译/剪贴板)、录音中(仅关闭)、
|
||||||
|
AI 结果(粘贴 + 关闭)、复制后(粘贴 + 相关技能胶囊 + 关闭)。
|
||||||
|
状态由 site.js 写到 .story 的 data-bar / data-mic / data-ctx / data-surface 上;
|
||||||
|
无 JavaScript 时保持 HTML 中的默认值,即键盘待机画面。
|
||||||
|
-->
|
||||||
<div class="kb">
|
<div class="kb">
|
||||||
<div class="kb-bar">
|
<div class="kb-top">
|
||||||
<span class="kb-status"><i></i><span data-zh="正在聆听…" data-en="Listening…">正在聆听…</span></span>
|
<div class="kb-bar kb-bar-tabs">
|
||||||
<span class="kb-tones">
|
<span class="kb-logo"><i></i><i></i><i></i></span>
|
||||||
<span data-tone="0" data-zh="普通" data-en="Normal">普通</span>
|
<span class="kb-seg">
|
||||||
<span data-tone="1" data-zh="正式" data-en="Formal">正式</span>
|
<span class="is-on"><svg class="i"><use href="#i-sparkle"/></svg></span>
|
||||||
<span data-tone="2" data-zh="轻松" data-en="Playful">轻松</span>
|
<span>中文</span>
|
||||||
<em data-zh="学自你的表达" data-en="Learned from you">学自你的表达</em>
|
<span>EN</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="kb-skills">
|
<span class="kb-chips">
|
||||||
<span><svg class="i"><use href="#i-nav"/></svg><span data-zh="导航" data-en="Navigate">导航</span></span>
|
<span class="kb-chip"><svg class="i"><use href="#i-charbubble"/></svg></span>
|
||||||
<span class="skill-event"><svg class="i"><use href="#i-event"/></svg><span data-zh="日程" data-en="Event">日程</span></span>
|
<span class="kb-chip"><svg class="i"><use href="#i-clipboard"/></svg></span>
|
||||||
<span><svg class="i"><use href="#i-reply"/></svg><span data-zh="回复" data-en="Reply">回复</span></span>
|
|
||||||
<span><svg class="i"><use href="#i-task"/></svg><span data-zh="待办" data-en="To-do">待办</span></span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="kb-bar kb-bar-cancel">
|
||||||
|
<span class="kb-chip kb-x"><svg class="i"><use href="#i-close"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-bar kb-bar-paste">
|
||||||
|
<span class="kb-cap" data-zh="粘贴" data-en="Paste">粘贴</span>
|
||||||
|
<span class="kb-chip kb-x"><svg class="i"><use href="#i-close"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-bar kb-bar-skills">
|
||||||
|
<span class="kb-caps">
|
||||||
|
<span class="kb-cap" data-zh="粘贴" data-en="Paste">粘贴</span>
|
||||||
|
<span class="kb-cap" data-zh="回复" data-en="Reply">回复</span>
|
||||||
|
<span class="kb-cap cap-event" data-zh="日程" data-en="Events">日程</span>
|
||||||
|
<span class="kb-cap" data-zh="待办" data-en="Tasks">待办</span>
|
||||||
|
<span class="kb-cap" data-zh="导航" data-en="Navigate">导航</span>
|
||||||
|
</span>
|
||||||
|
<span class="kb-chip kb-x"><svg class="i"><use href="#i-close"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="kb-stage">
|
||||||
<div class="kb-main">
|
<div class="kb-main">
|
||||||
<span class="kb-wave" data-wave="11"></span>
|
<!-- 上下文区:提示语 / 实时转写 / 状态文字 / 技能提示,同一时刻只显示一种 -->
|
||||||
<span class="kb-mic"><svg class="i"><use href="#i-mic"/></svg></span>
|
<div class="kb-ctx">
|
||||||
<span class="kb-wave kb-wave-r" data-wave="11"></span>
|
<span class="ctx ctx-hint" data-zh="轻点听写 · 长按问 AI" data-en="Tap to dictate · Hold to ask AI">轻点听写 · 长按问 AI</span>
|
||||||
|
<span class="ctx ctx-live" id="storyLive"></span>
|
||||||
|
<span class="ctx ctx-think" data-zh="AI 正在思考…" data-en="AI is thinking…">AI 正在思考…</span>
|
||||||
|
<span class="ctx ctx-extract" data-zh="正在提取日程…" data-en="Finding events…">正在提取日程…</span>
|
||||||
|
<span class="ctx ctx-tip" data-zh="正在加入日历…" data-en="Adding to Calendar…">正在加入日历…</span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-row">
|
||||||
|
<span class="kb-circle"><svg class="i"><use href="#i-delete"/></svg></span>
|
||||||
|
<span class="kb-mic">
|
||||||
|
<svg class="i mic-icon"><use href="#i-mic"/></svg>
|
||||||
|
<span class="mic-wave" data-wave="7"></span>
|
||||||
|
<span class="mic-spin"></span>
|
||||||
|
<svg class="i mic-check"><use href="#i-check"/></svg>
|
||||||
|
</span>
|
||||||
|
<span class="kb-circle"><svg class="i"><use href="#i-space"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-row kb-row-low">
|
||||||
|
<span class="kb-circle kb-undo"><svg class="i"><use href="#i-undo"/></svg></span>
|
||||||
|
<span class="kb-action"><svg class="i"><use href="#i-return"/></svg></span>
|
||||||
|
<span class="kb-circle kb-edit"><svg class="i"><use href="#i-pencil"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 回复候选:真实界面中替换整个键盘主体,点一张即插入 -->
|
||||||
|
<div class="kb-variants">
|
||||||
|
<span class="kb-card" data-card="0">
|
||||||
|
<svg class="i i-solid"><use href="#i-bubble-fill"/></svg>
|
||||||
|
<span><small data-zh="普通" data-en="Ordinary">普通</small><b data-zh="好的,我整理一下预算版,明天上午发你。" data-en="Sure, I’ll put the budget version together and send it over tomorrow morning.">好的,我整理一下预算版,明天上午发你。</b></span>
|
||||||
|
</span>
|
||||||
|
<span class="kb-card" data-card="1">
|
||||||
|
<svg class="i i-solid"><use href="#i-briefcase-fill"/></svg>
|
||||||
|
<span><small data-zh="正式" data-en="Formal">正式</small><b data-zh="收到,预算版本我今晚整理完毕,明日上午发送给您。" data-en="Received. I’ll finalize the budget version tonight and send it to you tomorrow morning.">收到,预算版本我今晚整理完毕,明日上午发送给您。</b></span>
|
||||||
|
</span>
|
||||||
|
<span class="kb-card" data-card="2">
|
||||||
|
<svg class="i i-solid"><use href="#i-masks-fill"/></svg>
|
||||||
|
<span><small data-zh="轻松趣味" data-en="Relaxed & playful">轻松趣味</small><b data-zh="行嘞~预算表这就去梳妆打扮,明早准时上桌!" data-en="You got it — the budget sheet is getting dressed up and will be on your desk first thing.">行嘞~预算表这就去梳妆打扮,明早准时上桌!</b></span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="kb-keys">
|
|
||||||
<span class="kb-key"><svg class="i"><use href="#i-globe"/></svg></span>
|
|
||||||
<span class="kb-key kb-space" data-zh="空格" data-en="space">空格</span>
|
|
||||||
<span class="kb-key kb-return" data-zh="发送" data-en="send">发送</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -659,7 +740,7 @@
|
|||||||
></video>
|
></video>
|
||||||
</div>
|
</div>
|
||||||
<h3 data-zh="个性回复,三种语气" data-en="Replies in three tones">个性回复,三种语气</h3>
|
<h3 data-zh="个性回复,三种语气" data-en="Replies in three tones">个性回复,三种语气</h3>
|
||||||
<p data-zh="普通、正式、轻松趣味,一键切换;回复偏好与听写偏好分别学习,选择反馈仅保留在本机。" data-en="Ordinary, formal, or playful — one tap apart. Reply and dictation preferences are learned separately, and choice feedback stays on device.">普通、正式、轻松趣味,一键切换;回复偏好与听写偏好分别学习,选择反馈仅保留在本机。</p>
|
<p data-zh="普通、正式、轻松趣味,一键切换;回复偏好与听写偏好分别学习,选择反馈仅保留在本机。" data-en="Ordinary, formal, or relaxed & playful — one tap apart. Reply and dictation preferences are learned separately, and choice feedback stays on device.">普通、正式、轻松趣味,一键切换;回复偏好与听写偏好分别学习,选择反馈仅保留在本机。</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="personal-card spot reveal" style="--d:.16s">
|
<article class="personal-card spot reveal" style="--d:.16s">
|
||||||
<div class="personal-media">
|
<div class="personal-media">
|
||||||
@@ -688,14 +769,14 @@
|
|||||||
<p class="eyebrow" data-zh="Agentic 键盘" data-en="Agentic keyboard">Agentic 键盘</p>
|
<p class="eyebrow" data-zh="Agentic 键盘" data-en="Agentic keyboard">Agentic 键盘</p>
|
||||||
<h2 data-split data-zh="复制之后,它替你做。" data-en="Copy it. Consider it done.">复制之后,它替你做。</h2>
|
<h2 data-split data-zh="复制之后,它替你做。" data-en="Copy it. Consider it done.">复制之后,它替你做。</h2>
|
||||||
<p
|
<p
|
||||||
data-zh="复制一段文字,本地语义分析会理解它该去哪——回复、总结、翻译、待办、日程、导航,从完整技能目录中推荐最多 5 个相关操作,全程不上传剪贴板原文。"
|
data-zh="复制一段文字,本地语义分析会理解它该去哪——回复、总结、翻译、待办、日程、导航。键盘顶栏只按相关度列出用得上的技能,分析全程不上传剪贴板原文。"
|
||||||
data-en="Copy any text and on-device semantic analysis understands where it should go — reply, summarize, translate, to-dos, events, navigation — picking up to five relevant actions from the full skill catalog, without the clipboard text ever leaving the device."
|
data-en="Copy any text and on-device semantic analysis understands where it should go — reply, summarize, translate, tasks, events, navigation. The keyboard’s top bar lists only the skills that fit, ranked by relevance, and the analysis never uploads the clipboard text."
|
||||||
>复制一段文字,本地语义分析会理解它该去哪——回复、总结、翻译、待办、日程、导航,从完整技能目录中推荐最多 5 个相关操作,全程不上传剪贴板原文。</p>
|
>复制一段文字,本地语义分析会理解它该去哪——回复、总结、翻译、待办、日程、导航。键盘顶栏只按相关度列出用得上的技能,分析全程不上传剪贴板原文。</p>
|
||||||
<div class="skill-chips" aria-label="内置技能" data-zh-label="内置技能" data-en-label="Built-in skills">
|
<div class="skill-chips" aria-label="内置技能" data-zh-label="内置技能" data-en-label="Built-in skills">
|
||||||
<span style="--ci:0"><svg class="i" aria-hidden="true"><use href="#i-reply"/></svg><span data-zh="回复" data-en="Reply">回复</span></span>
|
<span style="--ci:0"><svg class="i" aria-hidden="true"><use href="#i-reply"/></svg><span data-zh="回复" data-en="Reply">回复</span></span>
|
||||||
<span style="--ci:1"><svg class="i" aria-hidden="true"><use href="#i-summarize"/></svg><span data-zh="总结" data-en="Summarize">总结</span></span>
|
<span style="--ci:1"><svg class="i" aria-hidden="true"><use href="#i-summarize"/></svg><span data-zh="总结" data-en="Summary">总结</span></span>
|
||||||
<span style="--ci:2"><svg class="i" aria-hidden="true"><use href="#i-translate"/></svg><span data-zh="翻译" data-en="Translate">翻译</span></span>
|
<span style="--ci:2"><svg class="i" aria-hidden="true"><use href="#i-translate"/></svg><span data-zh="翻译" data-en="Translate">翻译</span></span>
|
||||||
<span style="--ci:3"><svg class="i" aria-hidden="true"><use href="#i-task"/></svg><span data-zh="待办" data-en="To-dos">待办</span></span>
|
<span style="--ci:3"><svg class="i" aria-hidden="true"><use href="#i-task"/></svg><span data-zh="待办" data-en="Tasks">待办</span></span>
|
||||||
<span style="--ci:4"><svg class="i" aria-hidden="true"><use href="#i-event"/></svg><span data-zh="日程" data-en="Events">日程</span></span>
|
<span style="--ci:4"><svg class="i" aria-hidden="true"><use href="#i-event"/></svg><span data-zh="日程" data-en="Events">日程</span></span>
|
||||||
<span style="--ci:5"><svg class="i" aria-hidden="true"><use href="#i-note"/></svg><span data-zh="备忘录" data-en="Notes">备忘录</span></span>
|
<span style="--ci:5"><svg class="i" aria-hidden="true"><use href="#i-note"/></svg><span data-zh="备忘录" data-en="Notes">备忘录</span></span>
|
||||||
<span style="--ci:6"><svg class="i" aria-hidden="true"><use href="#i-nav"/></svg><span data-zh="导航" data-en="Navigate">导航</span></span>
|
<span style="--ci:6"><svg class="i" aria-hidden="true"><use href="#i-nav"/></svg><span data-zh="导航" data-en="Navigate">导航</span></span>
|
||||||
@@ -706,20 +787,85 @@
|
|||||||
<li data-zh="可创建自定义技能:名称、图标、提示词与可选快捷指令" data-en="Create custom skills with a name, icon, prompt, and an optional Shortcut">可创建自定义技能:名称、图标、提示词与可选快捷指令</li>
|
<li data-zh="可创建自定义技能:名称、图标、提示词与可选快捷指令" data-en="Create custom skills with a name, icon, prompt, and an optional Shortcut">可创建自定义技能:名称、图标、提示词与可选快捷指令</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-frame unmask reveal" data-parallax="0.06" style="--d:.12s">
|
<!--
|
||||||
<video
|
技能演示:复用故事区的真实键盘组件,循环播放
|
||||||
src="assets/whats-new/clipboard-skills-zh.mp4"
|
「复制 → 顶栏换成相关技能 → 点日程 → 提取 → 写入日历」。
|
||||||
data-zh-src="assets/whats-new/clipboard-skills-zh.mp4"
|
-->
|
||||||
data-en-src="assets/whats-new/clipboard-skills-en.mp4"
|
<div
|
||||||
autoplay
|
class="media-frame skills-frame unmask reveal"
|
||||||
muted
|
data-parallax="0.06"
|
||||||
loop
|
style="--d:.12s"
|
||||||
playsinline
|
role="img"
|
||||||
preload="metadata"
|
aria-label="复制含时间的文字后,键盘顶栏出现粘贴、回复、日程、待办、导航等技能,点日程即写入日历"
|
||||||
data-zh-label="OSGKeyboard Agentic 剪贴板技能实机演示"
|
data-zh-label="复制含时间的文字后,键盘顶栏出现粘贴、回复、日程、待办、导航等技能,点日程即写入日历"
|
||||||
data-en-label="OSGKeyboard agentic clipboard skills recorded on device"
|
data-en-label="After copying text with a time, the keyboard’s top bar shows Paste, Reply, Events, Tasks, and Navigate; tapping Events adds it to Calendar"
|
||||||
aria-label="OSGKeyboard Agentic 剪贴板技能实机演示"
|
>
|
||||||
></video>
|
<div class="skills-demo" id="skillsDemo" data-bar="skills" data-mic="idle" data-ctx="none" data-surface="main" aria-hidden="true">
|
||||||
|
<div class="sd-source">
|
||||||
|
<span class="sd-msg">
|
||||||
|
<span data-zh="明天下午三点开会,地点:南京西路 1266 号 3 楼" data-en="Meeting tomorrow at 3 PM — 1266 W Nanjing Rd, 3F">明天下午三点开会,地点:南京西路 1266 号 3 楼</span>
|
||||||
|
<span class="copy-toast"><svg class="i"><use href="#i-check"/></svg><span data-zh="已复制" data-en="Copied">已复制</span></span>
|
||||||
|
</span>
|
||||||
|
<span class="event-banner sd-event">
|
||||||
|
<span class="event-icon"><svg class="i"><use href="#i-event"/></svg></span>
|
||||||
|
<span class="event-copy">
|
||||||
|
<small data-zh="已添加到日历" data-en="Added to Calendar">已添加到日历</small>
|
||||||
|
<b data-zh="开会" data-en="Meeting">开会</b>
|
||||||
|
<span data-zh="明天 15:00 · 南京西路 1266 号 3 楼" data-en="Tomorrow 3:00 PM · 1266 W Nanjing Rd, 3F">明天 15:00 · 南京西路 1266 号 3 楼</span>
|
||||||
|
</span>
|
||||||
|
<span class="event-check"><svg class="i"><use href="#i-check"/></svg></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kb">
|
||||||
|
<div class="kb-top">
|
||||||
|
<div class="kb-bar kb-bar-tabs">
|
||||||
|
<span class="kb-logo"><i></i><i></i><i></i></span>
|
||||||
|
<span class="kb-seg">
|
||||||
|
<span class="is-on"><svg class="i"><use href="#i-sparkle"/></svg></span>
|
||||||
|
<span>中文</span>
|
||||||
|
<span>EN</span>
|
||||||
|
</span>
|
||||||
|
<span class="kb-chips">
|
||||||
|
<span class="kb-chip"><svg class="i"><use href="#i-charbubble"/></svg></span>
|
||||||
|
<span class="kb-chip"><svg class="i"><use href="#i-clipboard"/></svg></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-bar kb-bar-skills">
|
||||||
|
<span class="kb-caps">
|
||||||
|
<span class="kb-cap" data-zh="粘贴" data-en="Paste">粘贴</span>
|
||||||
|
<span class="kb-cap" data-zh="回复" data-en="Reply">回复</span>
|
||||||
|
<span class="kb-cap cap-event" data-zh="日程" data-en="Events">日程</span>
|
||||||
|
<span class="kb-cap" data-zh="待办" data-en="Tasks">待办</span>
|
||||||
|
<span class="kb-cap" data-zh="导航" data-en="Navigate">导航</span>
|
||||||
|
</span>
|
||||||
|
<span class="kb-chip kb-x"><svg class="i"><use href="#i-close"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="kb-stage">
|
||||||
|
<div class="kb-main">
|
||||||
|
<div class="kb-ctx">
|
||||||
|
<span class="ctx ctx-hint" data-zh="轻点听写 · 长按问 AI" data-en="Tap to dictate · Hold to ask AI">轻点听写 · 长按问 AI</span>
|
||||||
|
<span class="ctx ctx-extract" data-zh="正在提取日程…" data-en="Finding events…">正在提取日程…</span>
|
||||||
|
<span class="ctx ctx-tip" data-zh="正在加入日历…" data-en="Adding to Calendar…">正在加入日历…</span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-row">
|
||||||
|
<span class="kb-circle"><svg class="i"><use href="#i-delete"/></svg></span>
|
||||||
|
<span class="kb-mic">
|
||||||
|
<svg class="i mic-icon"><use href="#i-mic"/></svg>
|
||||||
|
<span class="mic-spin"></span>
|
||||||
|
<svg class="i mic-check"><use href="#i-check"/></svg>
|
||||||
|
</span>
|
||||||
|
<span class="kb-circle"><svg class="i"><use href="#i-space"/></svg></span>
|
||||||
|
</div>
|
||||||
|
<div class="kb-row kb-row-low">
|
||||||
|
<span class="kb-circle kb-undo"><svg class="i"><use href="#i-undo"/></svg></span>
|
||||||
|
<span class="kb-action"><svg class="i"><use href="#i-return"/></svg></span>
|
||||||
|
<span class="kb-circle kb-edit"><svg class="i"><use href="#i-pencil"/></svg></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -928,14 +1074,14 @@
|
|||||||
<div class="faq-list">
|
<div class="faq-list">
|
||||||
<details class="faq-item reveal">
|
<details class="faq-item reveal">
|
||||||
<summary data-zh="什么是嘴替输入法?" data-en="What is a “zuiti” (speak-for-me) keyboard?">什么是嘴替输入法?</summary>
|
<summary data-zh="什么是嘴替输入法?" data-en="What is a “zuiti” (speak-for-me) keyboard?">什么是嘴替输入法?</summary>
|
||||||
<p data-zh="嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,并提供普通、正式、轻松三种回复语气,结果先预览、再插入。" data-en="“Zuiti” (嘴替) is Chinese internet slang for someone who says what you mean, better than you would. OSGKeyboard learns from dictation you have confirmed, then turns casual speech into polished text in your own tone — normal, formal, or playful — and shows a preview before inserting.">
|
<p data-zh="嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,复制对方的消息后,会按你的口吻给出普通、正式、轻松趣味三条回复,点选即插入。" data-en="“Zuiti” (嘴替) is Chinese internet slang for someone who says what you mean, better than you would. OSGKeyboard learns from dictation you have confirmed, then turns casual speech into polished text in your own tone. Copy a message and it drafts three replies — ordinary, formal, and relaxed & playful — tap one to insert.">
|
||||||
嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,并提供普通、正式、轻松三种回复语气,结果先预览、再插入。
|
嘴替输入法是能替你把话说好的输入法:你随口说出想法,它在保留你个人语气的前提下整理成得体的文字。OSGKeyboard 会从你确认过的真实听写中学习说话风格,复制对方的消息后,会按你的口吻给出普通、正式、轻松趣味三条回复,点选即插入。
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
<details class="faq-item reveal">
|
<details class="faq-item reveal">
|
||||||
<summary data-zh="什么是 Agentic 键盘(Agentic keyboard)?" data-en="What is an agentic keyboard?">什么是 Agentic 键盘(Agentic keyboard)?</summary>
|
<summary data-zh="什么是 Agentic 键盘(Agentic keyboard)?" data-en="What is an agentic keyboard?">什么是 Agentic 键盘(Agentic keyboard)?</summary>
|
||||||
<p data-zh="Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会推荐最多 5 个相关操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。" data-en="An agentic keyboard does more than enter text — it understands content and takes the next step for you. In OSGKeyboard, when you copy text, on-device semantic analysis suggests up to five relevant actions such as reply, summarize, translate, create a to-do, add an event, or navigate. To-dos and events are written natively through EventKit, and suggestions never upload the clipboard text.">
|
<p data-zh="Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会在键盘顶栏按相关度列出可用操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。" data-en="An agentic keyboard does more than enter text — it understands content and takes the next step for you. In OSGKeyboard, when you copy text, on-device semantic analysis lists the relevant actions in the keyboard’s top bar, such as reply, summarize, translate, create a task, add an event, or navigate. To-dos and events are written natively through EventKit, and suggestions never upload the clipboard text.">
|
||||||
Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会推荐最多 5 个相关操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。
|
Agentic 键盘不只负责输入文字,还能理解内容并替你完成下一步。在 OSGKeyboard 中复制一段文字后,本地语义分析会在键盘顶栏按相关度列出可用操作,例如回复、总结、翻译、写待办、建日程或导航;待办与日程通过系统 EventKit 直接写入,推荐过程不会上传剪贴板原文。
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
<details class="faq-item reveal">
|
<details class="faq-item reveal">
|
||||||
@@ -1057,6 +1203,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="assets/site.js?v=20261001"></script>
|
<script src="assets/site.js?v=20261001b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ Current repository version: 2.1.0 (build 110). Later work is listed under CHANGE
|
|||||||
## Category definitions
|
## Category definitions
|
||||||
|
|
||||||
- AI voice keyboard (AI 语音输入法): OSGKeyboard turns speech into text with on-device recognition by default, lets you hold the mic to ask AI with preview before insert, and lets you revise the last insertion by voice.
|
- AI voice keyboard (AI 语音输入法): OSGKeyboard turns speech into text with on-device recognition by default, lets you hold the mic to ask AI with preview before insert, and lets you revise the last insertion by voice.
|
||||||
- "Zuiti" keyboard (嘴替输入法): "嘴替" is Chinese internet slang for someone who says what you mean, better than you would. OSGKeyboard learns from dictation you have confirmed and rewrites casual speech in your own tone — normal, formal, or playful — with a preview before insert.
|
- "Zuiti" keyboard (嘴替输入法): "嘴替" is Chinese internet slang for someone who says what you mean, better than you would. OSGKeyboard learns from dictation you have confirmed; copy a message and it drafts three replies in your tone — ordinary, formal, and relaxed & playful — and you tap one to insert.
|
||||||
- Agentic keyboard (Agentic 键盘): a keyboard that understands content and takes the next step. When you copy text, OSGKeyboard's on-device semantic analysis suggests up to five relevant actions (reply, summarize, translate, to-do, event, notes, navigation); to-dos and events are written natively through EventKit, and suggestions do not upload the clipboard text.
|
- Agentic keyboard (Agentic 键盘): a keyboard that understands content and takes the next step. When you copy text, OSGKeyboard's on-device semantic analysis turns the keyboard's top bar into the relevant actions, ranked by relevance (reply, summarize, translate, tasks, events, notes, navigation); to-dos and events are written natively through EventKit, and suggestions do not upload the clipboard text.
|
||||||
|
|
||||||
## Product facts
|
## Product facts
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user