Skip to content

Commit b743656

Browse files
committed
Implement update notification feature with version check and download functionality
1 parent 1c1a844 commit b743656

11 files changed

+542
-6
lines changed

SemanticCode.Desktop/Program.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,24 @@ public static void Main(string[] args) => BuildAvaloniaApp()
1818
public static AppBuilder BuildAvaloniaApp()
1919
=> AppBuilder.Configure<App>()
2020
.UsePlatformDetect()
21+
.UseSkia()
2122
.WithInterFont()
23+
.With(new Win32PlatformOptions()
24+
{
25+
RenderingMode =
26+
[
27+
Win32RenderingMode.Software
28+
]
29+
})
30+
.With(new X11PlatformOptions
31+
{
32+
RenderingMode = [X11RenderingMode.Software]
33+
})
2234
.UseReactiveUI()
35+
// 仅在 Debug 模式下启用日志追踪
36+
#if DEBUG
2337
.LogToTrace();
38+
#else
39+
;
40+
#endif
2441
}

SemanticCode.Desktop/Properties/PublishProfiles/FolderProfile.pubxml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<PublishDir>bin\Release\net9.0\publish\win-x64\</PublishDir>
88
<PublishProtocol>FileSystem</PublishProtocol>
99
<_TargetId>Folder</_TargetId>
10-
<TargetFramework>net9.0</TargetFramework>
10+
<TargetFramework>net10.0</TargetFramework>
1111
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
1212
<SelfContained>true</SelfContained>
1313
<PublishSingleFile>false</PublishSingleFile>

SemanticCode.Desktop/Properties/PublishProfiles/FolderProfile.pubxml.user

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
33
<Project>
44
<PropertyGroup>
5-
<History>True|2025-08-05T08:12:45.8443145Z||;False|2025-08-05T16:11:45.2522596+08:00||;False|2025-08-05T16:11:29.0963440+08:00||;False|2025-08-05T16:10:51.9252612+08:00||;False|2025-08-05T16:10:26.6893109+08:00||;</History>
5+
<History>False|2025-08-06T09:34:42.7092506Z||;False|2025-08-06T17:34:36.2787621+08:00||;True|2025-08-05T16:12:45.8443145+08:00||;False|2025-08-05T16:11:45.2522596+08:00||;False|2025-08-05T16:11:29.0963440+08:00||;False|2025-08-05T16:10:51.9252612+08:00||;False|2025-08-05T16:10:26.6893109+08:00||;</History>
66
<LastFailureDetails />
77
</PropertyGroup>
88
</Project>

SemanticCode.Desktop/SemanticCode.Desktop.csproj

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,43 @@
77
<AssemblyTitle>Semantic Code</AssemblyTitle>
88
<Product>Semantic Code</Product>
99
<AssemblyProduct>Semantic Code</AssemblyProduct>
10+
11+
<!-- 内存优化设置 -->
12+
<TieredCompilation>false</TieredCompilation>
13+
<ReadyToRun>false</ReadyToRun>
14+
<ServerGarbageCollection>false</ServerGarbageCollection>
15+
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
16+
<RetainVMGarbageCollection>false</RetainVMGarbageCollection>
17+
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
18+
<TieredCompilationQuickJitForLoops>false</TieredCompilationQuickJitForLoops>
19+
20+
<!-- 减少工作集内存 -->
21+
<UseWorkingSetMemory>true</UseWorkingSetMemory>
22+
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
23+
<DebuggerSupport>false</DebuggerSupport>
24+
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
25+
<EventSourceSupport>false</EventSourceSupport>
26+
<UseSystemResourceKeys>true</UseSystemResourceKeys>
27+
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
28+
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
29+
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
1030
</PropertyGroup>
1131

1232
<PropertyGroup>
1333
<PublishAot>true</PublishAot>
14-
<TrimMode>lite</TrimMode>
34+
<TrimMode>full</TrimMode>
1535
<PublishTrimmed>true</PublishTrimmed>
1636
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
17-
37+
38+
<!-- AOT 内存优化 -->
39+
<IlcDisableReflection>true</IlcDisableReflection>
40+
<IlcTrimMetadata>true</IlcTrimMetadata>
41+
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
42+
<InvariantGlobalization>false</InvariantGlobalization>
43+
44+
<!-- 移除调试信息以减少内存 -->
45+
<DebugType>none</DebugType>
46+
<DebugSymbols>false</DebugSymbols>
1847
</PropertyGroup>
1948

2049
<PropertyGroup>

