Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Anthropic 설정을 언어 모델 기능 설정에 추가 #1

Merged
merged 15 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
Binary file modified bun.lockb
Binary file not shown.
180 changes: 90 additions & 90 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,92 +1,92 @@
{
"name": "max-chatbot",
"version": "0.8.2",
"description": "Generate and brainstorm ideas while creating your notes using Large Language Models (LLMs) from Ollama, LM Studio, Anthropic, OpenAI, Mistral AI, and more for Obsidian.",
"main": "main.js",
"scripts": {
"dev": "VITE_CJS_IGNORE_WARNING=true vite build --mode development --watch",
"build": "VITE_CJS_IGNORE_WARNING=true vite build --mode production",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
"prepare": "husky",
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"lint:quiet": "eslint ./src --quiet",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"obsidian",
"chatbot",
"chatgpt",
"ollama"
],
"author": "anpigon",
"license": "MIT",
"devDependencies": {
"@commitlint/config-conventional": "^19.2.2",
"@eslint/js": "^9.5.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-typescript": "^11.1.6",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/lodash": "^4.17.5",
"@types/node": "^20.14.7",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.10.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"builtin-modules": "^3.3.0",
"bun-types": "latest",
"esbuild": "0.21.5",
"eslint": "^9.5.0",
"husky": "^9.0.11",
"obsidian": "latest",
"postcss": "^8.4.38",
"postcss-prefix-selector": "^1.16.1",
"prettier": "^3.3.2",
"prettier-plugin-sort-imports": "^1.8.5",
"prettier-plugin-tailwindcss": "^0.6.5",
"rollup-plugin-copy": "^3.5.0",
"tailwindcss": "^3.4.4",
"tslib": "^2.6.3",
"typescript": "^5.4.5",
"typescript-eslint": "^7.13.1",
"vite": "^5.3.1"
},
"dependencies": {
"@commitlint/cli": "^19.3.0",
"@heroicons/react": "^2.1.4",
"@langchain/community": "^0.2.12",
"@langchain/google-genai": "^0.0.20",
"@langchain/groq": "^0.0.12",
"@langchain/openai": "^0.1.3",
"@msgpack/msgpack": "^3.0.0-beta2",
"@orama/orama": "^2.0.20",
"@popperjs/core": "^2.11.8",
"clsx": "^2.1.1",
"i18next": "^23.11.5",
"langchain": "^0.2.5",
"lodash": "^4.17.21",
"merge-refs": "^1.3.0",
"monkey-around": "^2.3.0",
"openai": "^4.52.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2",
"tailwind-merge": "^2.3.0",
"usehooks-ts": "^3.1.0"
},
"overrides": {
"@langchain/core": "0.2.0"
},
"resolutions": {
"@langchain/core": "0.2.0"
},
"pnpm": {
"overrides": {
"@langchain/core": "0.2.0"
}
}
"name": "max-chatbot",
"version": "0.8.2",
"description": "Generate and brainstorm ideas while creating your notes using Large Language Models (LLMs) from Ollama, LM Studio, Anthropic, OpenAI, Mistral AI, and more for Obsidian.",
"main": "main.js",
"scripts": {
"dev": "VITE_CJS_IGNORE_WARNING=true vite build --mode development --watch",
"build": "VITE_CJS_IGNORE_WARNING=true vite build --mode production",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
"prepare": "husky",
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"lint:quiet": "eslint ./src --quiet",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"obsidian",
"chatbot",
"chatgpt",
"ollama"
],
"author": "anpigon",
"license": "MIT",
"devDependencies": {
"@commitlint/config-conventional": "^19.2.2",
"@eslint/js": "^9.6.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-typescript": "^11.1.6",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/lodash": "^4.17.6",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"builtin-modules": "^3.3.0",
"bun-types": "latest",
"esbuild": "0.23.0",
"eslint": "^9.6.0",
"husky": "^9.0.11",
"obsidian": "latest",
"postcss": "^8.4.39",
"postcss-prefix-selector": "^1.16.1",
"prettier": "^3.3.2",
"prettier-plugin-sort-imports": "^1.8.5",
"prettier-plugin-tailwindcss": "^0.6.5",
"rollup-plugin-copy": "^3.5.0",
"tailwindcss": "^3.4.4",
"tslib": "^2.6.3",
"typescript": "^5.4.5",
"typescript-eslint": "^7.15.0",
"vite": "^5.3.3"
},
"dependencies": {
"@commitlint/cli": "^19.3.0",
"@heroicons/react": "^2.1.4",
"@langchain/anthropic": "^0.2.3",
"@langchain/community": "^0.2.17",
"@langchain/google-genai": "^0.0.21",
"@langchain/groq": "^0.0.13",
"@langchain/openai": "^0.2.1",
"@msgpack/msgpack": "3.0.0-beta2",
"@orama/orama": "^2.0.21",
"@popperjs/core": "^2.11.8",
"clsx": "^2.1.1",
"i18next": "^23.11.5",
"langchain": "^0.2.8",
"lodash": "^4.17.21",
"merge-refs": "^1.3.0",
"monkey-around": "^2.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2",
"tailwind-merge": "^2.3.0",
"usehooks-ts": "^3.1.0"
},
"overrides": {
"@langchain/core": "0.2.14"
},
"resolutions": {
"@langchain/core": "0.2.14"
},
"pnpm": {
"overrides": {
"@langchain/core": "0.2.14"
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에 대한 간단한 코드 리뷰는 다음과 같습니다:

개선 사항:

  1. 서식 변경:
    • 탭 대신 스페이스로 들여쓰기를 변경했습니다. 프로젝트 내부 규칙으로 통일이 잘 되었는지 확인해야 합니다.
  2. 의존성 업데이트:
    • 중요한 라이브러리들의 버전이 업데이트되었습니다. 이는 보안 및 성능 향상에 도움이 될 수 있습니다.

잠재적 버그 위험:

  1. 의존성 충돌:

    • 여러 패키지의 버전이 변경되어 상호 호환성 문제가 발생할 수 있습니다. 특히 typescript-eslint와 관련된 패키지들이 모두 버전이 변경되었기 때문에 주의가 필요합니다.
  2. 중복 의존성:

    • langchain 관련 패키지들이 빈번히 업데이트되고 있으며, @langchain/core에 대해 여러 번의 overrides가 설정되어 있습니다. 같은 기능과 관련된 중복된 설정이 문제를 발생시킬 수 있습니다.
  3. 카멜 케이스 유지:

    • 한 곳에서 devDependencies 항목의 "esbuild" 버전이 0.21.5에서 0.23.0으로 업데이트되었습니다. 서로 다른 부분에서 영향을 미칠 수 있으므로 주의 깊게 테스트해야 합니다.

추가적인 제안:

  • 테스트 추가:

    • "test": "echo \"Error: no test specified\" && exit 1" 부분을 실제 테스트 실행 코드로 대체하는 것이 좋습니다. 이는 자동화된 빌드 파이프라인에서 코드 품질을 개선할 수 있습니다.
  • 변경된 항목 설명 문서화:

    • 변경된 패키지의 릴리스 노트를 참고하여 주요 변경 사항을 README나 CHANGELOG에 기록해두면 추후 문제가 발생했을 때 원인을 파악하기 쉽습니다.

2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const UPSTAGE_MODELS = [
'solar-1-mini-chat', // GPT-3.5보다 뛰어난 성능을 제공하는 소형 LLM으로, 영어와 한국어를 모두 지원하는 강력한 다국어 기능을 갖추고 있어 더 작은 패키지로 높은 효율성을 제공합니다. Context Length: 32768
'solar-1-mini-chat-ja', // 영어와 한국어의 높은 효율성과 성능을 유지하면서 일본어에 특화된 solar-mini-chat의 기능을 확장한 소형 LLM입니다. Context Length: 32768
];
export const ANTHROPIC_MODELS = ['claude-instant-1.2', 'claude-2.0', 'claude-2.1', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229'];
export const ANTHROPIC_MODELS = ['claude-3-5-sonnet-20240620', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'];
export const OPEN_AI_MODELS = ['gpt-3.5-turbo', 'gpt-4o', 'gpt-4'];

export const enum LLM_PROVIDERS {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에 제공된 코드 패치에 대한 간단한 코드 리뷰입니다:

변경 사항 개요

  • ANTHROPIC_MODELS 배열의 내용을 변화시켰습니다. 일부 모델을 새로운 이름과 데이터로 교체했습니다.

개선 제안 및 버그 위험 평가

  1. 정렬 문제

    • 새로운 모델 리스트에서 날짜 순서가 혼동될 수 있습니다. 이를 해결하기 위해 날짜 순으로 정렬하는 것이 좋습니다.

      export const ANTHROPIC_MODELS = [
          'claude-3-opus-20240229', 
          'claude-3-sonnet-20240229', 
          'claude-3-haiku-20240307',
          'claude-3-5-sonnet-20240620'
      ];
  2. 변경된 모델이 예상대로 동작할 검증 필요

    • 새로 추가된 모델에 대해 충분한 테스트가 있는지 확인해야 합니다. 특히, 새 모델이 기존 구조와 호환되는지를 검증하는 것이 중요합니다.
  3. 중복 제거

    • 업데이트로 인해 기존에 사용되지 않는 모델 정보가 남아있다면, 이를 정리하여 관리의 복잡도를 줄이는 것을 고려해보세요.
  4. 주석 추가

    • 각 모델에 대한 간단한 설명 주석을 추가하면, 나중에 코드를 관리하거나 새로운 팀원이 합류했을 때 더 이해하기 쉬울 것입니다.

제안된 패치는 주요 기능에 큰 영향을 미치지 않지만, 위의 개선 사항들을 반영하면 더 관리하기 좋은 코드가 될 수 있습니다.

Expand Down
15 changes: 12 additions & 3 deletions src/features/chatbot/components/chatbot-header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {ReactNode, type ChangeEventHandler, type FC, type PropsWithChildren} from 'react';

import {ANTHROPIC_MODELS, LLM_PROVIDERS} from '@/constants';
import {ProviderModels} from '@/hooks/useEnabledModels';
import {Dropdown} from '@/components/form/dropdown';
import {LLM_PROVIDERS} from '@/constants';

interface ChatbotHeaderProps extends PropsWithChildren {
botName: string;
Expand All @@ -18,6 +18,14 @@ interface ChatbotHeaderProps extends PropsWithChildren {
rightComponent?: ReactNode;
}

function getModels(provider: LLM_PROVIDERS, models: string[]) {
let currentModels = models.filter(model => model !== 'embed');
if (provider === LLM_PROVIDERS.ANTHROPIC && !currentModels?.length) {
currentModels = ANTHROPIC_MODELS;
}
return currentModels;
}

export const ChatbotHeader: FC<ChatbotHeaderProps> = ({botName, providers, disabled, currentModel, onChangeModel, leftComponent, rightComponent, children}) => {
const handleChangeModel: ChangeEventHandler<HTMLSelectElement> = e => {
const value = e.target.value;
Expand All @@ -37,11 +45,12 @@ export const ChatbotHeader: FC<ChatbotHeaderProps> = ({botName, providers, disab
disabled={disabled}
>
{providers
?.filter(({provider, models}) => models.length > 0)
?.filter(({models}) => models.length > 0)
.map(({provider, models}) => {
const currentModels = getModels(provider, models);
return (
<optgroup key={provider} label={provider}>
{models.map(model => {
{currentModels.map(model => {
const value = `${provider}/${model}`;
return (
<option key={value} value={value}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {useCallback, useState, useEffect} from 'react';
import {useTranslation} from 'react-i18next';
import {twMerge} from 'tailwind-merge';
import clsx from 'clsx';

import {SettingItem} from '@/components/settings/setting-item';
import {usePlugin, useSettings} from '@/hooks/useApp';
import {useSettingDispatch} from '../../context';
import {Toggle} from '@/components/form/toggle';

export const AnthropicSetting = () => {
const {t} = useTranslation('settings');

const plugin = usePlugin();
const settings = useSettings();
const {refreshChatbotView} = useSettingDispatch();
const providerSettings = settings.providers.ANTHROPIC;

const [enable, setEnable] = useState(providerSettings?.enable ?? false);
const [apiKey, setApiKey] = useState(providerSettings?.apiKey ?? '');
const [allowStream, setAllowStream] = useState(providerSettings?.allowStream ?? false);

useEffect(() => {
const save = async () => {
await plugin.saveSettings();
refreshChatbotView();
};
void save();
}, [enable, apiKey, allowStream, plugin]);

return (
<>
<SettingItem heading name={t('Anthropic')} className="bg-secondary rounded-lg !px-3 mt-1">
<Toggle
checked={enable}
onChange={event => {
const value = event.target.checked;
setEnable(value);
providerSettings.enable = value;
}}
/>
</SettingItem>

<div className={twMerge(clsx('p-3 hidden', {block: enable}))}>
<SettingItem name={t('Provider API Key', {name: 'Anthropic API'})} description={t('Insert your provider API Key', {name: 'Anthropic API'})}>
<input
type="password"
spellCheck={false}
placeholder="sk-ant-api03-...-57SQAA"
defaultValue={apiKey}
onChange={event => {
const value = event.target.value?.trim();
setApiKey(value);
providerSettings.apiKey = value;
}}
/>
</SettingItem>

<SettingItem name={t('Allow Stream')} description={t('Allow the model to stream responses.', {name: 'Anthropic'})}>
<Toggle
name="allowStream"
checked={allowStream}
onChange={event => {
const value = event.target.checked;
setAllowStream(value);
providerSettings.allowStream = value;
}}
/>
</SettingItem>
</div>
</>
);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에 대한 간단한 코드 리뷰입니다:

개선 사항:

  1. 성능 최적화:

    • saveSettings 함수가 각 설정 항목의 변화마다 호출됩니다. 여러 설정이 동시에 변경될 때, async/await로 인해 비효율적일 수 있습니다. 단일 useEffect 후크를 사용하여 모든 상태 업데이트 후 한번만 저장하는 것이 좋습니다.
    • 예시:
    useEffect(() => {
        const save = async () => {
            await plugin.saveSettings();
            refreshChatbotView();
        };
        save();
    }, [enable, apiKey, allowStream, plugin]);
  2. 불필요한 리렌더링 방지:

    • onChange 핸들러 내부에서 상태가 같은 경우 setState를 호출하지 않도록 합니다. 불필요한 리렌더링을 줄일 수 있습니다.
  3. 입력 필드 보안:

    • API 키 입력 필드의 경우 비밀번호 타입으로 지정했으므로, 복사/붙여넣기 금지와 같은 추가적인 보안 조치를 고려해볼 수 있습니다.

버그 위험:

  1. 초기 값 문제:

    • providerSettings.allowStream 초기 값을 직접 넣고 있으나 해당 키가 존재하지 않을 가능성을 고려해야 합니다. 기본 값을 명시적으로 지정하는 것이 좋습니다.
    • 예:
    const [allowStream, setAllowStream] = useState(providerSettings?.allowStream ?? false);
  2. 비동기 함수로 인한 경합 상태:

    • 설정 변경 후 saveSettings가 비동기로 처리됨에 따라 동시성 문제가 발생할 가능성이 있습니다. 이를 방지하기 위해서는 useState 혹은 다른 상태 관리 라이브러리를 활용하여 동시성 문제를 해결해야 합니다.

전체 코드 스타일:

  • 전반적으로 읽기 쉽고 모듈화된 구조로 잘 작성되었습니다.
  • Tailwind CSS 및 기타 유틸리티 클래스의 조합도 적절합니다.

제안된 변경 사항을 통해 성능을 향상시키고, 잠재적인 버그를 줄이는 데 도움이 되길 바랍니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에 대한 간단한 코드 리뷰는 다음과 같습니다:

버그 리스크 및 개선 사항

  1. 상태 업데이트 비동기 처리:

    • providerSettings 객체의 속성을 직접 수정하는 것은 React 상태 관리 방식과 맞지 않습니다. React가 변경 사항을 인지하지 못할 수 있어 UI 갱신이 되지 않을 수 있습니다. 대신, setEnable, setApiKey, setAllowStream을 호출하여 상태를 업데이트하고, 상태 업데이트 후에 plugin.saveSettings()를 호출해야 합니다.
  2. useEffect 의존성 배열:

    • 현재 useEffect에서는 변경된 값을 저장하지만, providerSettings 객체의 직접 수정은 포함되어 있지 않습니다. 이로 인해 상태 동기화 문제를 초래할 수 있습니다. 이 또한 위에서 언급한 방법으로 변경해야 합니다.
  3. 기본값 설정:

    • defaultValue 방식을 사용하기보다는 value 속성을 사용하는 것이 좋습니다. 이는 입력 필드가 상태와 동기화되도록 보장합니다.
  4. 명확한 이벤트 핸들링:

    • onChange 핸들러를 별도로 정의하면 가독성이 향상되고 재사용성도 높일 수 있습니다. 예를 들어, toggle을 위한 공통 함수로 만들 수 있습니다.
  5. 불필요한 void 사용:

    • void save();는 불필요하게 보입니다. 그냥 save(); 를 호출해도 동일한 효과를 줍니다.

전체적인 요약:

상태 관리를 보다 효율적으로 처리하기 위해 React의 상태 업데이트 규칙을 준수하고, UI 컴포넌트의 동기화를 보장하는 방향으로 코드를 리팩토링하는 것이 필요합니다. 이러한 변경을 통해 코드의 안정성과 유지보수성이 향상될 것입니다.

2 changes: 2 additions & 0 deletions src/features/setting/components/language-models/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default function LanguageModels() {
<GoogleSetting />
<GroqSetting />
<UpstageSetting />
{/* TODO: Anthropic API의 CORS 에러로 인해 현재는 주석 처리 됨. 이후 CORS 문제 해결 시 주석 해제 요망 */}
{/* <AnthropicSetting /> */}
</>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드와 관련하여 몇 가지 의견을 드립니다:

버그 위험성

  1. 주석 처리된 코드: <AnthropicSetting />이 주석 처리되어 있지만 코드에 남아 있습니다. 이는 장기적으로 혼란을 줄 수 있으므로, 현재 필요 없다면 완전히 제거하거나 필요한 경우가 있다면 Todo 주석을 남겨두는 것이 좋습니다. 예:
    // TODO: CORS 문제 해결 후 AnthropicSetting 사용

개선 제안

  1. 중복된 import 정리: 'useTranslation'을 사용하는 부분이 보이지 않습니다. 불필요한 import를 정리하면 코드 가독성을 높일 수 있습니다.
  2. 추가적인 설명 주석: CORS 문제로 인해 현재 주석 처리했다는 부분은 충분히 설명되어 있으나, 다른 팀원이 이해하기 쉽도록 더 자세히 설명하는 것도 좋습니다.

다음을 고려해 볼 수 있습니다:

// CORS 에러로 인해 현재는 주석 처리 됨. 이후 CORS 문제 해결 시 주석 해제 요망
{/* <AnthropicSetting /> */}

전반적으로 코드의 구조는 깔끔하며, 큰 문제점은 없어 보입니다. 향후 유지보수 및 협업을 위해 위 언급한 사항들을 고려해 보세요.

3 changes: 3 additions & 0 deletions src/libs/ai/createChatModelInstance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ChatOllama} from '@langchain/community/chat_models/ollama';
import {ChatGoogleGenerativeAI} from '@langchain/google-genai';
import {ChatAnthropic} from '@langchain/anthropic';
import {ChatOpenAI} from '@langchain/openai';
import {ChatGroq} from '@langchain/groq';

Expand All @@ -20,6 +21,8 @@ export default function createChatModelInstance(provider: LLM_PROVIDERS, model:
return new ChatGoogleGenerativeAI(options);
case LLM_PROVIDERS.GROQ:
return new ChatGroq(options);
case LLM_PROVIDERS.ANTHROPIC:
return new ChatAnthropic(options);
default:
return new ChatOpenAI({
...options,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 코드 패치는 새로운 ChatAnthropic 모델을 추가하려는 것으로 보입니다. 아래에 몇 가지 개선 사항 및 버그 위험을 제시합니다:

  1. 주석 추가:

    • 새롭게 추가된 코드는 주석이 없습니다. 각 케이스가 어떤 역할을 하는지 간단히 설명하는 주석을 추가하면 코드 가독성이 향상됩니다.
  2. 종속성 관리:

    • import {ChatAnthropic} from '@langchain/anthropic'; 로 새로운 모듈을 가져오고 있습니다. package.json 파일에 해당 종속성이 추가되었는지 확인해야 합니다. 그렇지 않으면 배포 시 문제가 발생할 수 있습니다.
  3. 테스트 케이스 추가:

    • 새로운 프로바이더가 추가된 만큼, 이에 대한 테스트 케이스도 작성되어야 합니다. 이를 통해 코드가 제대로 동작하는지 확인할 수 있으며, 추후 문제 발생 시 디버깅이 용이합니다.
  4. 기본 조건 고려:

    • 다른 case 문처럼 .groq.google에서도 기본 객체를 반환하고 있으므로, ANTHROPIC의 경우도 동일하게 처리하여 일관성을 유지하는 방안을 고려할 수 있습니다.
  5. 선택적 체이닝 검사 (TS 혹은 JS 옵션 기능을 사용하는 경우):

    • options 객체가 반드시 필요한 모든 속성을 가지고 있는지 확인해야 합니다. 필요한 경우 옵션 유효성 검사를 추가하십시오.

코드 패치 자체는 간결하고 정확해 보이지만 위의 개선 사항을 반영하면 더 안정적인 코드가 될 것입니다.

Expand Down