Skip to content

Commit 0d8b483

Browse files
committed
refactor(ui): 移动并重构设置相关工具函数至utils目录
- 将App.tsx中的设置读写及相关辅助函数移除 - 在ui/utils/index.ts新增对应工具函数实现,包括读取写入用户及项目设置 - 将DEFAULT_MODEL和DEFAULT_BASE_URL常量移至constants.ts统一管理 - 优化导入路径,修正组件和工具的引用路径 - 修改RawModeContext和openai-client模块中依赖import路径 - 统一和调整DropdownMenu相关组件的导入路径 - 使代码结构更清晰,职责划分更明确,提高维护性
1 parent acfacc9 commit 0d8b483

14 files changed

Lines changed: 198 additions & 188 deletions

File tree

src/common/openai-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as os from "os";
33
import * as path from "path";
44
import OpenAI from "openai";
55
import { Agent, fetch as undiciFetch } from "undici";
6-
import { resolveCurrentSettings } from "../ui/App";
6+
import { resolveCurrentSettings } from "../ui";
77

88
// Custom undici Agent with a 180-second keepAlive timeout. The default
99
// global fetch (undici) only keeps connections alive for 4 seconds, which

src/tests/dropdownMenu.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { test } from "node:test";
22
import assert from "node:assert/strict";
3-
import { calculateVisibleStart } from "../ui/DropdownMenu";
3+
import { calculateVisibleStart } from "../ui/components/DropdownMenu";
44

