<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="https://publishpress.com/"
	>

<channel>
	<title>Nguyen Minh, Author at Tomoshare</title>
	<atom:link href="https://blog.tomosia.com.vn/author/minhnguyen/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.tomosia.com.vn/author/minhnguyen/</link>
	<description>Kênh chia sẻ kiến thức Tomosia Việt Nam</description>
	<lastBuildDate>Fri, 26 Dec 2025 09:22:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://blog.tomosia.com.vn/wp-content/uploads/2023/09/cropped-icon-32x32.png</url>
	<title>Nguyen Minh, Author at Tomoshare</title>
	<link>https://blog.tomosia.com.vn/author/minhnguyen/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Helidon là gì ? Hướng dẫn chuyển đổi các dự án Spring Boot sang Helidon bằng AI</title>
		<link>https://blog.tomosia.com.vn/helidon-la-gi-huong-dan-chuyen-doi-cac-du-an-spring-boot-sang-helidon-bang-ai/</link>
					<comments>https://blog.tomosia.com.vn/helidon-la-gi-huong-dan-chuyen-doi-cac-du-an-spring-boot-sang-helidon-bang-ai/#comments</comments>
		
		<dc:creator><![CDATA[Nguyen Minh]]></dc:creator>
		<pubDate>Mon, 08 Dec 2025 01:33:33 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=3486</guid>

					<description><![CDATA[<p>Trong thế giới phát triển microservices bằng Java, Spring Boot là lựa chọn phổ biến nhờ tính dễ&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/helidon-la-gi-huong-dan-chuyen-doi-cac-du-an-spring-boot-sang-helidon-bang-ai/">Helidon là gì ? Hướng dẫn chuyển đổi các dự án Spring Boot sang Helidon bằng AI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Trong thế giới phát triển microservices bằng Java, Spring Boot là lựa chọn phổ biến nhờ tính dễ sử dụng và hệ sinh thái phong phú. Tuy nhiên, khi cần tối ưu hiệu suất, footprint nhỏ, và khả năng khởi động nhanh, <strong>Helidon</strong> xuất hiện như một framework nhẹ, hiện đại, được thiết kế đặc biệt cho cloud-native microservices.<br><br>Trong bài viết này, tôi sẽ giải thích Helidon là gì, điểm mạnh của nó, và hướng dẫn cách <strong>chuyển đổi một dự án Spring Boot sang Helidon</strong> bằng cách sử dụng AI, dựa theo kinh nghiệm triển khai thực tế.</p>



<h4 id="1-helidon-la-gi" class="wp-block-heading">1. Helidon là gì?</h4>



<h5 id="1-1-gioi-thieu-tong-quan" class="wp-block-heading">1.1. Giới thiệu tổng quan</h5>



<p>Helidon là một bộ công cụ Java microservices do Oracle phát triển, tập trung vào hiệu suất, tính gọn nhẹ và khả năng triển khai linh hoạt. Helidon cho phép xây dựng ứng dụng cloud-native theo chuẩn Jakarta EE, MicroProfile hoặc theo kiểu lập trình phản ứng</p>



<h5 id="1-2-hai-flavor-cua-helidon-se-va-mp" class="wp-block-heading">1.2. Hai “flavor” của Helidon: SE và MP</h5>



<p><strong>Helidon SE</strong>: Lập trình thủ công theo style functional, phù hợp với ứng dụng hiệu suất cao, yêu cầu kiểm soát chi tiết.</p>



<p><strong>Helidon MP</strong>: Tuân theo chuẩn MicroProfile, tương thích quen thuộc với mô hình enterprise Java (JAX-RS, CDI, Metrics, Config). Đây là lựa chọn gần nhất với Spring Boot.</p>



<h5 id="1-3-nhung-diem-noi-bat" class="wp-block-heading">1.3. Những điểm nổi bật</h5>



<ul class="wp-block-list">
<li>Khởi động nhanh và sử dụng ít tài nguyên.</li>



<li>Thiết kế dành cho microservices và cloud-native.</li>



<li>Tích hợp tốt các tiêu chuẩn Jakarta &amp; MicroProfile.</li>



<li>Cấu hình đơn giản, dễ triển khai.</li>
</ul>



<h5 id="1-4-moi-truong-va-kha-nang-tich-hop" class="wp-block-heading">1.4. Môi trường và khả năng tích hợp</h5>



<p>Helidon hoạt động tốt trong môi trường container, Kubernetes, hỗ trợ OpenTelemetry, GraalVM và dễ kết nối với các dịch vụ bên ngoài như databases, messaging hoặc service mesh.</p>



<h4 id="2-tai-sao-nen-can-nhac-chuyen-tu-spring-boot-sang-helidon" class="wp-block-heading">2. Tại sao nên cân nhắc chuyển từ Spring Boot sang Helidon</h4>



<h5 id="2-1-hieu-suat-va-footprint" class="wp-block-heading">2.1. Hiệu suất và footprint</h5>



<p>Helidon có footprint nhỏ hơn Spring Boot và thời gian khởi động nhanh, phù hợp cho scaling tự động và serverless.</p>



<h5 id="2-2-microservices-va-kha-nang-quan-sat" class="wp-block-heading">2.2. Microservices và khả năng quan sát</h5>



<p>MicroProfile mang theo sẵn metrics, health check, config và fault tolerance, giúp theo dõi và vận hành dịch vụ rõ ràng hơn.</p>



<h5 id="2-3-native-image-voi-graalvm" class="wp-block-heading">2.3. Native-image với GraalVM</h5>



<p>Helidon hỗ trợ native-image tốt, mang lại tốc độ khởi động gần tức thì và giảm tối đa tiêu thụ CPU/RAM.</p>



<h5 id="2-4-loi-the-khi-dung-ai" class="wp-block-heading">2.4. Lợi thế khi dùng AI</h5>



<p>AI giúp tự động chuyển đổi mã, giảm sai sót, tăng tốc độ refactor và đồng bộ hóa kiến trúc từ Spring Boot sang Helidon.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1024" height="1024" data-id="3644" src="https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26.png" alt="" class="wp-image-3644" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-300x300.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-150x150.png 150w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-768x768.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-80x80.png 80w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-380x380.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2025/11/image-26-800x800.png 800w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p><em>Nguồn :</em> <a href=" https://medium.com/oracledevs/framework-choice-strategy-when-to-use-spring-boot-vs-helidon-in-cloud-native-microservices-ab6e288b9821">https://medium.com/oracledevs/framework-choice-strategy-when-to-use-spring-boot-vs-helidon-in-cloud-native-microservices-ab6e288b9821</a></p>



<h4 id="3-chuyen-doi-spring-boot-sang-helidon-bang-ai" class="wp-block-heading">3 Chuyển đổi Spring Boot sang Helidon bằng AI</h4>



<h5 id="3-1-tao-workspace-chuyen-doi" class="wp-block-heading">3.1 Tạo Workspace chuyển đổi</h5>



<p>Mở Terminal và chạy:</p>



<pre class="wp-block-code"><code>mkdir spring-to-helidon
cd spring-to-helidon</code></pre>



<p><em><strong>Thư mục này sẽ chứa toàn bộ converter và output chuyển đổi.</strong></em></p>



<h5 id="3-2-clone-du-an-spring-boot-mau" class="wp-block-heading">3.2 Clone  dự án spring boot mẫu</h5>



<p>Chúng ta sử dụng Spring Petclinic để làm mẫu (bạn có thể thay bằng dự án của bạn).</p>



<pre class="wp-block-code"><code>git clone https://github.com/spring-projects/spring-petclinic.git</code></pre>



<p>Cấu trúc được tạo:</p>



<pre class="wp-block-code"><code>spring-to-helidon/
 └── spring-petclinic/</code></pre>



<h5 id="3-3-tao-bo-converter-ai" class="wp-block-heading">3.3 Tạo bộ CONVERTER AI</h5>



<pre class="wp-block-code"><code>mkdir converter
cd converter
mkdir prompts
mkdir output</code></pre>



<p>Trong đó :</p>



<ul class="wp-block-list">
<li>prompts/  → chứa các prompt hướng dẫn AI convert</li>



<li>output/ → nơi lưu file đã chuyển đổi</li>
</ul>



<h5 id="3-4-tao-prompt-chuyen-doi-chuyen-biet-cho-tung-loai-file" class="wp-block-heading">3.4 Tạo prompt chuyển đổi chuyên biệt cho từng loại File</h5>



<p><em>Mỗi loại file cần một hướng dẫn riêng để AI convert chính xác.</em></p>



<h6 id="3-4-1-prompt-cho-controller" class="wp-block-heading">3.4.1 Prompt cho Controller</h6>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>nano prompts/controller.txt</code></pre>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>You are converting a Spring Boot REST Controller to a Helidon MP JAX-RS resource.

Rules:
- Replace @RestController with @Path and @ApplicationScoped.
- Replace @GetMapping, @PostMapping, @PutMapping, @DeleteMapping with @GET, @POST, @PUT, @DELETE.
- Replace @Autowired with @Inject (CDI).
- Remove all Spring imports.
- Keep logic unchanged.
- Output FULL source file.</code></pre>



<h6 id="3-4-2-prompt-cho-service" class="wp-block-heading">3.4.2 Prompt cho Service</h6>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>nano prompts/service.txt</code></pre>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>Convert Spring Service into CDI bean for Helidon MP.

Rules:
- Replace @Service with @ApplicationScoped.
- Replace @Autowired with @Inject.
- Remove Spring imports.
- Output full file.</code></pre>



<h6 id="3-4-3-prompt-cho-repository" class="wp-block-heading">3.4.3 Prompt cho Repository </h6>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>nano prompts/repo.txt</code></pre>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>Convert a Spring Data repository to a Helidon MP-compatible JPA repository.

Rules:
- Remove Spring Data interfaces.
- Replace query methods (findBy…) with JPQL.
- Use @ApplicationScoped and @Inject.
- Output full file.</code></pre>



<h6 id="3-4-4-prompt-cho-entity" class="wp-block-heading">3.4.4 <strong>Prompt cho Entity</strong></h6>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>nano prompts/entity.txt</code></pre>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>Convert a Spring JPA Entity into Helidon MP Entity using Jakarta Persistence.

Rules:
- Keep @Entity, @Id, @GeneratedValue.
- Remove Lombok annotations.
- Replace org.springframework imports with jakarta.
- Output full file.</code></pre>



<h6 id="3-4-5-prompt-cho-pom-xml" class="wp-block-heading">3.4.5 Prompt cho pom.xml</h6>



<pre class="wp-block-code has-f-6-f-6-f-4-color has-text-color has-875-rem-font-size"><code>nano prompts/pom.txt</code></pre>



<pre class="wp-block-code"><code>Convert Spring Boot pom.xml to Helidon MP pom.xml.

Replace:
- spring-boot-starter-* with helidon-microprofile modules
- Use jakarta dependencies
Return a valid Maven pom.xml</code></pre>



<h5 id="3-5-tao-script-phan-loai-file-classifier" class="wp-block-heading">3.5 Tạo script phân loại file (classifier)</h5>



<p><strong><em>AI</em></strong> cần biết:<br>→ file nào là Controller?<br>→ Service?<br>→ Repository?<br>→ Entity?</p>



<h6 id="3-5-1-tao-file-classify-py" class="wp-block-heading">3.5.1 Tạo file classify.py</h6>



<pre class="wp-block-code"><code>nano classify.py</code></pre>



<p>Code: </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Python</span><span role="button" tabindex="0" data-code="import os

def classify(path):
    text = open(path).read()

    if path.endswith(&quot;pom.xml&quot;):
        return &quot;pom&quot;

    if &quot;@RestController&quot; in text:
        return &quot;controller&quot;

    if &quot;JpaRepository&quot; in text or &quot;CrudRepository&quot; in text:
        return &quot;repo&quot;

    if &quot;@Service&quot; in text or &quot;@Component&quot; in text:
        return &quot;service&quot;

    if &quot;@Entity&quot; in text:
        return &quot;entity&quot;

    return &quot;java&quot;

if __name__ == &quot;__main__&quot;:
    base_dir = &quot;../spring-petclinic&quot;
    
    for root, dirs, files in os.walk(base_dir):
        for file in files:
            if file.endswith(&quot;.java&quot;) or file.endswith(&quot;pom.xml&quot;):
                path = os.path.join(root, file)
                ftype = classify(path)
                print(f&quot;{path}:{ftype}&quot;)
" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> os</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">def</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">classify</span><span style="color: #F6F6F4">(</span><span style="color: #FFB86C; font-style: italic">path</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">    text </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">open</span><span style="color: #F6F6F4">(path).read()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> path.endswith(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">pom.xml</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">pom</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">@RestController</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">controller</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">JpaRepository</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">CrudRepository</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">repo</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">@Service</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">@Component</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">service</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">@Entity</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> text:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">entity</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">java</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">__name__</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">__main__</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">    base_dir </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">../spring-petclinic</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> root, dirs, files </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> os.walk(base_dir):</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> file </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> files:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> file.endswith(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">.java</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> file.endswith(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">pom.xml</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">                path </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> os.path.join(root, file)</span></span>
<span class="line"><span style="color: #F6F6F4">                ftype </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> classify(path)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">path</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">:</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">ftype</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span></code></pre></div>



<p>Chạy để test:</p>



<pre class="wp-block-code"><code>python3 classify.py</code></pre>



<h5 id="3-6-script-goi-openai-va-chuyen-doi-code" class="wp-block-heading">3.6 Script gọi OpenAI và chuyển đổi code </h5>



<h6 id="3-6-1-export-key" class="wp-block-heading">3.6.1 Export key</h6>



<p>Sau đó: </p>



<pre class="wp-block-code"><code>export OPENAI_API_KEY="YOUR_API_KEY"</code></pre>



<h6 id="3-6-2-tao-convert-py" class="wp-block-heading">3.6.2 Tạo convert.py</h6>



<pre class="wp-block-code"><code>nano convert.py</code></pre>



<p>Code: </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:25.2890625px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Python</span><span role="button" tabindex="0" data-code="import os
from openai import OpenAI
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
import time
from datetime import datetime

# Support multiple API providers via environment variables
# Default to Ollama if available, otherwise use DeepSeek
api_key = (os.getenv(&quot;OPENAI_API_KEY&quot;) or &quot;&quot;).strip()
base_url = (os.getenv(&quot;OPENAI_BASE_URL&quot;) or &quot;&quot;).strip()
model_name = os.getenv(&quot;MODEL_NAME&quot;, &quot;&quot;).strip()

# Auto-detect Ollama if not explicitly set
if not base_url:
    # Check if Ollama is running
    import socket
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        result = sock.connect_ex(('localhost', 11434))
        sock.close()
        if result == 0:
            # Ollama is running, use it as default
            base_url = &quot;http://localhost:11434/v1&quot;
            model_name = model_name or &quot;llama3&quot;
            api_key = api_key or &quot;ollama&quot;
            print(&quot;ℹ Auto-detected Ollama, using it as default&quot;)
    except:
        pass
    
    # Fallback to DeepSeek if Ollama not available
    if not base_url:
        base_url = &quot;https://api.deepseek.com&quot;
        model_name = model_name or &quot;deepseek-chat&quot;
        if not api_key:
            print(&quot;⚠ Warning: Using DeepSeek default. Set OPENAI_API_KEY for Ollama or other providers.&quot;)

if not api_key:
    raise ValueError(&quot;OPENAI_API_KEY environment variable is not set&quot;)

client = OpenAI(
    api_key=api_key,
    base_url=base_url
)

PROMPTS = {
    &quot;controller&quot;: &quot;prompts/controller.txt&quot;,
    &quot;repo&quot;: &quot;prompts/repo.txt&quot;,
    &quot;service&quot;: &quot;prompts/service.txt&quot;,
    &quot;entity&quot;: &quot;prompts/entity.txt&quot;,
    &quot;pom&quot;: &quot;prompts/pom.txt&quot;,
    &quot;java&quot;: &quot;prompts/entity.txt&quot;  # fallback
}

# Thread-safe counters for progress tracking
success_lock = Lock()
error_lock = Lock()
progress_lock = Lock()
success_count = 0
error_count = 0
completed_count = 0
start_time = None

def print_progress(completed, total, current_file=None):
    &quot;&quot;&quot;Print progress with percentage and time estimate&quot;&quot;&quot;
    global start_time
    if total == 0:
        return
    
    percentage = (completed / total) * 100
    elapsed = time.time() - start_time if start_time else 0
    
    if completed &gt; 0 and elapsed &gt; 0:
        avg_time = elapsed / completed
        remaining = total - completed
        eta_seconds = avg_time * remaining
        eta_str = f&quot;ETA: {int(eta_seconds//60)}m {int(eta_seconds%60)}s&quot;
    else:
        eta_str = &quot;Calculating...&quot;
    
    elapsed_str = f&quot;{int(elapsed//60)}m {int(elapsed%60)}s&quot;
    
    bar_length = 30
    filled = int(bar_length * completed / total)
    bar = &quot;█&quot; * filled + &quot;░&quot; * (bar_length - filled)
    
    status = f&quot;[{bar}] {completed}/{total} ({percentage:.1f}%) | {elapsed_str} elapsed | {eta_str}&quot;
    if current_file:
        filename = os.path.basename(current_file)
        status += f&quot; | Processing: {filename}&quot;
    
    print(f&quot;\r{status}&quot;, end=&quot;&quot;, flush=True)


def convert(path, ftype, max_retries=3):
    file_start_time = time.time()
    prompt = open(PROMPTS[ftype]).read()
    code = open(path).read()
    
    # Calculate dynamic timeout based on file size
    # Base timeout: 60s, add 2s per KB of code
    code_size_kb = len(code) / 1024
    base_timeout = 60
    dynamic_timeout = int(base_timeout + (code_size_kb * 2))
    # Cap at 10 minutes for very large files
    timeout = min(dynamic_timeout, 600)

    messages = [
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: prompt},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: code},
    ]

    last_error = None
    for attempt in range(max_retries):
        try:
            # Add small delay between retries
            if attempt &gt; 0:
                wait_time = min(2 ** attempt, 10)  # Exponential backoff, max 10s
                time.sleep(wait_time)
                print(f&quot;  ↻ Retry {attempt}/{max_retries-1} for {os.path.basename(path)}...&quot;)
            
            # Call API (supports multiple providers)
            result = client.chat.completions.create(
                model=model_name,
                temperature=0,
                messages=messages,
                timeout=timeout
            )

            output = result.choices[0].message.content

            # Tạo file output theo cấu trúc map
            out_path = &quot;output/&quot; + path.replace(&quot;../spring-petclinic/&quot;, &quot;&quot;)
            os.makedirs(os.path.dirname(out_path), exist_ok=True)
            with open(out_path, &quot;w&quot;) as f:
                f.write(output)

            elapsed = time.time() - file_start_time
            return True, elapsed
        except Exception as e:
            last_error = e
            error_msg = str(e)
            # If it's not a timeout, don't retry
            if &quot;timeout&quot; not in error_msg.lower() and &quot;timed out&quot; not in error_msg.lower():
                elapsed = time.time() - file_start_time
                return False, elapsed, error_msg
            # For timeout, continue to retry
    
    # All retries failed
    elapsed = time.time() - file_start_time
    return False, elapsed, str(last_error)


def convert_with_counter(path, ftype, total, skip_existing=True):
    global success_count, error_count, completed_count
    
    # Check if output file already exists
    if skip_existing:
        out_path = &quot;output/&quot; + path.replace(&quot;../spring-petclinic/&quot;, &quot;&quot;)
        if os.path.exists(out_path) and os.path.getsize(out_path) &gt; 0:
            with progress_lock:
                completed_count += 1
                success_count += 1
                filename = os.path.basename(path)
                print(f&quot;\n⊘ [{completed_count}/{total}] {filename} (skipped - already exists)&quot;)
                print_progress(completed_count, total)
            return True
    
    result_data = convert(path, ftype)
    
    if len(result_data) == 2:
        success, elapsed = result_data
        error_msg = None
    else:
        success, elapsed, error_msg = result_data
    
    with progress_lock:
        completed_count += 1
        
        if success:
            success_count += 1
            status = &quot;✔&quot;
        else:
            with error_lock:
                error_count += 1
            status = &quot;✗&quot;
        
        # Print detailed result on new line
        filename = os.path.basename(path)
        if success:
            print(f&quot;\n{status} [{completed_count}/{total}] {filename} ({elapsed:.1f}s)&quot;)
        else:
            if error_msg:
                if &quot;402&quot; in error_msg or &quot;Insufficient Balance&quot; in error_msg:
                    print(f&quot;\n{status} [{completed_count}/{total}] {filename} - Insufficient Balance&quot;)
                elif &quot;401&quot; in error_msg or &quot;Unauthorized&quot; in error_msg:
                    print(f&quot;\n{status} [{completed_count}/{total}] {filename} - Invalid API key&quot;)
                elif &quot;timeout&quot; in error_msg.lower():
                    print(f&quot;\n{status} [{completed_count}/{total}] {filename} - Timeout ({elapsed:.1f}s)&quot;)
                else:
                    print(f&quot;\n{status} [{completed_count}/{total}] {filename} - Error: {error_msg[:50]}&quot;)
            else:
                print(f&quot;\n{status} [{completed_count}/{total}] {filename} - Failed&quot;)
        
        # Update progress bar
        print_progress(completed_count, total)
    
    return success


# Cho phép chạy convert thủ công
if __name__ == &quot;__main__&quot;:
    import sys
    
    print(f&quot;Using API: {base_url}&quot;)
    print(f&quot;Using model: {model_name}&quot;)
    print()
    
    if len(sys.argv) == 3:
        result_data = convert(sys.argv[1], sys.argv[2])
        if len(result_data) == 2:
            success, elapsed = result_data
            if success:
                print(f&quot;✔ Converted in {elapsed:.1f}s&quot;)
            else:
                print(f&quot;✗ Failed in {elapsed:.1f}s&quot;)
        else:
            success, elapsed, error_msg = result_data
            print(f&quot;✗ Error: {error_msg}&quot;)
    else:
        success_count = 0
        error_count = 0
        completed_count = 0
        start_time = time.time()
        
        # Parse file list
        tasks = []
        with open(&quot;file-list.txt&quot;, &quot;r&quot;) as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                # Support both formats: &quot;path:type&quot; and &quot;path type&quot;
                if &quot;:&quot; in line:
                    parts = line.split(&quot;:&quot;, 1)
                else:
                    parts = line.rsplit(None, 1)  # Split on last whitespace
                
                if len(parts) == 2:
                    path, ftype = parts
                    path = path.strip()
                    ftype = ftype.strip()
                    tasks.append((path, ftype))
                else:
                    print(f&quot;⚠ Skipped invalid line: {line[:50]}...&quot;)
        
        total = len(tasks)
        # Reduce default workers to avoid overwhelming Ollama
        # Ollama can be slow with too many concurrent requests
        max_workers = int(os.getenv(&quot;MAX_WORKERS&quot;, &quot;2&quot;))
        
        print(f&quot;Found {total} files to convert&quot;)
        print(f&quot;Using {max_workers} parallel workers (reduce if getting timeouts)&quot;)
        print(f&quot;Timeout: 60s base + 2s per KB (max 10min)&quot;)
        print(f&quot;Retries: 3 attempts per file&quot;)
        print(f&quot;Started at {datetime.now().strftime('%H:%M:%S')}\n&quot;)
        
        # Initialize progress bar
        print_progress(0, total)
        
        # Use ThreadPoolExecutor for parallel processing
        # Add small delay between submissions to avoid overwhelming the API
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = []
            for idx, (path, ftype) in enumerate(tasks):
                future = executor.submit(convert_with_counter, path, ftype, total)
                futures.append(future)
                # Small delay to avoid overwhelming Ollama
                if idx &lt; len(tasks) - 1:
                    time.sleep(0.5)
            
            # Wait for all tasks to complete
            for future in as_completed(futures):
                try:
                    future.result()
                except Exception as e:
                    with progress_lock:
                        completed_count += 1
                        error_count += 1
                        print(f&quot;\n✗ Unexpected error: {e}&quot;)
                        print_progress(completed_count, total)
        
        # Final summary
        total_time = time.time() - start_time
        print(f&quot;\n\n{'='*60}&quot;)
        print(f&quot;Summary:&quot;)
        print(f&quot;  ✓ Succeeded: {success_count}&quot;)
        print(f&quot;  ✗ Failed:    {error_count}&quot;)
        print(f&quot;  ⏱ Total time: {int(total_time//60)}m {int(total_time%60)}s&quot;)
        if success_count &gt; 0:
            avg_time = total_time / success_count
            print(f&quot;  📊 Avg time/file: {avg_time:.1f}s&quot;)
        print(f&quot;{'='*60}&quot;)

" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> os</span></span>
<span class="line"><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> openai </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> OpenAI</span></span>
<span class="line"><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> concurrent.futures </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> ThreadPoolExecutor, as_completed</span></span>
<span class="line"><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> threading </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> Lock</span></span>
<span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> time</span></span>
<span class="line"><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> datetime </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> datetime</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B"># Support multiple API providers via environment variables</span></span>
<span class="line"><span style="color: #7B7F8B"># Default to Ollama if available, otherwise use DeepSeek</span></span>
<span class="line"><span style="color: #F6F6F4">api_key </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (os.getenv(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">OPENAI_API_KEY</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">).strip()</span></span>
<span class="line"><span style="color: #F6F6F4">base_url </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (os.getenv(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">OPENAI_BASE_URL</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">).strip()</span></span>
<span class="line"><span style="color: #F6F6F4">model_name </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> os.getenv(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">MODEL_NAME</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">).strip()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B"># Auto-detect Ollama if not explicitly set</span></span>
<span class="line"><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> base_url:</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Check if Ollama is running</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> socket</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">try</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        sock </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> socket.socket(socket.</span><span style="color: #BF9EEE">AF_INET</span><span style="color: #F6F6F4">, socket.</span><span style="color: #BF9EEE">SOCK_STREAM</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        sock.settimeout(</span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        result </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> sock.connect_ex((</span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">localhost</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">, </span><span style="color: #BF9EEE">11434</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">        sock.close()</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> result </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># Ollama is running, use it as default</span></span>
<span class="line"><span style="color: #F6F6F4">            base_url </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">http://localhost:11434/v1</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            model_name </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> model_name </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">llama3</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            api_key </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> api_key </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">ollama</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">ℹ Auto-detected Ollama, using it as default</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">except</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">pass</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Fallback to DeepSeek if Ollama not available</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> base_url:</span></span>
<span class="line"><span style="color: #F6F6F4">        base_url </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">https://api.deepseek.com</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        model_name </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> model_name </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">deepseek-chat</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> api_key:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">⚠ Warning: Using DeepSeek default. Set OPENAI_API_KEY for Ollama or other providers.</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> api_key:</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">raise</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ValueError</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">OPENAI_API_KEY environment variable is not set</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">client </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> OpenAI(</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #FFB86C; font-style: italic">api_key</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">api_key,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #FFB86C; font-style: italic">base_url</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">base_url</span></span>
<span class="line"><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BF9EEE">PROMPTS</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">controller</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/controller.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">repo</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/repo.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">service</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/service.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">entity</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/entity.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">pom</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/pom.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">java</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">prompts/entity.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">  </span><span style="color: #7B7F8B"># fallback</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B"># Thread-safe counters for progress tracking</span></span>
<span class="line"><span style="color: #F6F6F4">success_lock </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> Lock()</span></span>
<span class="line"><span style="color: #F6F6F4">error_lock </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> Lock()</span></span>
<span class="line"><span style="color: #F6F6F4">progress_lock </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> Lock()</span></span>
<span class="line"><span style="color: #F6F6F4">success_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">error_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">completed_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">start_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">None</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">def</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">print_progress</span><span style="color: #F6F6F4">(</span><span style="color: #FFB86C; font-style: italic">completed</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">total</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">current_file</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">None</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B">&quot;&quot;&quot;Print progress with percentage and time estimate&quot;&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">global</span><span style="color: #F6F6F4"> start_time</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> total </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    percentage </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (completed </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> total) </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">100</span></span>
<span class="line"><span style="color: #F6F6F4">    elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time() </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> start_time </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> start_time </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> completed </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">and</span><span style="color: #F6F6F4"> elapsed </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        avg_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> elapsed </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> completed</span></span>
<span class="line"><span style="color: #F6F6F4">        remaining </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> total </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> completed</span></span>
<span class="line"><span style="color: #F6F6F4">        eta_seconds </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> avg_time </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> remaining</span></span>
<span class="line"><span style="color: #F6F6F4">        eta_str </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;ETA: </span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(eta_seconds</span><span style="color: #F286C4">//</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">m </span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(eta_seconds</span><span style="color: #F286C4">%</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        eta_str </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Calculating...</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    elapsed_str </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(elapsed</span><span style="color: #F286C4">//</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">m </span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(elapsed</span><span style="color: #F286C4">%</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    bar_length </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">30</span></span>
<span class="line"><span style="color: #F6F6F4">    filled </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(bar_length </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> completed </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> total)</span></span>
<span class="line"><span style="color: #F6F6F4">    bar </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">█</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> filled </span><span style="color: #F286C4">+</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">░</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> (bar_length </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> filled)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    status </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;[</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">bar</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> (</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">percentage</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">%) | </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">elapsed_str</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> elapsed | </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">eta_str</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> current_file:</span></span>
<span class="line"><span style="color: #F6F6F4">        filename </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> os.path.basename(current_file)</span></span>
<span class="line"><span style="color: #F6F6F4">        status </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot; | Processing: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\r</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">end</span><span style="color: #F286C4">=</span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">flush</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">True</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">def</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">convert</span><span style="color: #F6F6F4">(</span><span style="color: #FFB86C; font-style: italic">path</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">ftype</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">max_retries</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">3</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">    file_start_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time()</span></span>
<span class="line"><span style="color: #F6F6F4">    prompt </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">open</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">PROMPTS</span><span style="color: #F6F6F4">[ftype]).read()</span></span>
<span class="line"><span style="color: #F6F6F4">    code </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">open</span><span style="color: #F6F6F4">(path).read()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Calculate dynamic timeout based on file size</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Base timeout: 60s, add 2s per KB of code</span></span>
<span class="line"><span style="color: #F6F6F4">    code_size_kb </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(code) </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1024</span></span>
<span class="line"><span style="color: #F6F6F4">    base_timeout </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">60</span></span>
<span class="line"><span style="color: #F6F6F4">    dynamic_timeout </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(base_timeout </span><span style="color: #F286C4">+</span><span style="color: #F6F6F4"> (code_size_kb </span><span style="color: #F286C4">*</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Cap at 10 minutes for very large files</span></span>
<span class="line"><span style="color: #F6F6F4">    timeout </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">min</span><span style="color: #F6F6F4">(dynamic_timeout, </span><span style="color: #BF9EEE">600</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    messages </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [</span></span>
<span class="line"><span style="color: #F6F6F4">        {</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">role</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">system</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">content</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: prompt},</span></span>
<span class="line"><span style="color: #F6F6F4">        {</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">role</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">user</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">content</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">: code},</span></span>
<span class="line"><span style="color: #F6F6F4">    ]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    last_error </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">None</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> attempt </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">range</span><span style="color: #F6F6F4">(max_retries):</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">try</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># Add small delay between retries</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> attempt </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                wait_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">min</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">**</span><span style="color: #F6F6F4"> attempt, </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">)  </span><span style="color: #7B7F8B"># Exponential backoff, max 10s</span></span>
<span class="line"><span style="color: #F6F6F4">                time.sleep(wait_time)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;  ↻ Retry </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">attempt</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">max_retries</span><span style="color: #F286C4">-</span><span style="color: #BF9EEE">1}</span><span style="color: #E7EE98"> for </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">os.path.basename(path)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">...&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># Call API (supports multiple providers)</span></span>
<span class="line"><span style="color: #F6F6F4">            result </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> client.chat.completions.create(</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #FFB86C; font-style: italic">model</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">model_name,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #FFB86C; font-style: italic">temperature</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #FFB86C; font-style: italic">messages</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">messages,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #FFB86C; font-style: italic">timeout</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">timeout</span></span>
<span class="line"><span style="color: #F6F6F4">            )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">            output </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> result.choices[</span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">].message.content</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># Tạo file output theo cấu trúc map</span></span>
<span class="line"><span style="color: #F6F6F4">            out_path </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">output/</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">+</span><span style="color: #F6F6F4"> path.replace(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">../spring-petclinic/</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            os.makedirs(os.path.dirname(out_path), </span><span style="color: #FFB86C; font-style: italic">exist_ok</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">True</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">open</span><span style="color: #F6F6F4">(out_path, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">w</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">as</span><span style="color: #F6F6F4"> f:</span></span>
<span class="line"><span style="color: #F6F6F4">                f.write(output)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">            elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time() </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> file_start_time</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">True</span><span style="color: #F6F6F4">, elapsed</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">except</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Exception</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">as</span><span style="color: #F6F6F4"> e:</span></span>
<span class="line"><span style="color: #F6F6F4">            last_error </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> e</span></span>
<span class="line"><span style="color: #F6F6F4">            error_msg </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">str</span><span style="color: #F6F6F4">(e)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># If it&#39;s not a timeout, don&#39;t retry</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">timeout</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg.lower() </span><span style="color: #F286C4">and</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">timed out</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg.lower():</span></span>
<span class="line"><span style="color: #F6F6F4">                elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time() </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> file_start_time</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">False</span><span style="color: #F6F6F4">, elapsed, error_msg</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># For timeout, continue to retry</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># All retries failed</span></span>
<span class="line"><span style="color: #F6F6F4">    elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time() </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> file_start_time</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">False</span><span style="color: #F6F6F4">, elapsed, </span><span style="color: #97E1F1; font-style: italic">str</span><span style="color: #F6F6F4">(last_error)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">def</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">convert_with_counter</span><span style="color: #F6F6F4">(</span><span style="color: #FFB86C; font-style: italic">path</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">ftype</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">total</span><span style="color: #F6F6F4">, </span><span style="color: #FFB86C; font-style: italic">skip_existing</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">True</span><span style="color: #F6F6F4">):</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">global</span><span style="color: #F6F6F4"> success_count, error_count, completed_count</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B"># Check if output file already exists</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> skip_existing:</span></span>
<span class="line"><span style="color: #F6F6F4">        out_path </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">output/</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">+</span><span style="color: #F6F6F4"> path.replace(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">../spring-petclinic/</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> os.path.exists(out_path) </span><span style="color: #F286C4">and</span><span style="color: #F6F6F4"> os.path.getsize(out_path) </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> progress_lock:</span></span>
<span class="line"><span style="color: #F6F6F4">                completed_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">                success_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">                filename </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> os.path.basename(path)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #E7EE98">⊘ [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> (skipped - already exists)&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                print_progress(completed_count, total)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">True</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    result_data </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> convert(path, ftype)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(result_data) </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        success, elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> result_data</span></span>
<span class="line"><span style="color: #F6F6F4">        error_msg </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">None</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        success, elapsed, error_msg </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> result_data</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> progress_lock:</span></span>
<span class="line"><span style="color: #F6F6F4">        completed_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> success:</span></span>
<span class="line"><span style="color: #F6F6F4">            success_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">            status </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">✔</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> error_lock:</span></span>
<span class="line"><span style="color: #F6F6F4">                error_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">            status </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">✗</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Print detailed result on new line</span></span>
<span class="line"><span style="color: #F6F6F4">        filename </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> os.path.basename(path)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> success:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> (</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">elapsed</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s)&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> error_msg:</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">402</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Insufficient Balance</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> - Insufficient Balance&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">elif</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">401</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg </span><span style="color: #F286C4">or</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Unauthorized</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> - Invalid API key&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">elif</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">timeout</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> error_msg.lower():</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> - Timeout (</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">elapsed</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s)&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> - Error: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">error_msg[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">50</span><span style="color: #F6F6F4">]</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">status</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> [</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">completed_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">/</span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">] </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">filename</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> - Failed&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Update progress bar</span></span>
<span class="line"><span style="color: #F6F6F4">        print_progress(completed_count, total)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> success</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B"># Cho phép chạy convert thủ công</span></span>
<span class="line"><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">__name__</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">__main__</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> sys</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Using API: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">base_url</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Using model: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">model_name</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(sys.argv) </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">3</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        result_data </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> convert(sys.argv[</span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">], sys.argv[</span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">])</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(result_data) </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            success, elapsed </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> result_data</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> success:</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;✔ Converted in </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">elapsed</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;✗ Failed in </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">elapsed</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            success, elapsed, error_msg </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> result_data</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;✗ Error: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">error_msg</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        success_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">        error_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">        completed_count </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span></span>
<span class="line"><span style="color: #F6F6F4">        start_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time()</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Parse file list</span></span>
<span class="line"><span style="color: #F6F6F4">        tasks </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> []</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">open</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">file-list.txt</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">r</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">as</span><span style="color: #F6F6F4"> f:</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> line </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> f:</span></span>
<span class="line"><span style="color: #F6F6F4">                line </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> line.strip()</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">not</span><span style="color: #F6F6F4"> line:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #F286C4">continue</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #7B7F8B"># Support both formats: &quot;path:type&quot; and &quot;path type&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">:</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> line:</span></span>
<span class="line"><span style="color: #F6F6F4">                    parts </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> line.split(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">:</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    parts </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> line.rsplit(</span><span style="color: #BF9EEE">None</span><span style="color: #F6F6F4">, </span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">)  </span><span style="color: #7B7F8B"># Split on last whitespace</span></span>
<span class="line"><span style="color: #F6F6F4">                </span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(parts) </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    path, ftype </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> parts</span></span>
<span class="line"><span style="color: #F6F6F4">                    path </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> path.strip()</span></span>
<span class="line"><span style="color: #F6F6F4">                    ftype </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> ftype.strip()</span></span>
<span class="line"><span style="color: #F6F6F4">                    tasks.append((path, ftype))</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;⚠ Skipped invalid line: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">line[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">50</span><span style="color: #F6F6F4">]</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">...&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        total </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(tasks)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Reduce default workers to avoid overwhelming Ollama</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Ollama can be slow with too many concurrent requests</span></span>
<span class="line"><span style="color: #F6F6F4">        max_workers </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(os.getenv(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">MAX_WORKERS</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">2</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Found </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">total</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> files to convert&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Using </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">max_workers</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98"> parallel workers (reduce if getting timeouts)&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Timeout: 60s base + 2s per KB (max 10min)&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Retries: 3 attempts per file&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Started at </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">datetime.now().strftime(</span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">%H:%M:%S</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #F286C4">\n</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Initialize progress bar</span></span>
<span class="line"><span style="color: #F6F6F4">        print_progress(</span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">, total)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Use ThreadPoolExecutor for parallel processing</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Add small delay between submissions to avoid overwhelming the API</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> ThreadPoolExecutor(</span><span style="color: #FFB86C; font-style: italic">max_workers</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">max_workers) </span><span style="color: #F286C4">as</span><span style="color: #F6F6F4"> executor:</span></span>
<span class="line"><span style="color: #F6F6F4">            futures </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> []</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> idx, (path, ftype) </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">enumerate</span><span style="color: #F6F6F4">(tasks):</span></span>
<span class="line"><span style="color: #F6F6F4">                future </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> executor.submit(convert_with_counter, path, ftype, total)</span></span>
<span class="line"><span style="color: #F6F6F4">                futures.append(future)</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #7B7F8B"># Small delay to avoid overwhelming Ollama</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> idx </span><span style="color: #F286C4">&lt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">len</span><span style="color: #F6F6F4">(tasks) </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    time.sleep(</span><span style="color: #BF9EEE">0.5</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #7B7F8B"># Wait for all tasks to complete</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> future </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> as_completed(futures):</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">try</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    future.result()</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">except</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Exception</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">as</span><span style="color: #F6F6F4"> e:</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #F286C4">with</span><span style="color: #F6F6F4"> progress_lock:</span></span>
<span class="line"><span style="color: #F6F6F4">                        completed_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">                        error_count </span><span style="color: #F286C4">+=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n</span><span style="color: #E7EE98">✗ Unexpected error: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">e</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                        print_progress(completed_count, total)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B"># Final summary</span></span>
<span class="line"><span style="color: #F6F6F4">        total_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> time.time() </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4"> start_time</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #F286C4">\n\n</span><span style="color: #BF9EEE">{</span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">=</span><span style="color: #DEE492">&#39;</span><span style="color: #F286C4">*</span><span style="color: #BF9EEE">60}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;Summary:&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;  ✓ Succeeded: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">success_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;  ✗ Failed:    </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">error_count</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;  ⏱ Total time: </span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(total_time</span><span style="color: #F286C4">//</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">m </span><span style="color: #BF9EEE">{</span><span style="color: #97E1F1; font-style: italic">int</span><span style="color: #F6F6F4">(total_time</span><span style="color: #F286C4">%</span><span style="color: #BF9EEE">60</span><span style="color: #F6F6F4">)</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> success_count </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">            avg_time </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> total_time </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> success_count</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;  📊 Avg time/file: </span><span style="color: #BF9EEE">{</span><span style="color: #F6F6F4">avg_time</span><span style="color: #F286C4">:.1f</span><span style="color: #BF9EEE">}</span><span style="color: #E7EE98">s&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">f</span><span style="color: #E7EE98">&quot;</span><span style="color: #BF9EEE">{</span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">=</span><span style="color: #DEE492">&#39;</span><span style="color: #F286C4">*</span><span style="color: #BF9EEE">60}</span><span style="color: #E7EE98">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"></span></code></pre></div>



<h5 id="3-7-chay-chuyen-doi" class="wp-block-heading">3.7 Chạy chuyển đổi</h5>



<h6 id="3-7-1-xuat-danh-sach-file-can-convert" class="wp-block-heading">3.7.1 <strong>Xuất danh sách file cần convert</strong></h6>



<pre class="wp-block-code"><code>python3 classify.py &gt; file-list.txt</code></pre>



<h6 id="3-7-2-chay-convert-tu-dong-toan-bo-project" class="wp-block-heading">3.7.2 Chạy convert tự động toàn bộ project</h6>



<pre class="wp-block-code"><code>python3 convert.py</code></pre>



<p>Kết quả sẽ được lưu ở :</p>



<pre class="wp-block-code"><code>converter/output/</code></pre>



<h5 id="3-8-tao-project-helidon-va-ghep-code" class="wp-block-heading">3.8 <strong><strong>Tạo project helidon và ghép code</strong><br></strong></h5>



<p>Tạo Helidon MP project:</p>



<pre class="wp-block-code"><code>cd ..
mvn archetype:generate -DinteractiveMode=false \
  -DarchetypeGroupId=io.helidon.archetypes \
  -DarchetypeArtifactId=helidon-mp</code></pre>



<p>Sau đó copy code đã chuyển đổi vào project Helidon.</p>



<pre class="wp-block-code"><code>mvn package
java -jar target/*.jar</code></pre>



<h5 id="3-9-ket-qua" class="wp-block-heading">3.9 Kết quả</h5>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="632" src="https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-1024x632.png" alt="" class="wp-image-3714" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-1024x632.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-300x185.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-768x474.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-1536x948.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-2048x1264.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-380x234.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-800x494.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-1160x716.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2025/12/image-scaled.png 2560w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h4 id="4-ket-luan" class="wp-block-heading">4. Kết luận</h4>



<p>AI chắc chắn đang trở thành một phần cốt lõi trong cuộc sống của chúng ta, làm thay đổi cách chúng ta tiếp cận lập trình. Nhiệm vụ chuyển đổi một dự án từ framework này sang framework khác vốn phức tạp giờ đây đã trở nên dễ dàng hơn rất nhiều với sự trợ giúp của AI, giúp tiết kiệm thời gian và tài nguyên, đồng thời mang lại hiệu quả chi phí cao.</p>



<p>Tuy nhiên, các bộ chuyển đổi này không chỉ giới hạn ở việc chuyển đổi Spring sang Helidon. Với bộ gợi ý phù hợp, chúng cũng có thể hỗ trợ việc chuyển đổi và di chuyển các dự án Java EE cũ dựa trên javax.* sang Jakarta EE hiện đại.</p>



<p>Tôi dự định sẽ tiếp tục cải thiện các bộ chuyển đổi. Như thường lệ, tôi hoan nghênh phản hồi của bạn, dù là trong phần bình luận hay trên mạng xã hội.</p>



<p>Dưới đây là các liên kết đến các dự án GitHub được đề cập trong bài viết này:</p>



<ul class="wp-block-list">
<li><a href="https://github.com/m0mus/spring-pets">Spring Pets</a> – Một dự án thử nghiệm được sử dụng để kiểm tra chuyển đổi</li>



<li><a href="https://github.com/m0mus/ai-converter-ctx">Bộ chuyển đổi theo ngữ cảnh</a></li>



<li><a href="https://github.com/m0mus/ai-converter-inc">Bộ chuyển đổi gia tăng</a></li>
</ul>



<p></p>
<p>The post <a href="https://blog.tomosia.com.vn/helidon-la-gi-huong-dan-chuyen-doi-cac-du-an-spring-boot-sang-helidon-bang-ai/">Helidon là gì ? Hướng dẫn chuyển đổi các dự án Spring Boot sang Helidon bằng AI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/helidon-la-gi-huong-dan-chuyen-doi-cac-du-an-spring-boot-sang-helidon-bang-ai/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