SemanticCode.sln.DotSettings.user

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAvaloniaXamlLoader_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fedf80116cb66406a9794be4ad469395814a00_003F67_003F72f2efdc_003FAvaloniaXamlLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
33
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClrPropertyInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c60568d0b54415dad3590c4497304461f9000_003Fc6_003F25c9038e_003FClrPropertyInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACultureInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F96cdc0cbcc484f48a653f0bd0ba43069f04918_003Fb1_003F05c3081b_003FCultureInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
45
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F96cdc0cbcc484f48a653f0bd0ba43069f04918_003Fe8_003F4bb1170b_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
56
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDisposable_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F96cdc0cbcc484f48a653f0bd0ba43069f04918_003F64_003F65730341_003FIDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
67
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANavigationView_002Eproperties_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d78d280f056b9a0e3eab34add17e11f4177cea9b9f692f38f54b8cbc195dc85_003FNavigationView_002Eproperties_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
78
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveObject_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fd06f96fa1816853b6b141e4e445f8117486b5f410e66dec683383a9bcce758_003FReactiveObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
89
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASymbol_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F860d38664572484ebf4032dba43bddb7341a00_003F85_003Fc6ba96c4_003FSymbol_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
910
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASynchronizationContextTaskScheduler_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F96cdc0cbcc484f48a653f0bd0ba43069f04918_003F54_003Faace4664_003FSynchronizationContextTaskScheduler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1011
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATask_00601_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F96cdc0cbcc484f48a653f0bd0ba43069f04918_003F88_003Faa4b39a0_003FTask_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
12+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWin32PlatformOptions_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb89d4253986414aaca91dbe0f45519bdda00_003F14_003F43705144_003FWin32PlatformOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
13+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AX11RenderingMode_002Ecs_002Fl_003AC_0021_003FUsers_003Ftoken_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd6c9ab445056464dbc0ef68cbc56474052600_003F54_003Fcfa5954f_003FX11RenderingMode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1114

1215

1316

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.IO;
3+
using System.Text.Json;
4+
5+
namespace SemanticCode.Services;
6+
7+
public class UpdateConfigService
8+
{
9+
private static readonly string ConfigFilePath = Path.Combine(
10+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
11+
"SemanticCode",
12+
"update-config.json");
13+
14+
public class UpdateConfig
15+
{
16+
public string IgnoredVersion { get; set; } = string.Empty;
17+
public DateTime LastCheckTime { get; set; } = DateTime.MinValue;
18+
}
19+
20+
public UpdateConfig LoadConfig()
21+
{
22+
try
23+
{
24+
if (File.Exists(ConfigFilePath))
25+
{
26+
var json = File.ReadAllText(ConfigFilePath);
27+
return JsonSerializer.Deserialize<UpdateConfig>(json) ?? new UpdateConfig();
28+
}
29+
}
30+
catch (Exception)
31+
{
32+
// 忽略读取错误
33+
}
34+
35+
return new UpdateConfig();
36+
}
37+
38+
public void SaveConfig(UpdateConfig config)
39+
{
40+
try
41+
{
42+
var directory = Path.GetDirectoryName(ConfigFilePath);
43+
if (!Directory.Exists(directory))
44+
{
45+
Directory.CreateDirectory(directory!);
46+
}
47+
48+
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
49+
{
50+
WriteIndented = true
51+
});
52+
File.WriteAllText(ConfigFilePath, json);
53+
}
54+
catch (Exception)
55+
{
56+
// 忽略保存错误
57+
}
58+
}
59+
60+
public void IgnoreVersion(string version)
61+
{
62+
var config = LoadConfig();
63+
config.IgnoredVersion = version;
64+
SaveConfig(config);
65+
}
66+
67+
public bool IsVersionIgnored(string version)
68+
{
69+
var config = LoadConfig();
70+
return config.IgnoredVersion == version;
71+
}
72+
}

SemanticCode/Services/UpdateService.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Text.Json;
44
using System.Reflection;
55
using System.Threading.Tasks;
6+
using System.IO;
7+
using System.Diagnostics;
68
using SemanticCode.Models;
79

810
namespace SemanticCode.Services;
@@ -79,6 +81,78 @@ private bool IsNewerVersion(string currentVersion, string latestVersion)
7981
return false;
8082
}
8183

