From 66a403488f3a7bc32a02bc9933c396dc4c4e031d Mon Sep 17 00:00:00 2001 From: haoyuren <13851610112@163.com> Date: Sun, 15 Mar 2026 20:52:13 -0500 Subject: Add API key management UI and wire S2 key to MCP - Settings modal on project list page for OpenAI, Anthropic, OpenRouter, Gemini, Semantic Scholar keys - Keys stored in userData/api-keys.json, masked by default with show/hide toggle - S2 API key passed to MCP server via .lattex-mcp.json to avoid rate limits Co-Authored-By: Claude Opus 4.6 --- src/renderer/src/components/ProjectList.tsx | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'src/renderer') diff --git a/src/renderer/src/components/ProjectList.tsx b/src/renderer/src/components/ProjectList.tsx index 2fe23de..7a9fd7f 100644 --- a/src/renderer/src/components/ProjectList.tsx +++ b/src/renderer/src/components/ProjectList.tsx @@ -32,6 +32,9 @@ export default function ProjectList({ onOpenProject }: Props) { const [sortOrder, setSortOrder] = useState('desc') const [showNewProject, setShowNewProject] = useState(false) const [newProjectName, setNewProjectName] = useState('Untitled Project') + const [showApiKeys, setShowApiKeys] = useState(false) + const [apiKeys, setApiKeys] = useState>({}) + const [apiKeysVisible, setApiKeysVisible] = useState>({}) const { setStatusMessage } = useAppStore() const loadProjects = useCallback(async () => { @@ -123,6 +126,32 @@ export default function ProjectList({ onOpenProject }: Props) { useAppStore.getState().setScreen('login') } + const openApiKeys = async () => { + const keys = await window.api.getApiKeys() + setApiKeys(keys) + setApiKeysVisible({}) + setShowApiKeys(true) + } + + const saveApiKeys = async () => { + // Strip empty keys before saving + const cleaned: Record = {} + for (const [k, v] of Object.entries(apiKeys)) { + if (v.trim()) cleaned[k] = v.trim() + } + await window.api.setApiKeys(cleaned) + setShowApiKeys(false) + setStatusMessage('API keys saved') + } + + const API_KEY_FIELDS = [ + { id: 'openai', label: 'OpenAI', placeholder: 'sk-...' }, + { id: 'anthropic', label: 'Anthropic (Claude)', placeholder: 'sk-ant-...' }, + { id: 'openrouter', label: 'OpenRouter', placeholder: 'sk-or-...' }, + { id: 'gemini', label: 'Google Gemini', placeholder: 'AIza...' }, + { id: 'semanticScholar', label: 'Semantic Scholar', placeholder: 'API key (optional, avoids rate limits)' } + ] + const toggleSort = (key: SortKey) => { if (sortBy === key) { setSortOrder((o) => (o === 'asc' ? 'desc' : 'asc')) @@ -206,6 +235,9 @@ export default function ProjectList({ onOpenProject }: Props) {

LatteX

+ @@ -312,6 +344,46 @@ export default function ProjectList({ onOpenProject }: Props) {
)} + {showApiKeys && ( +
setShowApiKeys(false)}> +
e.stopPropagation()} style={{ minWidth: 460 }}> +

API Keys

+

+ Keys are stored locally on this device. +

+ {API_KEY_FIELDS.map((field) => ( +
+ +
+ setApiKeys({ ...apiKeys, [field.id]: e.target.value })} + placeholder={field.placeholder} + spellCheck={false} + autoComplete="off" + /> + +
+
+ ))} +
+ + +
+
+
+ )} ) } -- cgit v1.2.3