@@ -22,18 +22,21 @@ import { Button } from '@/shared/ui/button'
2222import { Row , Section , Segmented } from './SettingsPrimitives'
2323import { AiSettingsSection } from './AiSettingsSection'
2424
25- const VERSION = '0.2.4 '
25+ const VERSION = '0.2.5 '
2626
2727interface Props {
2828 open : boolean
2929 onClose : ( ) => void
30+ /** 打开时自动滚动到 AI 服务区域并聚焦 API Key 输入框 */
31+ focusAi ?: boolean
3032}
3133
32- export function SettingsModal ( { open, onClose } : Props ) {
34+ export function SettingsModal ( { open, onClose, focusAi } : Props ) {
3335 const mode = useThemeStore ( ( s ) => s . mode )
3436 const setMode = useThemeStore ( ( s ) => s . setMode )
3537 const pushToast = useToastStore ( ( s ) => s . pushToast )
3638 const confirm = useConfirm ( )
39+ const aiSectionRef = useRef < HTMLDivElement > ( null )
3740
3841 const [ improve , setImprove ] = useState ( true )
3942 const fileRef = useRef < HTMLInputElement > ( null )
@@ -55,6 +58,24 @@ export function SettingsModal({ open, onClose }: Props) {
5558 return ( ) => document . removeEventListener ( 'keydown' , esc )
5659 } , [ open , onClose ] )
5760
61+ // focusAi:打开时自动滚动到 AI 服务区域并聚焦 API Key 输入框
62+ useEffect ( ( ) => {
63+ if ( ! focusAi || ! open || ! aiSectionRef . current ) return
64+ const el = aiSectionRef . current
65+ const scrollTimer = setTimeout ( ( ) => {
66+ el . scrollIntoView ( { behavior : 'smooth' , block : 'center' } )
67+ } , 100 )
68+ // 等滚动完成后聚焦 API Key 输入框
69+ const focusTimer = setTimeout ( ( ) => {
70+ const input = el . querySelector ( 'input[type="password"]' )
71+ if ( input instanceof HTMLElement ) input . focus ( )
72+ } , 400 )
73+ return ( ) => {
74+ clearTimeout ( scrollTimer )
75+ clearTimeout ( focusTimer )
76+ }
77+ } , [ focusAi , open ] )
78+
5879 const handleClearAll = async ( ) => {
5980 const ok = await confirm ( {
6081 title : '清空所有数据?' ,
@@ -68,9 +89,9 @@ export function SettingsModal({ open, onClose }: Props) {
6889 await clearAllData ( )
6990 await useBookmarkStore . getState ( ) . hydrate ( )
7091 await usePendingStore . getState ( ) . hydratePending ( )
71- pushToast ( 'success' , '已清空所有数据' )
92+ pushToast ( 'success' , '已清空所有数据' , 'clear-all' )
7293 } catch ( e ) {
73- pushToast ( 'error' , `清空失败:${ ( e as Error ) . message } ` )
94+ pushToast ( 'error' , `清空失败:${ ( e as Error ) . message } ` , 'clear-fail' )
7495 }
7596 }
7697
@@ -84,13 +105,13 @@ export function SettingsModal({ open, onClose }: Props) {
84105 try {
85106 const items = await getPendingIndexBookmarks ( )
86107 if ( items . length === 0 ) {
87- pushToast ( 'info' , '所有书签均已索引' )
108+ pushToast ( 'info' , '所有书签均已索引' , 'reindex-none' )
88109 return
89110 }
90111 await chrome . runtime . sendMessage ( { type : MSG . retryIndex } )
91- pushToast ( 'success' , `已发起 ${ items . length } 条重新索引` )
112+ pushToast ( 'success' , `已发起 ${ items . length } 条重新索引` , 'reindex-start' )
92113 } catch ( e ) {
93- pushToast ( 'error' , `重新索引失败:${ ( e as Error ) . message } ` )
114+ pushToast ( 'error' , `重新索引失败:${ ( e as Error ) . message } ` , 'reindex-fail' )
94115 } finally {
95116 setReindexBusy ( false )
96117 }
@@ -108,14 +129,15 @@ export function SettingsModal({ open, onClose }: Props) {
108129 pushToast (
109130 'success' ,
110131 `导入:新增 ${ result . written } 条,重新归类 ${ result . reassigned } ` ,
132+ 'import-html'
111133 )
112134 } else if ( result . skipped > 0 ) {
113- pushToast ( 'info' , `全部跳过:${ result . skipped } 条已存在` )
135+ pushToast ( 'info' , `全部跳过:${ result . skipped } 条已存在` , 'import-html' )
114136 } else {
115- pushToast ( 'error' , '未解析到可导入条目' )
137+ pushToast ( 'error' , '未解析到可导入条目' , 'import-no-items' )
116138 }
117139 } catch ( e ) {
118- pushToast ( 'error' , `导入失败:${ ( e as Error ) . message } ` )
140+ pushToast ( 'error' , `导入失败:${ ( e as Error ) . message } ` , 'import-fail' )
119141 } finally {
120142 setImportBusy ( false )
121143 }
@@ -126,12 +148,12 @@ export function SettingsModal({ open, onClose }: Props) {
126148 try {
127149 const count = await exportBookmarksHtml ( )
128150 if ( count > 0 ) {
129- pushToast ( 'success' , `已导出 ${ count } 条书签` )
151+ pushToast ( 'success' , `已导出 ${ count } 条书签` , 'export-ok' )
130152 } else {
131- pushToast ( 'info' , '暂无书签可导出' )
153+ pushToast ( 'info' , '暂无书签可导出' , 'export-none' )
132154 }
133155 } catch ( e ) {
134- pushToast ( 'error' , `导出失败:${ ( e as Error ) . message } ` )
156+ pushToast ( 'error' , `导出失败:${ ( e as Error ) . message } ` , 'export-fail' )
135157 } finally {
136158 setExportBusy ( false )
137159 }
@@ -223,7 +245,9 @@ export function SettingsModal({ open, onClose }: Props) {
223245 </ Section >
224246
225247 { /* ── AI 服务 ── */ }
226- < AiSettingsSection />
248+ < div ref = { aiSectionRef } >
249+ < AiSettingsSection />
250+ </ div >
227251
228252 { /* ── 数据 ── */ }
229253 < Section title = "数据管理" >
0 commit comments