84+
public bool IsWindowsPlatform()
85+
{
86+
return OperatingSystem.IsWindows();
87+
}
88+
89+
public string GetWindowsInstallerUrl(string version)
90+
{
91+
return $"https://github.com/AIDotNet/SemanticCode/releases/download/v{version}/SemanticCode-Setup-{version}-win-x64.exe";
92+
}
93+
94+
public async Task<string?> DownloadUpdateAsync(string downloadUrl, IProgress<float>? progress = null)
95+
{
96+
try
97+
{
98+
var fileName = Path.GetFileName(new Uri(downloadUrl).LocalPath);
99+
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
100+
101+
using var response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
102+
response.EnsureSuccessStatusCode();
103+
104+
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
105+
var downloadedBytes = 0L;
106+
107+
using var contentStream = await response.Content.ReadAsStreamAsync();
108+
using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
109+
110+
var buffer = new byte[8192];
111+
int bytesRead;
112+
113+
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
114+
{
115+
await fileStream.WriteAsync(buffer, 0, bytesRead);
116+
downloadedBytes += bytesRead;
117+
118+
if (totalBytes > 0 && progress != null)
119+
{
120+
var progressPercentage = (float)downloadedBytes / totalBytes;
121+
progress.Report(progressPercentage);
122+
}
123+
}
124+
125+
return tempPath;
126+
}
127+
catch (Exception)
128+
{
129+
return null;
130+
}
131+
}
132+
133+
public void StartUpdateInstaller(string installerPath)
134+
{
135+
if (!File.Exists(installerPath))
136+
return;
137+
138+
try
139+
{
140+
// 启动安装程序
141+
Process.Start(new ProcessStartInfo
142+
{
143+
FileName = installerPath,
144+
UseShellExecute = true
145+
});
146+
147+
// 关闭当前应用程序
148+
Environment.Exit(0);
149+
}
150+
catch (Exception)
151+
{
152+
// 忽略错误,让用户手动运行安装程序
153+
}
154+
}
155+
82156
public void Dispose()
83157
{
84158
_httpClient.Dispose();

SemanticCode/ViewModels/HomeViewModel.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using System.ComponentModel;
1313
using System.Net.Http;
1414
using System.Text;
15+
using SemanticCode.Services;
16+
using SemanticCode.Views;
1517

1618
namespace SemanticCode.ViewModels;
1719

@@ -28,6 +30,9 @@ public class HomeViewModel : ViewModelBase
2830
private IBrush _nodeStatusColor = Brushes.Orange;
2931
private IBrush _gitStatusColor = Brushes.Orange;
3032
private IBrush _envVarStatusColor = Brushes.Orange;
33+
private readonly UpdateService _updateService;
34+
private readonly UpdateConfigService _updateConfigService;
35+
private bool _hasCheckedForUpdatesThisSession = false;
3136

3237
public string Title { get; } = "Semantic Code";
3338
public string WelcomeMessage { get; } = "欢迎使用 Semantic Code 一款Claude Code工具。";
@@ -118,6 +123,9 @@ public IBrush EnvVarStatusColor
118123

119124
public HomeViewModel()
120125
{
126+
_updateService = new UpdateService();
127+
_updateConfigService = new UpdateConfigService();
128+
121129
NavigateCommand = ReactiveCommand.Create<string>(Navigate);
122130
OpenProjectCommand = ReactiveCommand.Create<string>(OpenProject);
123131
RefreshStatusCommand = ReactiveCommand.CreateFromTask(RefreshStatusAsync);
@@ -127,8 +135,12 @@ public HomeViewModel()
127135
InitializeQuickActions();
128136
InitializeRecentProjects();
129137

130-
// 启动时检查状态
131-
Task.Run(async () => await RefreshStatusAsync());
138+
// 启动时检查状态和版本更新(只检查一次)
139+
Task.Run(async () =>
140+
{
141+
await RefreshStatusAsync();
142+
await CheckForUpdatesAsync();
143+
});
132144
}
133145

134146
private void InitializeFeatures()
@@ -700,6 +712,62 @@ private async Task<CommandResult> RunCommandAsync(string command, string argumen
700712
}
701713
});
702714
}
715+
716+
private async Task CheckForUpdatesAsync()
717+
{
718+
// 确保只在首次加载时检查更新,避免重复检查
719+
if (_hasCheckedForUpdatesThisSession)
720+
return;
721+
722+
_hasCheckedForUpdatesThisSession = true;
723+
724+
try
725+
{
726+
var updateInfo = await _updateService.CheckForUpdatesAsync();
727+
728+
if (updateInfo != null && updateInfo.IsNewerVersion)
729+
{
730+
// 检查是否被用户忽略
731+
if (!_updateConfigService.IsVersionIgnored(updateInfo.Version))
732+
{
733+
// 在UI线程中显示更新对话框
734+
await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () =>
735+
{
736+
await ShowUpdateNotification(updateInfo);
737+
});
738+
}
739+
}
740+
}
741+
catch (Exception)
742+
{
743+
// 静默处理更新检查失败
744+
}
745+
}
746+
747+
private async Task ShowUpdateNotification(Models.UpdateInfo updateInfo)
748+
{
749+
var viewModel = new UpdateNotificationViewModel(_updateService, updateInfo);
750+
var dialog = new UpdateNotificationDialog(viewModel);
751+
752+
// 设置对话框事件处理
753+
viewModel.RemindLaterRequested += (s, e) => dialog.Close();
754+
viewModel.IgnoreVersionRequested += (s, e) =>
755+
{
756+
_updateConfigService.IgnoreVersion(updateInfo.Version);
757+
dialog.Close();
758+
};
759+
viewModel.UpdateCompleted += (s, e) => dialog.Close();
760+
761+
// 显示对话框
762+
var mainWindow = Avalonia.Application.Current?.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop
763+
? desktop.MainWindow
764+
: null;
765+
766+
if (mainWindow != null)
767+
{
768+
await dialog.ShowDialog(mainWindow);
769+
}
770+
}
703771
}
704772

705773
public class CommandResult

0 commit comments

Comments
 (0)