feat: add desktop app release packaging

This commit is contained in:
lutc5
2026-04-29 18:45:25 +08:00
parent 74bbd8e6d2
commit 92c8735bfc
73 changed files with 8934 additions and 757 deletions

View File

@@ -0,0 +1,218 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { GetConfig, UpdateConfig } from '../../wailsjs/go/main/App.js'
const emit = defineEmits(['log', 'status-refresh'])
const config = ref({})
const saving = ref(false)
const openSelect = ref('')
const selectOptions = {
Transport: [
{ value: 'auto', label: '自动' },
{ value: 'pipe', label: '命名管道' },
{ value: 'websocket', label: 'WebSocket' },
],
Mode: [
{ value: 'agent', label: 'Agent' },
{ value: 'chat', label: 'Chat' },
],
ShellType: [
{ value: 'zsh', label: 'zsh' },
{ value: 'bash', label: 'bash' },
{ value: 'powershell', label: 'PowerShell' },
{ value: 'cmd', label: 'cmd' },
],
SessionMode: [
{ value: 'auto', label: '自动' },
{ value: 'reuse', label: '复用' },
{ value: 'fresh', label: '每次新建' },
],
}
const selectLabel = computed(() => (field) => {
const option = selectOptions[field]?.find((item) => item.value === config.value[field])
return option?.label || '请选择'
})
function toggleSelect(field) {
openSelect.value = openSelect.value === field ? '' : field
}
function chooseOption(field, value) {
config.value[field] = value
openSelect.value = ''
}
onMounted(async () => {
try {
config.value = await GetConfig()
} catch (e) {
emit('log', 'error', '配置加载失败:' + (e.message || String(e)))
}
})
async function save() {
saving.value = true
try {
await UpdateConfig(config.value)
emit('log', 'info', '配置已保存,代理已按需重启')
emit('status-refresh')
} catch (e) {
emit('log', 'error', '配置保存失败:' + (e.message || String(e)))
} finally {
saving.value = false
}
}
</script>
<template>
<div class="page">
<div class="page-title">
<div>
<h1>设置</h1>
<p>配置监听地址Lingma 传输方式会话复用和请求超时</p>
</div>
<button class="primary-button" type="button" :disabled="saving" @click="save">
{{ saving ? '保存中...' : '保存并重启' }}
</button>
</div>
<section class="grid-2">
<div class="glass-panel">
<div class="panel-header">
<div>
<h2>服务监听</h2>
<p>第三方客户端连接本地代理使用这组地址</p>
</div>
</div>
<div class="form-grid">
<div class="field">
<label>主机</label>
<input v-model="config.Host" type="text" placeholder="127.0.0.1" />
</div>
<div class="field">
<label>端口</label>
<input v-model.number="config.Port" type="number" placeholder="8095" />
</div>
<div class="field">
<label>传输方式</label>
<div class="custom-select" :class="{ open: openSelect === 'Transport' }">
<button type="button" @click="toggleSelect('Transport')">
<span>{{ selectLabel('Transport') }}</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
<div v-if="openSelect === 'Transport'" class="select-menu">
<button
v-for="option in selectOptions.Transport"
:key="option.value"
:class="{ selected: option.value === config.Transport }"
type="button"
@click="chooseOption('Transport', option.value)"
>
{{ option.label }}
</button>
</div>
</div>
</div>
<div class="field">
<label>超时秒数</label>
<input v-model.number="config.Timeout" type="number" min="1" />
</div>
<div class="field span-2">
<label>WebSocket 地址</label>
<input v-model="config.WebSocketURL" type="text" placeholder="留空自动探测 Lingma WebSocket" />
</div>
<div class="field span-2">
<label>命名管道</label>
<input v-model="config.Pipe" type="text" placeholder="留空自动探测 Windows Named Pipe" />
</div>
</div>
<div class="hint-box">
<strong>自动探测失败时</strong>
<span>先确认 VS Code / Lingma 插件已启动并登录macOS 通常填写 WebSocket例如 <code>ws://127.0.0.1:36510/</code>Windows 可填写命名管道,例如 <code>\\.\pipe\lingma-xxxx</code>,也可填写 WebSocket例如 <code>ws://127.0.0.1:36510/</code>。</span>
</div>
</div>
<div class="glass-panel">
<div class="panel-header">
<div>
<h2>会话与环境</h2>
<p>影响 Lingma 会话上下文和工具执行环境</p>
</div>
</div>
<div class="form-grid">
<div class="field">
<label>模式</label>
<div class="custom-select" :class="{ open: openSelect === 'Mode' }">
<button type="button" @click="toggleSelect('Mode')">
<span>{{ selectLabel('Mode') }}</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
<div v-if="openSelect === 'Mode'" class="select-menu">
<button
v-for="option in selectOptions.Mode"
:key="option.value"
:class="{ selected: option.value === config.Mode }"
type="button"
@click="chooseOption('Mode', option.value)"
>
{{ option.label }}
</button>
</div>
</div>
</div>
<div class="field">
<label>Shell 类型</label>
<div class="custom-select" :class="{ open: openSelect === 'ShellType' }">
<button type="button" @click="toggleSelect('ShellType')">
<span>{{ selectLabel('ShellType') }}</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
<div v-if="openSelect === 'ShellType'" class="select-menu">
<button
v-for="option in selectOptions.ShellType"
:key="option.value"
:class="{ selected: option.value === config.ShellType }"
type="button"
@click="chooseOption('ShellType', option.value)"
>
{{ option.label }}
</button>
</div>
</div>
</div>
<div class="field">
<label>会话策略</label>
<div class="custom-select" :class="{ open: openSelect === 'SessionMode' }">
<button type="button" @click="toggleSelect('SessionMode')">
<span>{{ selectLabel('SessionMode') }}</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
<div v-if="openSelect === 'SessionMode'" class="select-menu">
<button
v-for="option in selectOptions.SessionMode"
:key="option.value"
:class="{ selected: option.value === config.SessionMode }"
type="button"
@click="chooseOption('SessionMode', option.value)"
>
{{ option.label }}
</button>
</div>
</div>
</div>
<div class="field">
<label>当前文件</label>
<input v-model="config.CurrentFilePath" type="text" placeholder="可选" />
</div>
<div class="field span-2">
<label>工作目录</label>
<textarea v-model="config.Cwd" placeholder="Lingma 创建 session 时使用的 cwd"></textarea>
</div>
</div>
</div>
</section>
</div>
</template>