Skip to content

Commit f9bf67b

Browse files
committed
feat(website): showcase TUI input modes
1 parent 37f5d0e commit f9bf67b

2 files changed

Lines changed: 261 additions & 42 deletions

File tree

website/theme/components/HomeLayout.tsx

Lines changed: 171 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ const runtimeLayers = [
310310
}>;
311311

312312
const tuiDemoPhases = [
313+
'slash',
314+
'mention',
315+
'shell',
316+
'research',
313317
'compose',
314318
'plan',
315319
'delegate',
@@ -318,7 +322,30 @@ const tuiDemoPhases = [
318322
'remoteui',
319323
'answer',
320324
] as const;
321-
const tuiDemoDurations = [2400, 1900, 2600, 2300, 2100, 2600, 4200];
325+
type TuiDemoPhase = (typeof tuiDemoPhases)[number];
326+
const tuiDemoDurations = {
327+
slash: 1900,
328+
mention: 1900,
329+
shell: 2200,
330+
research: 2400,
331+
compose: 2400,
332+
plan: 1900,
333+
delegate: 2600,
334+
track: 2300,
335+
artifact: 2100,
336+
remoteui: 2600,
337+
answer: 4200,
338+
} satisfies Record<TuiDemoPhase, number>;
339+
const tuiDemoIndex = {
340+
compose: tuiDemoPhases.indexOf('compose'),
341+
plan: tuiDemoPhases.indexOf('plan'),
342+
delegate: tuiDemoPhases.indexOf('delegate'),
343+
track: tuiDemoPhases.indexOf('track'),
344+
artifact: tuiDemoPhases.indexOf('artifact'),
345+
remoteui: tuiDemoPhases.indexOf('remoteui'),
346+
answer: tuiDemoPhases.indexOf('answer'),
347+
} as const;
348+
type TuiComposerMode = 'default' | 'shell' | 'research';
322349
type TuiDemoStatus = 'pending' | 'active' | 'done';
323350
const tuiDemoStatusGlyph: Record<TuiDemoStatus, string> = {
324351
pending: '◻',
@@ -446,6 +473,17 @@ const copy = {
446473
tuiWorkspace: '~/workspace/a3s',
447474
tuiMode: 'default',
448475
tuiTip: '输入消息 · / 打开命令 · Shift+Tab 切换模式 · Ctrl+C 两次退出',
476+
tuiSlashInput: '/effort',
477+
tuiSlashEffort: '调整推理强度',
478+
tuiSlashModel: '切换模型与 Provider',
479+
tuiSlashTheme: '选择终端主题',
480+
tuiMentionInput: '检查 @AGENTS.md',
481+
tuiFilePicker: '@ file · ↑/↓ · →/← folder · Enter · Esc',
482+
tuiFileInstructions: '项目指令',
483+
tuiFileManifest: 'Rust workspace',
484+
tuiFileWebsite: '文档应用',
485+
tuiShellInput: 'cargo test -p a3s-code-core',
486+
tuiResearchInput: '调研 A3S Code 最新发布与兼容性变化',
449487
tuiUser: 'You',
450488
tuiWorking: 'Working…',
451489
tuiPlan: 'Plan',
@@ -546,6 +584,17 @@ const copy = {
546584
tuiMode: 'default',
547585
tuiTip:
548586
'Type a message · / for commands · Shift+Tab cycles mode · Ctrl+C twice to exit',
587+
tuiSlashInput: '/effort',
588+
tuiSlashEffort: 'adjust model effort',
589+
tuiSlashModel: 'switch model and provider',
590+
tuiSlashTheme: 'select the terminal theme',
591+
tuiMentionInput: 'Review @AGENTS.md',
592+
tuiFilePicker: '@ file · ↑/↓ · →/← folder · Enter · Esc',
593+
tuiFileInstructions: 'workspace instructions',
594+
tuiFileManifest: 'Rust workspace',
595+
tuiFileWebsite: 'documentation app',
596+
tuiShellInput: 'cargo test -p a3s-code-core',
597+
tuiResearchInput: 'Research the latest A3S Code releases and compatibility',
549598
tuiUser: 'You',
550599
tuiWorking: 'Working…',
551600
tuiPlan: 'Plan',
@@ -678,23 +727,91 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
678727
const [activeIndex, setActiveIndex] = useState(tuiDemoPhases.length - 1);
679728
const [isPlaying, setIsPlaying] = useState(false);
680729
const [isVisible, setIsVisible] = useState(false);
681-
const [typedCount, setTypedCount] = useState(labels.flowTask.length);
730+
const [typedCount, setTypedCount] = useState(0);
682731
const active = tuiDemoPhases[activeIndex] ?? tuiDemoPhases[0];
732+
const composerDemos: Partial<
733+
Record<
734+
TuiDemoPhase,
735+
{ mode: TuiComposerMode; symbol: string; text: string }
736+
>
737+
> = {
738+
slash: { mode: 'default', symbol: '❯', text: labels.tuiSlashInput },
739+
mention: { mode: 'default', symbol: '❯', text: labels.tuiMentionInput },
740+
shell: { mode: 'shell', symbol: '!', text: labels.tuiShellInput },
741+
research: {
742+
mode: 'research',
743+
symbol: '?',
744+
text: labels.tuiResearchInput,
745+
},
746+
compose: { mode: 'default', symbol: '❯', text: labels.flowTask },
747+
};
748+
const composerDemo = composerDemos[active];
749+
const composerMode = composerDemo?.mode ?? 'default';
750+
const composerText = composerDemo?.text ?? '';
751+
const typedComposerText = composerText.slice(0, typedCount);
752+
const composerSymbol = composerDemo?.symbol ?? '❯';
753+
const composerStatus =
754+
composerMode === 'research'
755+
? '◇ deep research · --web | --local-only'
756+
: '◇ high';
757+
const mentionStart = typedComposerText.lastIndexOf('@');
758+
const mentionQuery =
759+
mentionStart >= 0
760+
? typedComposerText.slice(mentionStart + 1).toLowerCase()
761+
: '';
762+
const inputMenuIsOpen =
763+
(active === 'slash' && typedComposerText.startsWith('/')) ||
764+
(active === 'mention' && mentionStart >= 0);
765+
const slashMenuItems: Array<[string, string]> = [
766+
['/effort', labels.tuiSlashEffort],
767+
['/model', labels.tuiSlashModel],
768+
['/theme', labels.tuiSlashTheme],
769+
];
770+
const fileMenuItems: Array<[string, string]> = [
771+
['AGENTS.md', labels.tuiFileInstructions],
772+
['Cargo.toml', labels.tuiFileManifest],
773+
['website/', labels.tuiFileWebsite],
774+
];
775+
const inputMenuItems: Array<[string, string]> =
776+
active === 'slash'
777+
? slashMenuItems.filter(([command]) =>
778+
command.startsWith(typedComposerText || '/'),
779+
)
780+
: active === 'mention'
781+
? fileMenuItems.filter(([path]) =>
782+
path.toLowerCase().includes(mentionQuery),
783+
)
784+
: [];
683785
const isRunning = isPlaying && isVisible;
684-
const isWorking = activeIndex > 0 && activeIndex < tuiDemoPhases.length - 1;
786+
const isWorking =
787+
activeIndex > tuiDemoIndex.compose && activeIndex < tuiDemoIndex.answer;
685788
const planItems: Array<{ label: string; status: TuiDemoStatus }> = [
686789
{
687790
label: labels.tuiPlanInspect,
688791
status:
689-
activeIndex < 1 ? 'pending' : activeIndex === 1 ? 'active' : 'done',
792+
activeIndex < tuiDemoIndex.plan
793+
? 'pending'
794+
: activeIndex === tuiDemoIndex.plan
795+
? 'active'
796+
: 'done',
690797
},
691798
{
692799
label: labels.tuiPlanDelegate,
693-
status: activeIndex < 2 ? 'pending' : activeIndex < 4 ? 'active' : 'done',
800+
status:
801+
activeIndex < tuiDemoIndex.delegate
802+
? 'pending'
803+
: activeIndex < tuiDemoIndex.artifact
804+
? 'active'
805+
: 'done',
694806
},
695807
{
696808
label: labels.tuiPlanPublish,
697-
status: activeIndex < 4 ? 'pending' : activeIndex < 5 ? 'active' : 'done',
809+
status:
810+
activeIndex < tuiDemoIndex.artifact
811+
? 'pending'
812+
: activeIndex < tuiDemoIndex.remoteui
813+
? 'active'
814+
: 'done',
698815
},
699816
];
700817
const subagents: Array<{
@@ -706,19 +823,19 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
706823
{
707824
name: 'explore',
708825
task: labels.tuiAgentExploreTask,
709-
status: activeIndex >= 3 ? 'done' : 'active',
826+
status: activeIndex >= tuiDemoIndex.track ? 'done' : 'active',
710827
tokens: '0.8k',
711828
},
712829
{
713830
name: 'test',
714831
task: labels.tuiAgentTestTask,
715-
status: activeIndex >= 4 ? 'done' : 'active',
832+
status: activeIndex >= tuiDemoIndex.artifact ? 'done' : 'active',
716833
tokens: '1.5k',
717834
},
718835
{
719836
name: 'review',
720837
task: labels.tuiAgentReviewTask,
721-
status: activeIndex >= 3 ? 'done' : 'active',
838+
status: activeIndex >= tuiDemoIndex.track ? 'done' : 'active',
722839
tokens: '0.9k',
723840
},
724841
];
@@ -727,18 +844,22 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
727844
).length;
728845
const runningSubagentCount = subagents.length - completedSubagentCount;
729846
const visibleSubagents =
730-
activeIndex === 2
847+
activeIndex === tuiDemoIndex.delegate
731848
? subagents
732-
: activeIndex === 3
849+
: activeIndex === tuiDemoIndex.track
733850
? subagents.filter((agent) => agent.status === 'active')
734851
: [];
735852
const subagentTokens =
736-
activeIndex < 3 ? '1.8k' : activeIndex < 4 ? '2.7k' : '3.2k';
737-
const artifactIsPublished = activeIndex >= 5;
738-
const remoteUiIsReady = activeIndex >= 6;
853+
activeIndex < tuiDemoIndex.track
854+
? '1.8k'
855+
: activeIndex < tuiDemoIndex.artifact
856+
? '2.7k'
857+
: '3.2k';
858+
const artifactIsPublished = activeIndex >= tuiDemoIndex.remoteui;
859+
const remoteUiIsReady = activeIndex >= tuiDemoIndex.answer;
739860
const typingInterval = Math.max(
740-
18,
741-
Math.floor(1550 / Math.max(labels.flowTask.length, 1)),
861+
16,
862+
Math.floor(1200 / Math.max(composerText.length, 1)),
742863
);
743864

744865
useEffect(() => {
@@ -772,36 +893,30 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
772893
useEffect(() => {
773894
if (!isRunning) return undefined;
774895

775-
const delay = tuiDemoDurations[activeIndex] ?? 1600;
896+
const delay = tuiDemoDurations[active] ?? 1600;
776897
const timer = window.setTimeout(() => {
898+
setTypedCount(0);
777899
if (activeIndex >= tuiDemoPhases.length - 1) {
778-
setTypedCount(0);
779900
setActiveIndex(0);
780901
} else {
781902
setActiveIndex((index) => index + 1);
782903
}
783904
}, delay);
784905

785906
return () => window.clearTimeout(timer);
786-
}, [activeIndex, isRunning]);
907+
}, [active, activeIndex, isRunning]);
787908

788909
useEffect(() => {
789-
if (!isRunning || activeIndex !== 0) return undefined;
790-
if (typedCount >= labels.flowTask.length) return undefined;
910+
if (!isRunning || composerText.length === 0) return undefined;
911+
if (typedCount >= composerText.length) return undefined;
791912

792913
const timer = window.setTimeout(
793914
() => setTypedCount((count) => count + 1),
794915
typingInterval,
795916
);
796917

797918
return () => window.clearTimeout(timer);
798-
}, [
799-
activeIndex,
800-
isRunning,
801-
labels.flowTask.length,
802-
typedCount,
803-
typingInterval,
804-
]);
919+
}, [active, composerText.length, isRunning, typedCount, typingInterval]);
805920

806921
function playFlow() {
807922
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
@@ -862,7 +977,7 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
862977
<p className="a3s-tui-tip">{labels.tuiTip}</p>
863978

864979
<div className="a3s-tui-transcript" aria-live="off">
865-
{activeIndex > 0 ? (
980+
{activeIndex > tuiDemoIndex.compose ? (
866981
<article className="a3s-tui-entry a3s-tui-entry--user">
867982
<span aria-hidden="true"></span>
868983
<div>
@@ -872,7 +987,7 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
872987
</article>
873988
) : null}
874989

875-
{activeIndex >= 4 ? (
990+
{activeIndex >= tuiDemoIndex.artifact ? (
876991
<article
877992
className={[
878993
'a3s-tui-artifact',
@@ -896,7 +1011,7 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
8961011
<span
8971012
className={[
8981013
'a3s-tui-open-view',
899-
activeIndex === 5 ? 'is-opening' : '',
1014+
activeIndex === tuiDemoIndex.remoteui ? 'is-opening' : '',
9001015
]
9011016
.filter(Boolean)
9021017
.join(' ')}
@@ -944,7 +1059,7 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
9441059
</article>
9451060
) : null}
9461061

947-
{activeIndex >= 6 ? (
1062+
{activeIndex >= tuiDemoIndex.answer ? (
9481063
<article className="a3s-tui-entry a3s-tui-entry--assistant">
9491064
<span aria-hidden="true"></span>
9501065
<div>
@@ -953,10 +1068,29 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
9531068
</div>
9541069
</article>
9551070
) : null}
1071+
1072+
{inputMenuIsOpen ? (
1073+
<div
1074+
aria-hidden="true"
1075+
className={`a3s-tui-input-menu is-${active}`}
1076+
>
1077+
{active === 'mention' ? (
1078+
<strong>{labels.tuiFilePicker}</strong>
1079+
) : null}
1080+
<ul>
1081+
{inputMenuItems.map(([label, detail], index) => (
1082+
<li className={index === 0 ? 'is-selected' : ''} key={label}>
1083+
<code>{label}</code>
1084+
<span>{detail}</span>
1085+
</li>
1086+
))}
1087+
</ul>
1088+
</div>
1089+
) : null}
9561090
</div>
9571091
</section>
9581092

959-
<section className="a3s-tui-composer">
1093+
<section className="a3s-tui-composer" data-input-mode={composerMode}>
9601094
<div className="a3s-tui-activity" aria-live="polite">
9611095
{isWorking ? (
9621096
<>
@@ -966,7 +1100,7 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
9661100
</>
9671101
) : null}
9681102
</div>
969-
{activeIndex >= 1 ? (
1103+
{activeIndex >= tuiDemoIndex.plan ? (
9701104
<section className="a3s-tui-plan" aria-label={labels.tuiPlan}>
9711105
<ol>
9721106
{planItems.map((item, index) => (
@@ -980,12 +1114,12 @@ function RuntimeExecutionFlow({ labels }: { labels: (typeof copy)[Locale] }) {
9801114
</section>
9811115
) : null}
9821116
<div className="a3s-tui-effort-rule">
983-
<span>◇ high</span>
1117+
<span>{composerStatus}</span>
9841118
</div>
9851119
<div className="a3s-tui-input">
986-
<span aria-hidden="true"></span>
1120+
<span aria-hidden="true">{composerSymbol}</span>
9871121
<p>
988-
{activeIndex === 0 ? labels.flowTask.slice(0, typedCount) : ''}
1122+
{typedComposerText}
9891123
<i aria-hidden="true" />
9901124
</p>
9911125
</div>

0 commit comments

Comments
 (0)