Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/web-ask-question-wizard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Rework the web ask-user-question card into a step-by-step wizard so multi-question navigation and the final Submit action are easier to see.
5 changes: 5 additions & 0 deletions .changeset/web-session-initial-page-size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Show the first five sessions per workspace in the web sidebar instead of ten.
156 changes: 124 additions & 32 deletions apps/kimi-web/src/components/chat/QuestionCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ function goNext(): void {
if (step.value < total.value - 1) step.value++;
}

function goToStep(index: number): void {
if (index >= 0 && index < total.value) step.value = index;
}

function isQuestionAnswered(qid: string): boolean {
const a = answers.value[qid];
if (!a) return false;
if (a.kind === 'multi') return a.optionIds.length > 0;
if (a.kind === 'multiWithOther') return a.optionIds.length > 0 || a.otherText.trim().length > 0;
if (a.kind === 'other') return a.text.trim().length > 0;
return true;
}

function isCurrentAnswered(): boolean {
return isQuestionAnswered(current.value.id);
}

// ---------------------------------------------------------------------------
// Per-question answers: Record<questionId, QuestionAnswer>
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -145,14 +162,7 @@ function isOtherSelected(qid: string): boolean {

function canSubmit(): boolean {
// All questions must have an answer
return props.question.questions.every((qi) => {
const a = answers.value[qi.id];
if (!a) return false;
if (a.kind === 'multi') return a.optionIds.length > 0;
if (a.kind === 'multiWithOther') return a.optionIds.length > 0 || a.otherText.trim().length > 0;
if (a.kind === 'other') return a.text.trim().length > 0;
return true;
});
return props.question.questions.every((qi) => isQuestionAnswered(qi.id));
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -184,7 +194,15 @@ function handleKeydown(e: KeyboardEvent): void {
if (minimized.value && e.key !== 'Escape') return;

if (e.key === 'Escape') { e.preventDefault(); dismiss(); return; }
if (e.key === 'Enter') { e.preventDefault(); submit(); return; }
if (e.key === 'Enter') {
e.preventDefault();
if (step.value < total.value - 1 && isCurrentAnswered()) {
goNext();
} else if (canSubmit()) {
submit();
}
return;
}

const num = parseInt(e.key, 10);
if (!isNaN(num) && num >= 1 && num <= 9) {
Expand All @@ -208,14 +226,10 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));

<template>
<div class="qcard" :class="{ minimized }">
<!-- Step indicator (multi-question) -->
<!-- Header: title, step count, minimize -->
<div class="qh">
<span class="qtitle">{{ t('question.title') }}</span>
<template v-if="total > 1 && !minimized">
<span class="qstep">{{ t('question.step', { current: step + 1, total }) }}</span>
<button class="qnav" :disabled="step === 0" @click="goBack">{{ t('question.prev') }}</button>
<button class="qnav" :disabled="step === total - 1" @click="goNext">{{ t('question.next') }}</button>
</template>
<span v-if="total > 1 && !minimized" class="qstep">{{ t('question.step', { current: step + 1, total }) }}</span>
<!-- When minimized, surface the question text so the bar stays identifiable -->
<span v-if="minimized" class="qmin-peek">{{ current.question }}</span>
<button
Expand All @@ -231,6 +245,22 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));

<!-- Current question -->
<div v-if="!minimized" class="qbody">
<!-- Stepper: only shown when there are multiple questions -->
<div v-if="total > 1" class="qsteps" role="tablist" :aria-label="t('question.step', { current: step + 1, total })">
<button
v-for="(q, i) in props.question.questions"
:key="q.id"
type="button"
class="qstep-dot"
:class="{ active: i === step, answered: isQuestionAnswered(q.id) }"
:aria-selected="i === step"
:aria-label="t('question.step', { current: i + 1, total })"
@click="goToStep(i)"
>
<span class="qstep-num">{{ i + 1 }}</span>
</button>
</div>

<!-- Header chip -->
<div v-if="current.header" class="qheader-chip">{{ current.header }}</div>

Expand Down Expand Up @@ -293,10 +323,31 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
</div>
</div>