55
test("calculateVisibleStart centers active item when possible", () => {
66
// 10 items, max 5 visible, active index 4 (middle)

src/ui/App.tsx

Lines changed: 14 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
22
import { Box, Static, Text, useApp, useStdout, useWindowSize } from "ink";
33
import chalk from "chalk";
4-
import * as fs from "fs";
5-
import * as os from "os";
6-
import * as path from "path";
74
import { createOpenAIClient } from "../common/openai-client";
85
import {
96
type LlmStreamProgress,
@@ -17,17 +14,11 @@ import {
1714
type UndoTarget,
1815
type UserPromptContent,
1916
} from "../session";
20-
import {
21-
applyModelConfigSelection,
22-
type DeepcodingSettings,
23-
type ModelConfigSelection,
24-
type ResolvedDeepcodingSettings,
25-
resolveSettingsSources,
26-
} from "../settings";
27-
import { PromptInput, type PromptDraft, type PromptSubmission } from "./PromptInput";
17+
import { type ModelConfigSelection } from "../settings";
18+
import { type PromptDraft, PromptInput, type PromptSubmission } from "./PromptInput";
2819
import { MessageView, RawModeExitPrompt } from "./components";
2920
import { SessionList } from "./SessionList";
30-
import { UndoSelector, type UndoRestoreMode } from "./UndoSelector";
21+
import { type UndoRestoreMode, UndoSelector } from "./UndoSelector";
3122
import { buildLoadingText } from "./loadingText";
3223
import { findExpandedThinkingId } from "./thinkingState";
3324
import { WelcomeScreen } from "./WelcomeScreen";
@@ -43,12 +34,19 @@ import { PermissionPrompt, type PermissionPromptResult } from "./PermissionPromp
4334
import { buildExitSummaryText } from "./exitSummary";
4435
import { RawMode, useRawModeContext } from "./contexts";
4536
import { renderMessageToStdout } from "./components/MessageView/utils";
46-
import { renderRawModeMessages } from "./utils";
37+
import {
38+
buildPromptDraftFromSessionMessage,
39+
buildStatusLine,
40+
buildSyntheticUserMessage,
41+
formatModelConfig,
42+
isCollapsedThinking,
43+
isCurrentSessionEmpty,
44+
renderRawModeMessages,
45+
resolveCurrentSettings,
46+
writeModelConfigSelection,
47+
} from "./utils";
4748
import { ANSI_CLEAR_SCREEN } from "./constants";
4849

49-
const DEFAULT_MODEL = "deepseek-v4-pro";
50-
const DEFAULT_BASE_URL = "https://api.deepseek.com";
51-
5250
type View = "chat" | "session-list" | "undo" | "mcp-status";
5351

5452
type AppProps = {
@@ -807,163 +805,3 @@ function App({ projectRoot, initialPrompt, onRestart }: AppProps): React.ReactEl
807805
}
808806

809807
export default App;
810-
811-
function isCollapsedThinking(message: SessionMessage, expandedId: string | null): boolean {
812-
if (message.role !== "assistant") {
813-
return false;
814-
}
815-
if (!message.meta?.asThinking) {
816-
return false;
817-
}
818-
return message.id !== expandedId;
819-
}
820-
821-
function buildSyntheticUserMessage(content: string, imageCount: number): SessionMessage {
822-
const now = new Date().toISOString();
823-
return {
824-
id: `local-${Math.random().toString(36).slice(2)}`,
825-
sessionId: "local",
826-
role: "user",
827-
content,
828-
contentParams:
829-
imageCount > 0
830-
? Array.from({ length: imageCount }, () => ({
831-
type: "image_url",
832-
image_url: { url: "" },
833-
}))
834-
: null,
835-
messageParams: null,
836-
compacted: false,
837-
visible: true,
838-
createTime: now,
839-
updateTime: now,
840-
};
841-
}
842-
843-
export function buildPromptDraftFromSessionMessage(message: SessionMessage, nonce: number): PromptDraft {
844-
return {
845-
nonce,
846-
text: typeof message.content === "string" ? message.content : "",
847-
imageUrls: extractImageUrlsFromContentParams(message.contentParams),
848-
};
849-
}
850-
851-
function extractImageUrlsFromContentParams(contentParams: unknown): string[] {
852-
const params = Array.isArray(contentParams) ? contentParams : contentParams ? [contentParams] : [];
853-
const imageUrls: string[] = [];
854-
for (const param of params) {
855-
if (!param || typeof param !== "object") {
856-
continue;
857-
}
858-
const record = param as { type?: unknown; image_url?: { url?: unknown } };
859-
const url = record.image_url?.url;
860-
if (record.type === "image_url" && typeof url === "string" && url) {
861-
imageUrls.push(url);
862-
}
863-
}
864-
return imageUrls;
865-
}
866-
867-
function isCurrentSessionEmpty(sessionManager: SessionManager): boolean {
868-
const activeSessionId = sessionManager.getActiveSessionId();
869-
return !activeSessionId || !sessionManager.getSession(activeSessionId);
870-
}
871-
872-
function buildStatusLine(entry: SessionEntry): string {
873-
const parts: string[] = [];
874-
parts.push(`status: ${entry.status}`);
875-
if (typeof entry.activeTokens === "number" && entry.activeTokens > 0) {
876-
parts.push(`tokens: ${entry.activeTokens}`);
877-
}
878-
if (entry.failReason) {
879-
parts.push(`fail: ${entry.failReason}`);
880-
}
881-
return parts.join(" · ");
882-
}
883-
884-
export function readSettings(): DeepcodingSettings | null {
885-
return readSettingsFile(getUserSettingsPath());
886-
}
887-
888-
export function readProjectSettings(projectRoot: string = process.cwd()): DeepcodingSettings | null {
889-
return readSettingsFile(getProjectSettingsPath(projectRoot));
890-
}
891-
892-
function readSettingsFile(settingsPath: string): DeepcodingSettings | null {
893-
try {
894-
if (!fs.existsSync(settingsPath)) {
895-
return null;
896-
}
897-
const raw = fs.readFileSync(settingsPath, "utf8");
898-
return JSON.parse(raw) as DeepcodingSettings;
899-
} catch {
900-
return null;
901-
}
902-
}
903-
904-
export function writeSettings(settings: DeepcodingSettings): void {
905-
const settingsPath = getUserSettingsPath();
906-
writeSettingsFile(settingsPath, settings);
907-
}
908-
909-
export function writeProjectSettings(settings: DeepcodingSettings, projectRoot: string = process.cwd()): void {
910-
const settingsPath = getProjectSettingsPath(projectRoot);
911-
writeSettingsFile(settingsPath, settings);
912-
}
913-
914-
function writeSettingsFile(settingsPath: string, settings: DeepcodingSettings): void {
915-
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
916-
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
917-
}
918-
919-
export function writeModelConfigSelection(
920-
selection: ModelConfigSelection,
921-
current: ModelConfigSelection = resolveCurrentSettings(),
922-
projectRoot: string = process.cwd()
923-
): { changed: boolean; settings: DeepcodingSettings } {
924-
const projectSettingsPath = getProjectSettingsPath(projectRoot);
925-
const shouldWriteProjectSettings = fs.existsSync(projectSettingsPath);
926-
const rawSettings = shouldWriteProjectSettings ? readProjectSettings(projectRoot) : readSettings();
927-
const result = applyModelConfigSelection(rawSettings, current, selection);
928-
if (result.changed) {
929-
if (shouldWriteProjectSettings) {
930-
writeProjectSettings(result.settings, projectRoot);
931-
} else {
932-
writeSettings(result.settings);
933-
}
934-
}
935-
return result;
936-
}
937-
938-
export function resolveCurrentSettings(projectRoot: string = process.cwd()): ResolvedDeepcodingSettings {
939-
return resolveSettingsSources(
940-
readSettings(),
941-
readProjectSettings(projectRoot),
942-
{
943-
model: DEFAULT_MODEL,
944-
baseURL: DEFAULT_BASE_URL,
945-
},
946-
process.env
947-
);
948-
}
949-
950-
export { createOpenAIClient } from "../common/openai-client";
951-
952-
function getUserSettingsPath(): string {
953-
return path.join(os.homedir(), ".deepcode", "settings.json");
954-
}
955-
956-
function getProjectSettingsPath(projectRoot: string): string {
957-
return path.join(projectRoot, ".deepcode", "settings.json");
958-
}
959-
960-
function formatThinkingMode(settings: Pick<ModelConfigSelection, "thinkingEnabled" | "reasoningEffort">): string {
961-
if (!settings.thinkingEnabled) {
962-
return "no thinking";
963-
}
964-
return `thinking ${settings.reasoningEffort}`;
965-
}
966-
967-
function formatModelConfig(settings: ModelConfigSelection): string {
968-
return `${settings.model}, ${formatThinkingMode(settings)}`;
969-
}

src/ui/AppContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import { AppContext } from "./contexts";
33
import App from "./App";
4-
import { RawModeProvider } from "./contexts/RawModeContext";
4+
import { RawModeProvider } from "./contexts";
55

66
const AppContainer: React.FC<{
77
projectRoot: string;

src/ui/components/FileMentionMenu/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useEffect, useState } from "react";
22
import { Box, Text } from "ink";
33
import { useInput } from "ink";
4-
import DropdownMenu from "../../DropdownMenu";
4+
import DropdownMenu from "../DropdownMenu";
55
import type { FileMentionItem, FileMentionToken } from "../../fileMentions";
66

77
type Props = {

src/ui/components/ModelsDropdown/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect, useState } from "react";
22
import { useInput } from "ink";
3-
import DropdownMenu from "../../DropdownMenu";
3+
import DropdownMenu from "../DropdownMenu";
44
import type { ModelConfigSelection, ReasoningEffort } from "../../../settings";
55

66
type ModelStep = "model" | "thinking";

src/ui/components/RawModelDropdown/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from "react";
22
import { useInput } from "ink";
3-
import DropdownMenu from "../../DropdownMenu";
3+
import DropdownMenu from "../DropdownMenu";
44
import type { RawMode } from "../../contexts";
55
import { RAW_COMMAND_MODELS, useRawModeContext } from "../../contexts";
66

src/ui/components/SkillsDropdown/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import DropdownMenu from "../../DropdownMenu";
1+
import Index from "../DropdownMenu";
22
import React, { useEffect, useState } from "react";
33
import type { SkillInfo } from "../../../session";
44
import { useInput } from "ink";
@@ -52,7 +52,7 @@ const SkillsDropdown: React.FC<{
5252
}
5353

5454
return (
55-
<DropdownMenu
55+
<Index
5656
width={width}
5757
title="Select Skills"
5858
helpText="Space toggle · Enter toggle · Esc to close"

src/ui/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { RawModeExitPrompt } from "./RawModeExitPrompt";
44
export { default as SkillsDropdown } from "./SkillsDropdown";
55
export { default as ModelsDropdown } from "./ModelsDropdown";
66
export { default as FileMentionMenu } from "./FileMentionMenu";
7+
export { default as DropdownMenu } from "./DropdownMenu";

0 commit comments

Comments
 (0)