mirror of
https://github.com/TommyFang2077/dsh-desktop.git
synced 2026-08-17 09:06:36 +08:00
Ship screenshots and third-party notices for the desktop shell, and package Windows, macOS, deb, rpm, and Flatpak from GitHub Releases. Co-authored-by: Cursor <cursoragent@cursor.com>
182 lines
5.1 KiB
JavaScript
182 lines
5.1 KiB
JavaScript
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||
import { homedir } from 'node:os'
|
||
import { dirname, join } from 'node:path'
|
||
|
||
export const name = 'dsh-desktop-vision'
|
||
export const inject = []
|
||
|
||
const PROVIDERS = [
|
||
{
|
||
id: 'openai',
|
||
label: 'OpenAI 兼容',
|
||
baseUrl: 'https://api.openai.com/v1',
|
||
apiUrl: 'https://platform.openai.com/api-keys',
|
||
example: 'gpt-4o',
|
||
},
|
||
{
|
||
id: 'gemini-api',
|
||
label: 'Gemini API',
|
||
baseUrl: 'https://generativelanguage.googleapis.com',
|
||
apiUrl: 'https://aistudio.google.com/apikey',
|
||
example: 'gemini-3.6-flash',
|
||
},
|
||
{
|
||
id: 'anthropic',
|
||
label: 'Anthropic API',
|
||
baseUrl: 'https://api.anthropic.com',
|
||
apiUrl: 'https://console.anthropic.com/settings/keys',
|
||
example: 'claude-haiku-4-5-20251001',
|
||
},
|
||
{
|
||
id: 'antigravity-cli',
|
||
label: 'Antigravity CLI(免费)',
|
||
baseUrl: '',
|
||
apiUrl: 'https://antigravity.google/',
|
||
example: 'gemini-3.6-flash-low',
|
||
},
|
||
{
|
||
id: 'claude-cli',
|
||
label: 'Claude Code 登录',
|
||
baseUrl: '',
|
||
apiUrl: 'https://code.claude.com',
|
||
example: 'haiku',
|
||
},
|
||
]
|
||
const PROVIDER_IDS = new Set(PROVIDERS.map((item) => item.id))
|
||
|
||
function providerOf(id) {
|
||
return PROVIDERS.find((entry) => entry.id === id)
|
||
}
|
||
|
||
function officialBaseUrl(provider) {
|
||
const item = providerOf(provider)
|
||
return item ? item.baseUrl : ''
|
||
}
|
||
|
||
function officialApiUrl(provider) {
|
||
const item = providerOf(provider)
|
||
return item ? item.apiUrl : ''
|
||
}
|
||
|
||
function officialExample(provider) {
|
||
const item = providerOf(provider)
|
||
return item ? item.example : ''
|
||
}
|
||
const FORM_FIELDS = ['apiKey', 'baseUrl', 'model']
|
||
|
||
function configPath() {
|
||
const home = process.env.MODLENS_HOME || join(homedir(), '.modlens')
|
||
return join(home, 'config.json')
|
||
}
|
||
|
||
function loadConfig() {
|
||
try {
|
||
const data = JSON.parse(readFileSync(configPath(), 'utf8'))
|
||
return data && typeof data === 'object' ? data : {}
|
||
} catch {
|
||
return {}
|
||
}
|
||
}
|
||
|
||
function formOf(cfg, provider) {
|
||
const id = PROVIDER_IDS.has(provider) ? provider : 'openai'
|
||
const entry =
|
||
cfg.providers && typeof cfg.providers === 'object' && typeof cfg.providers[id] === 'object'
|
||
? cfg.providers[id]
|
||
: {}
|
||
return {
|
||
provider: id,
|
||
apiKey: String(entry.apiKey || ''),
|
||
baseUrl: String(entry.baseUrl || officialBaseUrl(id)),
|
||
officialBaseUrl: officialBaseUrl(id),
|
||
apiUrl: officialApiUrl(id),
|
||
example: officialExample(id),
|
||
model: String(entry.model || ''),
|
||
}
|
||
}
|
||
|
||
function saveForm(values) {
|
||
const provider = String(values.provider || 'openai').trim()
|
||
if (!PROVIDER_IDS.has(provider)) {
|
||
const error = new Error(`unknown provider: ${provider}`)
|
||
error.status = 400
|
||
throw error
|
||
}
|
||
const cfg = loadConfig()
|
||
cfg.provider = provider
|
||
if (!cfg.providers || typeof cfg.providers !== 'object') cfg.providers = {}
|
||
if (!cfg.providers[provider] || typeof cfg.providers[provider] !== 'object') {
|
||
cfg.providers[provider] = {}
|
||
}
|
||
const entry = cfg.providers[provider]
|
||
for (const field of FORM_FIELDS) {
|
||
const raw = String(values[field] || '').trim()
|
||
if (raw) entry[field] = raw
|
||
else delete entry[field]
|
||
}
|
||
const path = configPath()
|
||
mkdirSync(dirname(path), { recursive: true })
|
||
writeFileSync(path, `${JSON.stringify(cfg, null, 2)}\n`, { encoding: 'utf8' })
|
||
try {
|
||
chmodSync(path, 0o600)
|
||
} catch {
|
||
// best-effort; some filesystems ignore mode
|
||
}
|
||
return formOf(cfg, provider)
|
||
}
|
||
|
||
async function readJsonBody(req) {
|
||
const chunks = []
|
||
for await (const chunk of req) chunks.push(chunk)
|
||
const raw = Buffer.concat(chunks).toString('utf8').trim()
|
||
if (!raw) return {}
|
||
return JSON.parse(raw)
|
||
}
|
||
|
||
function json(res, status, body) {
|
||
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
||
res.end(JSON.stringify(body))
|
||
}
|
||
|
||
export function apply(ctx) {
|
||
if (typeof ctx.inject !== 'function') return
|
||
ctx.inject(['webServer'], (scope) => {
|
||
try {
|
||
scope.webServer.register({
|
||
name: 'dsh-desktop-modlens',
|
||
kind: 'exact',
|
||
path: '/dsh-desktop/modlens',
|
||
handler: async (req, res) => {
|
||
try {
|
||
if (req.method === 'GET') {
|
||
const cfg = loadConfig()
|
||
const provider = new URL(req.url, 'http://localhost').searchParams.get('provider')
|
||
json(res, 200, {
|
||
...formOf(cfg, provider || cfg.provider || 'openai'),
|
||
options: PROVIDERS.map((item) => ({
|
||
id: item.id,
|
||
label: item.label,
|
||
officialBaseUrl: item.baseUrl,
|
||
apiUrl: item.apiUrl,
|
||
example: item.example,
|
||
})),
|
||
})
|
||
return
|
||
}
|
||
if (req.method === 'PUT' || req.method === 'POST') {
|
||
const body = await readJsonBody(req)
|
||
json(res, 200, { ...saveForm(body), saved: true })
|
||
return
|
||
}
|
||
res.writeHead(405).end()
|
||
} catch (error) {
|
||
json(res, error.status || 500, { error: String(error?.message || error) })
|
||
}
|
||
},
|
||
})
|
||
} catch (error) {
|
||
console.error(`[dsh-desktop-vision] config route skipped: ${error}`)
|
||
}
|
||
})
|
||
}
|