<!-- Action buttons -->
<!-- Action buttons: primary action first, all left-aligned; dismiss is
de-emphasized as a text-only button. -->
<div v-if="!minimized" class="qfooter">
<button class="qbtn pri" :disabled="!canSubmit()" @click="submit">{{ t('question.submit') }}</button>
<button class="qbtn" @click="dismiss">{{ t('question.dismiss') }}</button>
<button
v-if="step < total - 1"
type="button"
class="qbtn pri qfooter-main"
:disabled="!isCurrentAnswered()"
@click="goNext"
>{{ t('question.nextQuestion') }}</button>
<button
v-else
type="button"
class="qbtn pri qfooter-main"
:disabled="!canSubmit()"
@click="submit"
>{{ t('question.submit') }}</button>
<button
v-if="total > 1"
type="button"
class="qbtn"
:disabled="step === 0"
@click="goBack"
>{{ t('question.back') }}</button>
<button type="button" class="qbtn qbtn-text" @click="dismiss">{{ t('question.dismiss') }}</button>
</div>
</div>
</template>
Expand All @@ -322,18 +373,6 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
}
.qtitle { color: var(--blue2); font-weight: 700; }
.qstep { color: var(--muted); font-size: calc(var(--ui-font-size) - 3px); margin-left: 4px; }
.qnav {
font-family: var(--mono);
font-size: calc(var(--ui-font-size) - 3px);
padding: 2px 8px;
border: 1px solid var(--line);
border-radius: 3px;
background: var(--bg);
color: var(--dim);
cursor: pointer;
}
.qnav:disabled { color: var(--faint); cursor: default; }
.qnav:not(:disabled):hover { background: var(--panel2); }

/* Minimize toggle — pinned to the right of the header row. */
.qmin {
Expand Down Expand Up @@ -368,6 +407,40 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
/* Body */
.qbody { padding: 12px 14px; }

/* Stepper */
.qsteps {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 10px;
}
.qstep-dot {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid var(--line);
background: var(--bg);
color: var(--dim);
font-size: calc(var(--ui-font-size) - 2px);
cursor: pointer;
padding: 0;
transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.qstep-dot:hover:not(.active) { background: var(--panel2); }
.qstep-dot.active {
border-color: var(--blue);
background: var(--blue);
color: var(--bg);
font-weight: 700;
}
.qstep-dot.answered:not(.active) {
border-color: var(--blue);
color: var(--blue);
}

.qheader-chip {
display: inline-block;
font-size: max(9px, calc(var(--ui-font-size) - 3.5px));
Expand Down Expand Up @@ -474,6 +547,18 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
}
.qbtn.pri:hover:not(:disabled) { background: var(--blue2); }
.qbtn:disabled { opacity: 0.45; cursor: default; }
.qbtn-text {
border-color: transparent;
background: transparent;
color: var(--muted);
padding-left: 8px;
padding-right: 8px;
}
.qbtn-text:hover:not(:disabled) {
background: transparent;
color: var(--text);
text-decoration: underline;
}

/* =========================================================================
MOBILE (≤640px): bigger option taps, comfortable nav, and full-width footer
Expand All @@ -482,11 +567,17 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
========================================================================= */
@media (max-width: 640px) {
.qh { padding: 9px 12px; flex-wrap: wrap; row-gap: 6px; }
.qnav { min-height: 34px; padding: 5px 12px; font-size: var(--ui-font-size-xs); border-radius: 6px; }

.qbody { padding: 14px; }
.qtext { font-size: var(--ui-font-size); }

/* Stepper → slightly larger tap targets. */
.qstep-dot {
width: 28px;
height: 28px;
font-size: var(--ui-font-size-xs);
}

/* Options → taller, finger-friendly rows. Label + description already stack
via .qopt-text, so no flex-wrap hack is needed. */
.qopt {
Expand All @@ -498,13 +589,14 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
.qopt-desc { font-size: var(--ui-font-size-xs); }
.other-input { flex-basis: 100%; min-height: 28px; }

/* Footer → full-width stacked buttons, Submit on top. */
/* Footer → full-width stacked buttons, Next/Submit on top. */
.qfooter { flex-direction: column; gap: 8px; padding: 12px 14px max(14px, env(safe-area-inset-bottom)); }
.qbtn {
width: 100%;
min-height: 46px;
font-size: var(--ui-font-size);
border-radius: 8px;
}
.qfooter-main { order: -1; }
}
</style>
2 changes: 1 addition & 1 deletion apps/kimi-web/src/composables/client/useWorkspaceState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export function useWorkspaceState(rawState: ExtendedState, deps: UseWorkspaceSta
const SESSION_PAGE_SIZE = 100;
// Sessions fetched per workspace on first load — keeps the initial request
// count at (number of workspaces) and each response small.
const SESSIONS_INITIAL_PAGE_SIZE = 10;
const SESSIONS_INITIAL_PAGE_SIZE = 5;
// Sessions fetched per "load more" click within a workspace.
const SESSIONS_LOAD_MORE_SIZE = 30;

Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-web/src/i18n/locales/en/question.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export default {
title: 'Question',
step: 'Q{current}/{total}',
prev: '‹ Prev',
next: 'Next ›',
back: '‹ Back',
nextQuestion: 'Next question ›',
otherDefault: 'Other…',
submit: 'Submit',
dismiss: 'Dismiss',
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-web/src/i18n/locales/zh/question.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export default {
title: '提问',
step: 'Q{current}/{total}',
prev: '‹ 上一',
next: '下一 ›',
back: '‹ 返回',
nextQuestion: '下一题 ›',
otherDefault: '其他…',
submit: '提交',
dismiss: '放弃',
Expand Down
Loading