diff --git a/desktop/frontend/src/views/Settings.vue b/desktop/frontend/src/views/Settings.vue
index 9b3df27..206a1d6 100644
--- a/desktop/frontend/src/views/Settings.vue
+++ b/desktop/frontend/src/views/Settings.vue
@@ -10,6 +10,7 @@ const saving = ref(false)
const openSelect = ref('')
const fallbackModelsText = ref('')
const isIPCBackend = computed(() => (config.value.Backend || 'ipc') === 'ipc')
+const formattedTokenExpireAt = computed(() => formatDateTime(detection.value?.remoteTokenExpireAt))
const selectOptions = {
Backend: [
@@ -53,6 +54,21 @@ function chooseOption(field, value) {
refreshDetection()
}
+function formatDateTime(value) {
+ if (!value) return ''
+ const date = new Date(value)
+ if (Number.isNaN(date.getTime())) return value
+ return new Intl.DateTimeFormat('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false,
+ }).format(date)
+}
+
onMounted(async () => {
try {
config.value = await GetConfig()
@@ -246,7 +262,8 @@ async function save() {
登录态有效期
- {{ detection.remoteTokenExpireAt || '未提供' }}
+ {{ formattedTokenExpireAt || '未提供' }}
+ 原始 {{ detection.remoteTokenExpireAt }}
(已过期)
diff --git a/internal/remote/client.go b/internal/remote/client.go
index 991dfcc..1e4b0d4 100644
--- a/internal/remote/client.go
+++ b/internal/remote/client.go
@@ -12,6 +12,7 @@ import (
"net/url"
"os"
"path/filepath"
+ "regexp"
"sort"
"strconv"
"strings"
@@ -25,6 +26,8 @@ const (
modelListPath = "/algo/api/v2/model/list"
)
+var remoteBaseURLPattern = regexp.MustCompile(`https?://[^\s"'<>),\]}]+`)
+
type Config struct {
BaseURL string
AuthFile string
@@ -535,12 +538,16 @@ func uniqueStrings(values []string) []string {
}
func extractBaseURLFromText(text string) string {
+ matches := remoteBaseURLPattern.FindAllString(text, -1)
+ for i := len(matches) - 1; i >= 0; i-- {
+ if value := normalizeRemoteBaseURLHint(matches[i]); value != "" {
+ return value
+ }
+ }
for _, marker := range []string{
"endpoint config:",
"Using service url:",
"Download asset from:",
- "https://ai-lingma",
- "https://lingma",
} {
if value := extractBaseURLAfterMarker(text, marker); value != "" {
return value
@@ -574,10 +581,16 @@ func normalizeRemoteBaseURLHint(raw string) string {
if raw == "" {
return ""
}
+ if strings.HasPrefix(raw, "ttps://") {
+ raw = "h" + raw
+ }
parsed, err := url.Parse(raw)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return ""
}
+ if parsed.Scheme != "http" && parsed.Scheme != "https" {
+ return ""
+ }
host := strings.ToLower(parsed.Host)
if !strings.Contains(host, "lingma") && !strings.Contains(host, "rdc.aliyuncs.com") {
return ""
diff --git a/internal/remote/client_test.go b/internal/remote/client_test.go
index 0ab9729..03824a9 100644
--- a/internal/remote/client_test.go
+++ b/internal/remote/client_test.go
@@ -37,6 +37,28 @@ func TestExtractBaseURLFromMarketplaceLog(t *testing.T) {
}
}
+func TestExtractBaseURLFromRawWindowsLogURL(t *testing.T) {
+ got := extractBaseURLFromText(`2026-05-06T12:00:00 endpoint=https://ai-lingma-cmb01-cn-beijing.rdc.aliyuncs.com/algo/api/v2/model/list`)
+ want := "https://ai-lingma-cmb01-cn-beijing.rdc.aliyuncs.com"
+ if got != want {
+ t.Fatalf("got %q, want %q", got, want)
+ }
+}
+
+func TestNormalizeBaseURLRepairsMissingLeadingH(t *testing.T) {
+ got := normalizeRemoteBaseURLHint(`ttps://ai-lingma-cmb01-cn-beijing.rdc.aliyuncs.com`)
+ want := "https://ai-lingma-cmb01-cn-beijing.rdc.aliyuncs.com"
+ if got != want {
+ t.Fatalf("got %q, want %q", got, want)
+ }
+}
+
+func TestNormalizeBaseURLRejectsUnsupportedScheme(t *testing.T) {
+ if got := normalizeRemoteBaseURLHint(`ftp://ai-lingma-cmb01-cn-beijing.rdc.aliyuncs.com`); got != "" {
+ t.Fatalf("got %q, want empty", got)
+ }
+}
+
func TestExtractMachineIDFromTextMarkers(t *testing.T) {
got := extractMachineIDFromText(`2026-05-06 info using machine id from file: abcdef1234567890abcdef`)
if got != "abcdef1234567890abcdef" {