diff --git a/apps/kimi-web/src/App.vue b/apps/kimi-web/src/App.vue index d71852bc..8bca4045 100644 --- a/apps/kimi-web/src/App.vue +++ b/apps/kimi-web/src/App.vue @@ -38,7 +38,7 @@ import type { SwarmMember } from './composables/swarmGroups'; import ServerAuthDialog from './components/ServerAuthDialog.vue'; import { initServerAuth, onAuthRequired } from './api/daemon/serverAuth'; import type { AppConfig, ThinkingLevel } from './api/types'; -import { commitLevel, effectiveThinkingLevel, segmentsFor } from './lib/modelThinking'; +import { commitLevel, defaultThinkingLevelFor, effectiveThinkingLevel, segmentsFor } from './lib/modelThinking'; import { stripSkillPrefix } from './lib/slashCommands'; import Button from './components/ui/Button.vue'; import IconButton from './components/ui/IconButton.vue'; @@ -119,7 +119,7 @@ function nextThinkingLevel(current: ThinkingLevel | undefined): ThinkingLevel { // No stored preference means the model default is in effect — cycle from // there; a level the model doesn't declare (indexOf → -1) starts the cycle // at the first segment. - const idx = segs.indexOf(effectiveThinkingLevel(model, current)); + const idx = segs.indexOf(effectiveThinkingLevel(model, current ?? defaultThinkingLevelFor(model))); const next = segs[(idx + 1) % segs.length] ?? segs[0] ?? 'off'; return commitLevel(model, next); } @@ -129,7 +129,7 @@ function nextThinkingLevel(current: ThinkingLevel | undefined): ThinkingLevel { // will actually run, not a blank. const statusPanelThinking = computed(() => { const model = client.models.value.find((m) => m.id === client.status.value.modelId); - return effectiveThinkingLevel(model, client.thinking.value); + return effectiveThinkingLevel(model, client.thinking.value ?? defaultThinkingLevelFor(model)); }); // First-run onboarding (language + welcome greeting). Shown until the user diff --git a/apps/kimi-web/src/api/daemon/client.ts b/apps/kimi-web/src/api/daemon/client.ts index 5ff57e74..08775c5a 100644 --- a/apps/kimi-web/src/api/daemon/client.ts +++ b/apps/kimi-web/src/api/daemon/client.ts @@ -933,6 +933,21 @@ export class DaemonKimiWebApi implements KimiWebApi { }; } + /** POST /sessions/{id}/fs:mkdir — create a directory. FS_ALREADY_EXISTS (40919) + * is treated as success (the directory is ready to use). */ + async makeDirectory( + sessionId: string, + input: { path: string }, + ): Promise<{ made: boolean; path: string }> { + const data = await this.http.post<{ made: boolean; path: string }>( + `/sessions/${encodeURIComponent(sessionId)}/fs:mkdir`, + { path: input.path }, + { allowCodes: [40919] }, // FS_ALREADY_EXISTS + ); + return { made: data?.made ?? false, path: input.path }; + } + + async readFile( sessionId: string, input: { path: string; offset?: number; length?: number }, diff --git a/apps/kimi-web/src/api/types.ts b/apps/kimi-web/src/api/types.ts index cbcde2d0..e16acbd3 100644 --- a/apps/kimi-web/src/api/types.ts +++ b/apps/kimi-web/src/api/types.ts @@ -706,6 +706,8 @@ export interface KimiWebApi { getTerminal(sessionId: string, terminalId: string): Promise; closeTerminal(sessionId: string, terminalId: string): Promise<{ closed: true }>; listDirectory(sessionId: string, input: { path?: string; depth?: number; includeGitStatus?: boolean }): Promise<{ items: FsEntry[]; childrenByPath?: Record; truncated: boolean }>; + /** Create a directory. Returns FS_ALREADY_EXISTS (40919) when the path already exists — callers should treat that as success. */ + makeDirectory(sessionId: string, input: { path: string }): Promise<{ made: boolean; path: string }>; readFile(sessionId: string, input: { path: string; offset?: number; length?: number }): Promise<{ path: string; content: string; encoding: 'utf-8' | 'base64'; size: number; truncated: boolean; etag: string; mime: string; languageId?: string; lineCount?: number; isBinary: boolean }>; searchFiles(sessionId: string, input: { query: string; limit?: number }): Promise<{ items: Array<{ path: string; name: string; kind: FsKind; score: number; matchPositions: number[] }>; truncated: boolean }>; grepFiles(sessionId: string, input: { pattern: string; regex?: boolean; caseSensitive?: boolean }): Promise<{ files: Array<{ path: string; matches: Array<{ line: number; col: number; text: string; before: string[]; after: string[] }> }>; filesScanned: number; truncated: boolean; elapsedMs: number }>; diff --git a/apps/kimi-web/src/components/chat/Composer.vue b/apps/kimi-web/src/components/chat/Composer.vue index 111c06fd..f3e33dcd 100644 --- a/apps/kimi-web/src/components/chat/Composer.vue +++ b/apps/kimi-web/src/components/chat/Composer.vue @@ -10,6 +10,7 @@ import type { FileItem } from './MentionMenu.vue'; import type { ActivationBadges, ConversationStatus, PermissionMode, QueuedPromptView } from '../../types'; import type { AppGoal, AppModel, AppSkill, ThinkingLevel } from '../../api/types'; import { + defaultThinkingLevelFor, commitLevel, effectiveThinkingLevel, effortLabel, @@ -617,7 +618,7 @@ const thinkingSegments = computed(() => segmentsFor(currentModel.value)); // the model default, which is what the daemon will resolve for the prompt. A // level the model doesn't declare highlights no segment but still shows in the // suffix. -const thinkingLevel = computed(() => effectiveThinkingLevel(currentModel.value, props.thinking)); +const thinkingLevel = computed(() => effectiveThinkingLevel(currentModel.value, props.thinking ?? defaultThinkingLevelFor(currentModel.value))); const activeThinkingSegment = computed(() => { const segs = thinkingSegments.value; return segs.includes(thinkingLevel.value) ? thinkingLevel.value : ''; diff --git a/apps/kimi-web/src/i18n/locales/en/study.ts b/apps/kimi-web/src/i18n/locales/en/study.ts index 86f739d1..0843e52a 100644 --- a/apps/kimi-web/src/i18n/locales/en/study.ts +++ b/apps/kimi-web/src/i18n/locales/en/study.ts @@ -71,21 +71,38 @@ export default { outlineDesigning: 'Kimi is designing the outline from your material and goal…', outlineCounts: '{chapters} chapters · {pages} pages · {quizzes} quizzes', outlineReviseTitle: 'Ask for a change', - outlineRevisePlaceholder: 'e.g. “Add a chapter on boundary cases”…', + outlineRevisePlaceholder: 'Describe the outline change in one sentence…', + outlineReviseExample: 'For example: assume no prior knowledge; lead with examples; reduce to 6 lessons; move risk analysis first.', outlineReviseAction: 'Update outline', outlineReviseSent: 'Change requested — a new outline revision will appear here once it passes review.', - outlineReviseError: 'The outline could not be updated right now. Try again in a moment.', + outlineReviseWaiting: 'Kimi is revising and checking the outline. The current version stays in place until the new one is ready.', + outlineReviseSuccess: 'The new outline passed its checks and is ready for your confirmation.', + outlineReviseError: 'This change did not finish. The previous outline is intact, and you can retry now.', planRevision: 'Outline revision {revision}', generate: 'Generate course', generating: 'Generating lessons…', generationProgress: '{published} of {total} lessons ready', - upgradeTitle: 'Ready to go deeper?', - upgradeDesc: 'Upgrade to Deep mastery without uploading again — your quick survey stays as the starting point.', - upgradeAction: 'Upgrade to Deep', + upgradeTitle: 'Deepen this course', + upgradeDesc: 'No need to upload again. Kimi will read the source carefully and improve the whole course while keeping existing lessons until replacements pass review.', + upgradeAction: 'Deepen course', learningTitle: 'Your course is ready', lessonsReady: '{published} of {total} lessons published', lessonsUnavailable: 'Lesson files are not available yet — they appear here as they are published.', lessonLoadError: 'This lesson could not be loaded.', + lessonReviseAction: 'Revise this lesson', + lessonRegenerateAction: 'Regenerate this lesson', + lessonReviseTitle: 'Revise the current lesson in one sentence', + lessonRevisePlaceholder: 'Describe how you want this lesson adjusted…', + lessonReviseExample: 'For example: add a beginner-friendly example; slow down the second explanation; add a self-check tied to the learning objective.', + lessonReviseSubmit: 'Update lesson', + lessonActionCancel: 'Cancel', + lessonRegenerateConfirmTitle: 'Regenerate the current lesson?', + lessonRegenerateConfirmBody: 'Only this lesson will be replaced. The current version stays visible until the replacement passes its checks.', + lessonRegenerateConfirmAction: 'Regenerate lesson', + lessonReviseWaiting: 'Kimi is revising this lesson. The current version stays in place until the replacement passes its checks.', + lessonRegenerateWaiting: 'Kimi is regenerating this lesson. The current version stays in place until the replacement passes its checks.', + lessonChangeSuccess: 'The current lesson passed its checks and was updated.', + lessonChangeError: 'This update did not finish. The previous lesson is intact, and you can retry now.', type_lesson: 'Lesson', type_reference: 'Reference', type_quiz: 'Quiz', diff --git a/apps/kimi-web/src/i18n/locales/zh/study.ts b/apps/kimi-web/src/i18n/locales/zh/study.ts index 6c362f56..e224d558 100644 --- a/apps/kimi-web/src/i18n/locales/zh/study.ts +++ b/apps/kimi-web/src/i18n/locales/zh/study.ts @@ -71,21 +71,38 @@ export default { outlineDesigning: 'Kimi 正在根据材料和目标设计大纲…', outlineCounts: '{chapters} 章 · {pages} 页 · {quizzes} 个测验', outlineReviseTitle: '提出修改', - outlineRevisePlaceholder: '例如:“加一章关于边界情况的讲解”…', + outlineRevisePlaceholder: '用一句话说明你想怎样调整大纲…', + outlineReviseExample: '例如:面向零基础读者;先讲案例再讲原理;缩减为 6 节;把风险分析移到最前面。', outlineReviseAction: '更新大纲', outlineReviseSent: '已提交修改——通过检查后,新的大纲版本会出现在这里。', - outlineReviseError: '暂时无法更新大纲,请稍后再试。', + outlineReviseWaiting: 'Kimi 正在按你的要求修改并检查大纲,当前版本会保留到新版本就绪。', + outlineReviseSuccess: '新大纲已通过检查并更新。请确认后再开始生成课程。', + outlineReviseError: '这次修改没有完成,原大纲已保留。你可以直接重试。', planRevision: '大纲版本 {revision}', generate: '生成课程', generating: '正在生成课程…', generationProgress: '{total} 节课已就绪 {published} 节', - upgradeTitle: '想学得更深?', - upgradeDesc: '无需重新上传即可升级为深度精学,快速通读成果会保留为起点。', - upgradeAction: '升级为深度精学', + upgradeTitle: '深入完善课程', + upgradeDesc: '不用重新上传。Kimi 会仔细通读材料并完善整门课,已有课节会保留到新版本通过检查。', + upgradeAction: '深入完善', learningTitle: '课程已就绪', lessonsReady: '已发布 {published} / {total} 节课', lessonsUnavailable: '课程文件尚未就绪——发布后会出现在这里。', lessonLoadError: '这节课暂时无法加载。', + lessonReviseAction: '修改本节', + lessonRegenerateAction: '重新生成本节', + lessonReviseTitle: '用一句话修改当前课节', + lessonRevisePlaceholder: '说明你希望这节课怎样调整…', + lessonReviseExample: '例如:补一个零基础也能懂的例子;把第二部分讲得更慢;增加一道对应学习目标的自测题。', + lessonReviseSubmit: '更新本节', + lessonActionCancel: '取消', + lessonRegenerateConfirmTitle: '确定重新生成当前课节?', + lessonRegenerateConfirmBody: '只会替换当前课节。新版本通过检查前,你仍然会看到现在的内容。', + lessonRegenerateConfirmAction: '确定重新生成', + lessonReviseWaiting: 'Kimi 正在按要求修改本节,旧版本会保留到新版本通过检查。', + lessonRegenerateWaiting: 'Kimi 正在重新生成本节,旧版本会保留到新版本通过检查。', + lessonChangeSuccess: '当前课节已通过检查并更新。', + lessonChangeError: '这次更新没有完成,原课节已保留。你可以直接重试。', type_lesson: '课程', type_reference: '参考', type_quiz: '测验', diff --git a/apps/kimi-web/src/study/StudyApp.vue b/apps/kimi-web/src/study/StudyApp.vue index 73f75055..8749b757 100644 --- a/apps/kimi-web/src/study/StudyApp.vue +++ b/apps/kimi-web/src/study/StudyApp.vue @@ -1,7 +1,6 @@ - +