fix: one app.
This commit is contained in:
parent
ad5312ba10
commit
667c1c91a1
@ -12,7 +12,8 @@
|
|||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(xargs ls:*)",
|
"Bash(xargs ls:*)",
|
||||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(e['Route']\\) for e in d.get\\('Endpoints',[]\\)[:20]]\")"
|
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(e['Route']\\) for e in d.get\\('Endpoints',[]\\)[:20]]\")",
|
||||||
|
"Bash(python3:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,8 +73,9 @@ public class AnalysisService
|
|||||||
|
|
||||||
yield return new AnalysisEvent { ProgressPercentage = 15, Message = "Obtendo transcrição..." };
|
yield return new AnalysisEvent { ProgressPercentage = 15, Message = "Obtendo transcrição..." };
|
||||||
string? transcript = null;
|
string? transcript = null;
|
||||||
|
string? transcriptReadable = null;
|
||||||
try {
|
try {
|
||||||
transcript = await GetTranscriptViaYtDlpAsync(request.VideoUrl, request.Language, tempDir);
|
(transcript, transcriptReadable) = await GetTranscriptViaYtDlpAsync(request.VideoUrl, request.Language, tempDir);
|
||||||
if (string.IsNullOrWhiteSpace(transcript)) errorMessage = "O vídeo não possui transcrição disponível.";
|
if (string.IsNullOrWhiteSpace(transcript)) errorMessage = "O vídeo não possui transcrição disponível.";
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
errorMessage = $"Erro na transcrição: {ex.Message}";
|
errorMessage = $"Erro na transcrição: {ex.Message}";
|
||||||
@ -112,7 +113,7 @@ public class AnalysisService
|
|||||||
|
|
||||||
yield return new AnalysisEvent { ProgressPercentage = 90, Message = "Gerando documento PDF..." };
|
yield return new AnalysisEvent { ProgressPercentage = 90, Message = "Gerando documento PDF..." };
|
||||||
try {
|
try {
|
||||||
var pdfBytes = GeneratePdf(docTitle!, summary!, request.VideoUrl, sections, category!);
|
var pdfBytes = GeneratePdf(docTitle!, summary!, request.VideoUrl, sections, category!, transcriptReadable);
|
||||||
finalResult = new AnalysisResult {
|
finalResult = new AnalysisResult {
|
||||||
VideoTitle = videoInfo.Title,
|
VideoTitle = videoInfo.Title,
|
||||||
DocumentTitle = docTitle!,
|
DocumentTitle = docTitle!,
|
||||||
@ -211,7 +212,7 @@ public class AnalysisService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetTranscriptViaYtDlpAsync(string url, string lang, string dir)
|
private async Task<(string flat, string readable)> GetTranscriptViaYtDlpAsync(string url, string lang, string dir)
|
||||||
{
|
{
|
||||||
var ytdlp = GetYtDlpPath();
|
var ytdlp = GetYtDlpPath();
|
||||||
var cookies = GetCookiesArg();
|
var cookies = GetCookiesArg();
|
||||||
@ -228,10 +229,12 @@ public class AnalysisService
|
|||||||
using var p = Process.Start(psi)!;
|
using var p = Process.Start(psi)!;
|
||||||
await p.WaitForExitAsync();
|
await p.WaitForExitAsync();
|
||||||
var file = Directory.GetFiles(dir, "*.vtt").FirstOrDefault();
|
var file = Directory.GetFiles(dir, "*.vtt").FirstOrDefault();
|
||||||
return file == null ? "" : ParseVttToText(await File.ReadAllTextAsync(file));
|
if (file == null) return ("", "");
|
||||||
|
var vtt = await File.ReadAllTextAsync(file);
|
||||||
|
return (ParseVttToFlat(vtt), ParseVttToReadable(vtt));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParseVttToText(string vtt)
|
private string ParseVttToFlat(string vtt)
|
||||||
{
|
{
|
||||||
var lines = vtt.Split('\n')
|
var lines = vtt.Split('\n')
|
||||||
.Select(l => l.Trim())
|
.Select(l => l.Trim())
|
||||||
@ -239,6 +242,72 @@ public class AnalysisService
|
|||||||
return string.Join(" ", lines.Select(l => Regex.Replace(l, @"<[^>]*>", ""))).Replace(" ", " ");
|
return string.Join(" ", lines.Select(l => Regex.Replace(l, @"<[^>]*>", ""))).Replace(" ", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ParseVttToReadable(string vtt)
|
||||||
|
{
|
||||||
|
// Parse cues: timestamp line + text lines
|
||||||
|
var cues = new List<(TimeSpan time, string text)>();
|
||||||
|
var lines = vtt.Split('\n').Select(l => l.Trim()).ToArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
var line = lines[i];
|
||||||
|
if (!line.Contains("-->")) continue;
|
||||||
|
|
||||||
|
// Parse start time from "HH:MM:SS.mmm --> HH:MM:SS.mmm"
|
||||||
|
var timePart = line.Split("-->")[0].Trim().Replace(',', '.');
|
||||||
|
if (!TimeSpan.TryParse(timePart, out var ts)) continue;
|
||||||
|
|
||||||
|
// Collect text lines until blank
|
||||||
|
var textLines = new List<string>();
|
||||||
|
i++;
|
||||||
|
while (i < lines.Length && !string.IsNullOrEmpty(lines[i]) && !lines[i].Contains("-->"))
|
||||||
|
{
|
||||||
|
var t = Regex.Replace(lines[i], @"<[^>]*>", "").Trim();
|
||||||
|
if (!string.IsNullOrEmpty(t)) textLines.Add(t);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
i--; // step back, outer loop will increment
|
||||||
|
|
||||||
|
if (textLines.Count > 0)
|
||||||
|
cues.Add((ts, string.Join(" ", textLines)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cues.Count == 0) return ParseVttToFlat(vtt);
|
||||||
|
|
||||||
|
// Merge consecutive cues with same/similar text (VTT often duplicates lines)
|
||||||
|
var merged = new List<(TimeSpan time, string text)>();
|
||||||
|
foreach (var cue in cues)
|
||||||
|
{
|
||||||
|
if (merged.Count > 0 && merged[^1].text == cue.text) continue;
|
||||||
|
merged.Add(cue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group into paragraphs every ~60 seconds
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
TimeSpan? paraStart = null;
|
||||||
|
var paraWords = new List<string>();
|
||||||
|
|
||||||
|
void FlushParagraph()
|
||||||
|
{
|
||||||
|
if (paraWords.Count == 0) return;
|
||||||
|
sb.AppendLine($"[{paraStart!.Value:hh\\:mm\\:ss}] {string.Join(" ", paraWords)}");
|
||||||
|
sb.AppendLine();
|
||||||
|
paraWords.Clear();
|
||||||
|
paraStart = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (time, text) in merged)
|
||||||
|
{
|
||||||
|
if (paraStart == null) paraStart = time;
|
||||||
|
paraWords.Add(text);
|
||||||
|
if ((time - paraStart.Value).TotalSeconds >= 60)
|
||||||
|
FlushParagraph();
|
||||||
|
}
|
||||||
|
FlushParagraph();
|
||||||
|
|
||||||
|
return sb.ToString().TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<(List<TutorialSection> sections, string rawJson, string category, string docTitle, string summary)>
|
private async Task<(List<TutorialSection> sections, string rawJson, string category, string docTitle, string summary)>
|
||||||
GenerateTutorialContentAsync(string transcript, VideoInfo video, string inLang, string? outLang, string? userContext, CancellationToken ct)
|
GenerateTutorialContentAsync(string transcript, VideoInfo video, string inLang, string? outLang, string? userContext, CancellationToken ct)
|
||||||
{
|
{
|
||||||
@ -322,11 +391,12 @@ Escreva tudo em {outName}.";
|
|||||||
return line?.Trim();
|
return line?.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] GeneratePdf(string title, string summary, string url, List<TutorialSection> sections, string category)
|
private byte[] GeneratePdf(string title, string summary, string url, List<TutorialSection> sections, string category, string? transcriptReadable = null)
|
||||||
{
|
{
|
||||||
var color = category switch { "TUTORIAL" => Colors.Green.Medium, "LECTURE" => Colors.Orange.Medium, _ => Colors.Blue.Medium };
|
var color = category switch { "TUTORIAL" => Colors.Green.Medium, "LECTURE" => Colors.Orange.Medium, _ => Colors.Blue.Medium };
|
||||||
return Document.Create(container =>
|
return Document.Create(container =>
|
||||||
{
|
{
|
||||||
|
// Main content page
|
||||||
container.Page(page =>
|
container.Page(page =>
|
||||||
{
|
{
|
||||||
page.Margin(2, Unit.Centimetre);
|
page.Margin(2, Unit.Centimetre);
|
||||||
@ -358,6 +428,53 @@ Escreva tudo em {outName}.";
|
|||||||
});
|
});
|
||||||
page.Footer().AlignCenter().Text(x => { x.Span("VideoStudy.app — "); x.CurrentPageNumber(); });
|
page.Footer().AlignCenter().Text(x => { x.Span("VideoStudy.app — "); x.CurrentPageNumber(); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Transcript appendix page
|
||||||
|
if (!string.IsNullOrWhiteSpace(transcriptReadable))
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Margin(2, Unit.Centimetre);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(10).FontFamily("Segoe UI"));
|
||||||
|
page.Header().Column(c =>
|
||||||
|
{
|
||||||
|
c.Item().Row(r =>
|
||||||
|
{
|
||||||
|
r.RelativeItem().Text("Apêndice — Transcrição").SemiBold().FontSize(18).FontColor(Colors.Grey.Darken2);
|
||||||
|
r.ConstantItem(80).AlignRight().Text(title).FontSize(8).FontColor(Colors.Grey.Medium).Italic();
|
||||||
|
});
|
||||||
|
c.Item().PaddingTop(5).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
|
||||||
|
c.Item().PaddingTop(4).Text("Cada parágrafo representa aproximadamente 60 segundos. O timestamp indica o início do trecho.")
|
||||||
|
.FontSize(8).Italic().FontColor(Colors.Grey.Medium);
|
||||||
|
});
|
||||||
|
page.Content().PaddingVertical(1, Unit.Centimetre).Column(col =>
|
||||||
|
{
|
||||||
|
foreach (var paragraph in transcriptReadable.Split("\n\n", StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var trimmed = paragraph.Trim();
|
||||||
|
if (string.IsNullOrEmpty(trimmed)) continue;
|
||||||
|
|
||||||
|
// Split timestamp from text: "[HH:MM:SS] rest of text"
|
||||||
|
var bracketEnd = trimmed.IndexOf(']');
|
||||||
|
if (bracketEnd > 0 && trimmed.StartsWith('['))
|
||||||
|
{
|
||||||
|
var timestamp = trimmed[..(bracketEnd + 1)];
|
||||||
|
var text = trimmed[(bracketEnd + 1)..].Trim();
|
||||||
|
col.Item().PaddingBottom(8).Column(p =>
|
||||||
|
{
|
||||||
|
p.Item().Text(timestamp).Bold().FontSize(9).FontColor(Colors.Blue.Medium);
|
||||||
|
p.Item().PaddingTop(2).Text(text).LineHeight(1.5f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
col.Item().PaddingBottom(8).Text(trimmed).LineHeight(1.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
page.Footer().AlignCenter().Text(x => { x.Span("VideoStudy.app — "); x.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
}).GeneratePdf();
|
}).GeneratePdf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
VideoStudy.App/AppShell.razor
Normal file
21
VideoStudy.App/AppShell.razor
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
|
@using VideoStudy.UI
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>VideoStudy</title>
|
||||||
|
<base href="/" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="_content/VideoStudy.UI/app.css" />
|
||||||
|
<HeadOutlet />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="_framework/blazor.web.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Photino.Blazor;
|
using Photino.NET;
|
||||||
using VideoStudy.UI;
|
using VideoStudy.UI;
|
||||||
|
|
||||||
namespace VideoStudy.App;
|
namespace VideoStudy.App;
|
||||||
@ -9,26 +11,48 @@ class Program
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var builder = PhotinoBlazorAppBuilder.CreateDefault(args);
|
const string appUrl = "http://localhost:5002";
|
||||||
|
|
||||||
builder.Services.AddVideoStudyUI();
|
var serverReady = new ManualResetEventSlim(false);
|
||||||
|
|
||||||
builder.Services.AddScoped(sp => new HttpClient
|
// Start Blazor Server in background thread
|
||||||
|
var serverThread = new Thread(() =>
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri("http://localhost:5000"),
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
Timeout = TimeSpan.FromMinutes(10)
|
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
builder.Services.AddVideoStudyUI();
|
||||||
|
builder.Services.AddScoped(_ => new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri("http://localhost:5000"),
|
||||||
|
Timeout = TimeSpan.FromMinutes(10)
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
app.UseAntiforgery();
|
||||||
|
app.MapRazorComponents<AppShell>()
|
||||||
|
.AddInteractiveServerRenderMode()
|
||||||
|
.AddAdditionalAssemblies(typeof(Routes).Assembly);
|
||||||
|
|
||||||
|
app.Lifetime.ApplicationStarted.Register(() => serverReady.Set());
|
||||||
|
app.Run(appUrl);
|
||||||
});
|
});
|
||||||
|
serverThread.IsBackground = true;
|
||||||
|
serverThread.Start();
|
||||||
|
|
||||||
builder.RootComponents.Add<VideoStudy.UI.App>("#app");
|
// Wait for Kestrel to be ready (max 15s)
|
||||||
|
serverReady.Wait(TimeSpan.FromSeconds(15));
|
||||||
|
|
||||||
var app = builder.Build();
|
// Open native window pointing to local server
|
||||||
|
var window = new PhotinoWindow()
|
||||||
app.MainWindow
|
|
||||||
.SetTitle("VideoStudy")
|
.SetTitle("VideoStudy")
|
||||||
.SetSize(1280, 800)
|
.SetSize(1280, 800)
|
||||||
.SetDevToolsEnabled(true)
|
.SetDevToolsEnabled(true)
|
||||||
.Center();
|
.Center()
|
||||||
|
.Load(new Uri(appUrl));
|
||||||
|
|
||||||
app.Run();
|
window.WaitForClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>VideoStudy.App</RootNamespace>
|
<RootNamespace>VideoStudy.App</RootNamespace>
|
||||||
@ -10,13 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Photino.Blazor" Version="3.2.0" />
|
<PackageReference Include="Photino.NET" Version="4.0.16" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Update="wwwroot\**\*">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,61 +1,37 @@
|
|||||||
@using VideoStudy.Shared
|
@using VideoStudy.Shared
|
||||||
|
|
||||||
<div class="processor-card p-2">
|
<div class="processor-card p-2">
|
||||||
<div class="input-group mb-3">
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold text-muted small">URL do YouTube</label>
|
||||||
<input type="text" class="form-control form-control-lg"
|
<input type="text" class="form-control form-control-lg"
|
||||||
placeholder="https://www.youtube.com/watch?v=..."
|
placeholder="https://www.youtube.com/watch?v=..."
|
||||||
@bind="VideoUrl" disabled="@IsProcessing" />
|
@bind="VideoUrl" @bind:event="oninput"
|
||||||
<button type="button" class="btn btn-primary"
|
@onchange="OnUrlChanged"
|
||||||
@onclick="FetchInfo"
|
disabled="@IsProcessing" />
|
||||||
disabled="@(IsProcessing || string.IsNullOrWhiteSpace(VideoUrl))">
|
|
||||||
Verificar
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (VideoInfo != null)
|
@if (VideoInfo != null)
|
||||||
{
|
{
|
||||||
<div class="mb-4 p-3 bg-light rounded-3">
|
<div class="p-3 bg-light rounded-3">
|
||||||
<div class="fw-bold">@VideoInfo.Title</div>
|
<div class="fw-bold">@VideoInfo.Title</div>
|
||||||
<div class="d-flex gap-3 text-muted small mt-1">
|
<div class="d-flex gap-3 text-muted small mt-1">
|
||||||
<span>@VideoInfo.Author</span>
|
<span>@VideoInfo.Author</span>
|
||||||
<span>@VideoInfo.Duration.ToString(@"hh\:mm\:ss")</span>
|
<span>@VideoInfo.Duration.ToString(@"hh\:mm\:ss")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
|
||||||
<button class="btn btn-lg btn-primary" @onclick="StartProcessing" disabled="@IsProcessing">
|
|
||||||
@if (IsProcessing)
|
|
||||||
{
|
|
||||||
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
||||||
<span>Analisando...</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>Analisar com IA</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter] public EventCallback<string> OnVideoUrlChanged { get; set; }
|
[Parameter] public EventCallback<string> OnVideoUrlChanged { get; set; }
|
||||||
[Parameter] public EventCallback OnStart { get; set; }
|
|
||||||
[Parameter] public bool IsProcessing { get; set; }
|
[Parameter] public bool IsProcessing { get; set; }
|
||||||
[Parameter] public VideoInfo? VideoInfo { get; set; }
|
[Parameter] public VideoInfo? VideoInfo { get; set; }
|
||||||
|
|
||||||
[Inject] YouTubeService YouTubeService { get; set; } = default!;
|
|
||||||
|
|
||||||
private string VideoUrl { get; set; } = "";
|
private string VideoUrl { get; set; } = "";
|
||||||
|
|
||||||
private async Task FetchInfo()
|
private async Task OnUrlChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(VideoUrl)) return;
|
VideoUrl = e.Value?.ToString() ?? "";
|
||||||
await OnVideoUrlChanged.InvokeAsync(VideoUrl);
|
await OnVideoUrlChanged.InvokeAsync(VideoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartProcessing()
|
|
||||||
{
|
|
||||||
await OnStart.InvokeAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,9 @@
|
|||||||
<div class="card shadow-sm border-0 rounded-4 overflow-hidden mb-4 p-4 bg-white">
|
<div class="card shadow-sm border-0 rounded-4 overflow-hidden mb-4 p-4 bg-white">
|
||||||
<YouTubeProcessor
|
<YouTubeProcessor
|
||||||
OnVideoUrlChanged="HandleVideoUrlChanged"
|
OnVideoUrlChanged="HandleVideoUrlChanged"
|
||||||
OnStart="StartAnalysis"
|
|
||||||
IsProcessing="@isProcessing"
|
IsProcessing="@isProcessing"
|
||||||
VideoInfo="@currentVideoInfo" />
|
VideoInfo="@currentVideoInfo" />
|
||||||
|
|
||||||
<!-- Contexto do usuário -->
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label class="form-label fw-bold text-muted small">
|
<label class="form-label fw-bold text-muted small">
|
||||||
Contexto <span class="fw-normal">(opcional)</span>
|
Contexto <span class="fw-normal">(opcional)</span>
|
||||||
@ -38,7 +36,7 @@
|
|||||||
@bind="userContext" disabled="@isProcessing"></textarea>
|
@bind="userContext" disabled="@isProcessing"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 mt-1">
|
<div class="row g-3 mt-2">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label class="form-label fw-bold text-muted small">Idioma de saída</label>
|
<label class="form-label fw-bold text-muted small">Idioma de saída</label>
|
||||||
<select class="form-select" @bind="selectedLanguage" disabled="@isProcessing">
|
<select class="form-select" @bind="selectedLanguage" disabled="@isProcessing">
|
||||||
@ -49,6 +47,21 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mt-4">
|
||||||
|
<button class="btn btn-lg btn-primary" @onclick="StartAnalysis"
|
||||||
|
disabled="@(isProcessing || string.IsNullOrWhiteSpace(videoUrl))">
|
||||||
|
@if (isProcessing)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
<span>Analisando...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>Analisar com IA</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (isProcessing || currentStep > 0)
|
@if (isProcessing || currentStep > 0)
|
||||||
@ -117,19 +130,10 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleVideoUrlChanged(string url)
|
private void HandleVideoUrlChanged(string url)
|
||||||
{
|
{
|
||||||
videoUrl = url;
|
videoUrl = url;
|
||||||
try
|
currentVideoInfo = null;
|
||||||
{
|
|
||||||
AddLog($"Buscando informações: {url}");
|
|
||||||
currentVideoInfo = await YouTubeService.GetVideoInfoAsync(url);
|
|
||||||
if (currentVideoInfo != null) AddLog($"Encontrado: {currentVideoInfo.Title}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
AddLog($"Erro ao buscar vídeo: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartAnalysis()
|
private async Task StartAnalysis()
|
||||||
@ -148,9 +152,22 @@
|
|||||||
cts = new CancellationTokenSource();
|
cts = new CancellationTokenSource();
|
||||||
var token = cts.Token;
|
var token = cts.Token;
|
||||||
|
|
||||||
|
// Buscar info do vídeo antes de iniciar
|
||||||
currentStep = 1;
|
currentStep = 1;
|
||||||
|
statusMessage = "Buscando informações do vídeo...";
|
||||||
|
AddLog($"Verificando URL: {videoUrl}", 2);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
currentVideoInfo = await YouTubeService.GetVideoInfoAsync(videoUrl);
|
||||||
|
AddLog($"Vídeo encontrado: {currentVideoInfo.Title}", 5);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Não foi possível obter informações do vídeo: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
statusMessage = "Iniciando análise...";
|
statusMessage = "Iniciando análise...";
|
||||||
AddLog("Enviando para o servidor...", 5);
|
AddLog("Enviando para o servidor...", 8);
|
||||||
|
|
||||||
var request = new AnalysisRequest
|
var request = new AnalysisRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
@ -15,6 +15,10 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.22" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.22" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user