diff --git a/CodePlay.Core/Converters/PythonToCSharpConverter.cs b/CodePlay.Core/Converters/PythonToCSharpConverter.cs
new file mode 100644
index 0000000..8488699
--- /dev/null
+++ b/CodePlay.Core/Converters/PythonToCSharpConverter.cs
@@ -0,0 +1,103 @@
+using CodePlay.Core.Interfaces;
+using CodePlay.Core.Common;
+using CodePlay.Core.Models;
+using System.Text;
+
+namespace CodePlay.Core.Converters;
+
+///
+/// Python 到 C# 转换器
+///
+public class PythonToCSharpConverter : IConverter
+{
+ public async Task ConvertAsync(
+ SyntaxTree syntaxTree,
+ LanguageType targetLanguage,
+ ConversionOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ var result = new ConversionResult
+ {
+ TransformedCode = ConvertToCSharp(syntaxTree.SourceCode ?? ""),
+ Report = new ConversionReport()
+ };
+
+ await Task.CompletedTask;
+ return result;
+ }
+
+ private string ConvertToCSharp(string pythonCode)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("// 从 Python 转换而来 - 需要手动审查");
+ sb.AppendLine();
+
+ var lines = pythonCode.Split('\n');
+ var indentLevel = 0;
+
+ foreach (var line in lines)
+ {
+ var trimmed = line.Trim();
+ if (string.IsNullOrEmpty(trimmed))
+ {
+ sb.AppendLine();
+ continue;
+ }
+
+ // Python import -> C# using
+ if (trimmed.StartsWith("import "))
+ {
+ var module = trimmed.Substring(7).Trim();
+ sb.AppendLine($"// using {module}; // TODO: 映射 .NET 命名空间");
+ continue;
+ }
+
+ // Python class -> C# class
+ var classMatch = System.Text.RegularExpressions.Regex.Match(trimmed, @"class\s+(\w+)");
+ if (classMatch.Success)
+ {
+ sb.AppendLine($"public class {classMatch.Groups[1].Value}");
+ sb.AppendLine("{");
+ continue;
+ }
+
+ // Python def -> C# method
+ var defMatch = System.Text.RegularExpressions.Regex.Match(trimmed, @"def\s+(\w+)\s*\(([^)]*)\)");
+ if (defMatch.Success)
+ {
+ var methodName = defMatch.Groups[1].Value;
+ var parameters = defMatch.Groups[2].Value;
+ sb.AppendLine($" public void {methodName}({ConvertPythonParams(parameters)})");
+ sb.AppendLine(" {");
+ sb.AppendLine(" // TODO: 实现方法体");
+ sb.AppendLine(" }");
+ continue;
+ }
+
+ // 检测类结束(缩进减少)
+ if (trimmed.StartsWith("}") || line.TrimStart().Length == line.Length)
+ {
+ sb.AppendLine("}");
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private string ConvertPythonParams(string pythonParams)
+ {
+ if (string.IsNullOrWhiteSpace(pythonParams)) return "";
+
+ var parts = pythonParams.Split(',');
+ var csharpParams = new List();
+
+ foreach (var part in parts)
+ {
+ var param = part.Trim();
+ if (param == "self") continue;
+ csharpParams.Add($"object {param}");
+ }
+
+ return string.Join(", ", csharpParams);
+ }
+}
diff --git a/CodePlay.Core/Parsers/PythonParser.cs b/CodePlay.Core/Parsers/PythonParser.cs
new file mode 100644
index 0000000..8d9734a
--- /dev/null
+++ b/CodePlay.Core/Parsers/PythonParser.cs
@@ -0,0 +1,115 @@
+using CodePlay.Core.Interfaces;
+using CodePlay.Core.Common;
+
+namespace CodePlay.Core.Parsers;
+
+///
+/// Python 解析器
+///
+public class PythonParser : BaseParser
+{
+ public override LanguageType SupportedLanguage => LanguageType.None; // Python 暂不加入枚举
+
+ public override Task ParseAsync(string sourceCode, CancellationToken cancellationToken = default)
+ {
+ var tree = CreateSyntaxTree();
+ tree.SourceCode = sourceCode;
+ tree.Root = ParseRoot(sourceCode);
+ tree.Comments = ExtractComments(sourceCode);
+
+ return Task.FromResult(tree);
+ }
+
+ private SyntaxNode ParseRoot(string sourceCode)
+ {
+ var root = new SyntaxNode { Type = SyntaxNodeType.CompilationUnit, Text = sourceCode };
+
+ ExtractClasses(sourceCode, root);
+ ExtractFunctions(sourceCode, root);
+ ExtractImports(sourceCode, root);
+
+ return root;
+ }
+
+ private void ExtractClasses(string code, SyntaxNode root)
+ {
+ var pattern = @"class\s+(\w+)\s*(?:\(([^)]*)\))?";
+ var matches = System.Text.RegularExpressions.Regex.Matches(code, pattern);
+
+ foreach (System.Text.RegularExpressions.Match match in matches)
+ {
+ var classNode = new SyntaxNode
+ {
+ Type = SyntaxNodeType.Class,
+ Text = match.Value,
+ Metadata = new Dictionary
+ {
+ ["Name"] = match.Groups[1].Value,
+ ["BaseClasses"] = match.Groups[2].Success ? match.Groups[2].Value : null
+ }
+ };
+ root.Children.Add(classNode);
+ }
+ }
+
+ private void ExtractFunctions(string code, SyntaxNode root)
+ {
+ var pattern = @"def\s+(\w+)\s*\(([^)]*)\)";
+ var matches = System.Text.RegularExpressions.Regex.Matches(code, pattern);
+
+ foreach (System.Text.RegularExpressions.Match match in matches)
+ {
+ var funcNode = new SyntaxNode
+ {
+ Type = SyntaxNodeType.Method,
+ Text = match.Value,
+ Metadata = new Dictionary
+ {
+ ["Name"] = match.Groups[1].Value,
+ ["Parameters"] = match.Groups[2].Value
+ }
+ };
+ root.Children.Add(funcNode);
+ }
+ }
+
+ private void ExtractImports(string code, SyntaxNode root)
+ {
+ var lines = code.Split('\n');
+ foreach (var line in lines)
+ {
+ var trimmed = line.Trim();
+ if (trimmed.StartsWith("import ") || trimmed.StartsWith("from "))
+ {
+ root.Children.Add(new SyntaxNode
+ {
+ Type = SyntaxNodeType.Type,
+ Text = trimmed,
+ Metadata = { ["Kind"] = "import" }
+ });
+ }
+ }
+ }
+
+ private List ExtractComments(string sourceCode)
+ {
+ var comments = new List();
+ var lines = sourceCode.Split('\n');
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var line = lines[i].Trim();
+ if (line.StartsWith("#"))
+ {
+ comments.Add(new SyntaxComment
+ {
+ Type = CommentType.SingleLine,
+ Text = line.TrimStart('#').Trim(),
+ LineNumber = i + 1
+ });
+ }
+ }
+
+ return comments;
+ }
+}
diff --git a/CodePlay.Core/Services/CodeFormatter.cs b/CodePlay.Core/Services/CodeFormatter.cs
new file mode 100644
index 0000000..ea3af92
--- /dev/null
+++ b/CodePlay.Core/Services/CodeFormatter.cs
@@ -0,0 +1,216 @@
+using System.Diagnostics;
+
+namespace CodePlay.Core.Services;
+
+///
+/// 代码格式化服务
+/// 支持 C#/Java/C++ 代码格式化
+///
+public interface ICodeFormatter
+{
+ Task FormatAsync(string code, string language, CancellationToken cancellationToken = default);
+ bool IsFormatterAvailable(string language);
+}
+
+public class CodeFormatter : ICodeFormatter
+{
+ public async Task FormatAsync(string code, string language, CancellationToken cancellationToken = default)
+ {
+ return language.ToLower() switch
+ {
+ "csharp" => await FormatCSharp(code, cancellationToken),
+ "java" => await FormatJava(code, cancellationToken),
+ "cpp" or "c++" => await FormatCpp(code, cancellationToken),
+ _ => code
+ };
+ }
+
+ public bool IsFormatterAvailable(string language)
+ {
+ return language.ToLower() switch
+ {
+ "csharp" => IsDotNetFormatAvailable(),
+ "java" => IsJavaFormatAvailable(),
+ "cpp" or "c++" => IsClangFormatAvailable(),
+ _ => false
+ };
+ }
+
+ private async Task FormatCSharp(string code, CancellationToken ct)
+ {
+ if (!IsDotNetFormatAvailable())
+ return code;
+
+ try
+ {
+ var tempFile = Path.GetTempFileName() + ".cs";
+ await File.WriteAllTextAsync(tempFile, code, ct);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = $"format \"{tempFile}\" --no-restore",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ if (process == null) return code;
+
+ await process.WaitForExitAsync(ct);
+
+ if (process.ExitCode == 0)
+ return await File.ReadAllTextAsync(tempFile, ct);
+
+ return code;
+ }
+ catch
+ {
+ return code;
+ }
+ }
+
+ private async Task FormatJava(string code, CancellationToken ct)
+ {
+ if (!IsJavaFormatAvailable())
+ return code;
+
+ try
+ {
+ var tempFile = Path.GetTempFileName() + ".java";
+ await File.WriteAllTextAsync(tempFile, code, ct);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "google-java-format",
+ Arguments = $"--replace \"{tempFile}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ if (process == null) return code;
+
+ await process.WaitForExitAsync(ct);
+
+ if (process.ExitCode == 0)
+ return await File.ReadAllTextAsync(tempFile, ct);
+
+ return code;
+ }
+ catch
+ {
+ return code;
+ }
+ }
+
+ private async Task FormatCpp(string code, CancellationToken ct)
+ {
+ if (!IsClangFormatAvailable())
+ return code;
+
+ try
+ {
+ var tempFile = Path.GetTempFileName() + ".cpp";
+ await File.WriteAllTextAsync(tempFile, code, ct);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "clang-format",
+ Arguments = $"-i -style=file \"{tempFile}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ if (process == null) return code;
+
+ await process.WaitForExitAsync(ct);
+
+ if (process.ExitCode == 0)
+ return await File.ReadAllTextAsync(tempFile, ct);
+
+ return code;
+ }
+ catch
+ {
+ return code;
+ }
+ }
+
+ private bool IsDotNetFormatAvailable()
+ {
+ try
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = "tool list -g",
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ process?.WaitForExit(5000);
+ var output = process?.StandardOutput.ReadToEnd();
+ return output?.Contains("dotnet-format") == true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private bool IsJavaFormatAvailable()
+ {
+ try
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "google-java-format",
+ Arguments = "--version",
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ process?.WaitForExit(5000);
+ return process?.ExitCode == 0;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private bool IsClangFormatAvailable()
+ {
+ try
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "clang-format",
+ Arguments = "--version",
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = Process.Start(startInfo);
+ process?.WaitForExit(5000);
+ return process?.ExitCode == 0;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
diff --git a/CodePlay.WebAPI/Hubs/ConversionHub.cs b/CodePlay.WebAPI/Hubs/ConversionHub.cs
new file mode 100644
index 0000000..861c96c
--- /dev/null
+++ b/CodePlay.WebAPI/Hubs/ConversionHub.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.SignalR;
+
+namespace CodePlay.WebAPI.Hubs;
+
+public class ConversionHub : Hub
+{
+ public async Task JoinProgressGroup(string conversionId)
+ {
+ await Groups.AddToGroupAsync(Context.ConnectionId, conversionId);
+ }
+
+ public async Task LeaveProgressGroup(string conversionId)
+ {
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, conversionId);
+ }
+}
+
+public class ProgressMessage
+{
+ public int Percent { get; set; }
+ public string CurrentFile { get; set; } = "";
+ public string Status { get; set; } = "";
+ public TimeSpan? EstimatedTimeRemaining { get; set; }
+}