<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4</id>
	<title>개인 PC 텍스트 검색 챗봇 웹 화면 - 편집 역사</title>
	<link rel="self" type="application/atom+xml" href="https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4"/>
	<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;action=history"/>
	<updated>2026-05-17T11:23:30Z</updated>
	<subtitle>이 문서의 편집 역사</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1900&amp;oldid=prev</id>
		<title>Devcafe: /* 전체 코드 */</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1900&amp;oldid=prev"/>
		<updated>2025-06-25T16:02:49Z</updated>

		<summary type="html">&lt;p&gt;&lt;span dir=&quot;auto&quot;&gt;&lt;span class=&quot;autocomment&quot;&gt;전체 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ko&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← 이전 판&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2025년 6월 26일 (목) 01:02 판&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l23&quot;&gt;23번째 줄:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;23번째 줄:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;import tempfile&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;import tempfile&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;import re&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;import re&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;# client = chromadb.Client()&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;# 문단 분리 (빈 줄 기준)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;# 문단 분리 (빈 줄 기준)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l60&quot;&gt;60번째 줄:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;62번째 줄:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;def build_vector_db(doc_data):&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;def build_vector_db(doc_data):&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     model = SentenceTransformer(&amp;quot;paraphrase-MiniLM-L6-v2&amp;quot;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     model = SentenceTransformer(&amp;quot;paraphrase-MiniLM-L6-v2&amp;quot;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     chroma_client = chromadb.Client(Settings(chroma_db_impl=&quot;mem&quot;))&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;# &lt;/ins&gt;chroma_client = chromadb.Client(Settings(chroma_db_impl=&quot;mem&quot;)&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;)&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;    chroma_client = chromadb.Client()&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;    # client = chromadb.PersistentClient(path=&quot;E:\\DEV\\streamlit\\&quot;&lt;/ins&gt;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     collection = chroma_client.create_collection(name=&amp;quot;paragraphs&amp;quot;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     collection = chroma_client.create_collection(name=&amp;quot;paragraphs&amp;quot;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     paragraphs = [d[&amp;quot;paragraph&amp;quot;] for d in doc_data]&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;     paragraphs = [d[&amp;quot;paragraph&amp;quot;] for d in doc_data]&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1862&amp;oldid=prev</id>
		<title>2025년 6월 24일 (화) 03:29에 Devcafe님의 편집</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1862&amp;oldid=prev"/>
		<updated>2025-06-24T03:29:55Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ko&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← 이전 판&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2025년 6월 24일 (화) 12:29 판&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l216&quot;&gt;216번째 줄:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;216번째 줄:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;심층 검색, 답변 요약, 다중 파일 업로드 병행, 기타 UI 커스터마이징 등 추가 확장 검토&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;심층 검색, 답변 요약, 다중 파일 업로드 병행, 기타 UI 커스터마이징 등 추가 확장 검토&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;[[category:python]]&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;[[category:chatbot]]&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1830&amp;oldid=prev</id>
		<title>2025년 6월 23일 (월) 16:49에 Devcafe님의 편집</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1830&amp;oldid=prev"/>
		<updated>2025-06-23T16:49:51Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ko&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← 이전 판&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2025년 6월 24일 (화) 01:49 판&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;1번째 줄:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;1번째 줄:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;알겠습니다. 요청하신 내용을 반영하여  &lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;1. **문단(Paragraph) 단위로 텍스트 파일을 섹션 분할 및 임베딩/검색**  &lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;2. **더 강화된 확장 UI (파일 업로드, 검색 결과 하이라이트, 검색 범위 설명, 대화 이력 다운로드 등)**  &lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;기능이 적용된 Streamlit 예제 코드를 미디어위키 양식으로 정리해 드립니다.&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;---&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= Streamlit 기반 문단 단위 검색 및 확장 UI 챗봇 =&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= Streamlit 기반 문단 단위 검색 및 확장 UI 챗봇 =&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1829&amp;oldid=prev</id>
		<title>Devcafe: 새 문서:  알겠습니다. 요청하신 내용을 반영하여   1. **문단(Paragraph) 단위로 텍스트 파일을 섹션 분할 및 임베딩/검색**   2. **더 강화된 확장 UI (파일 업로드, 검색 결과 하이라이트, 검색 범위 설명, 대화 이력 다운로드 등)**   기능이 적용된 Streamlit 예제 코드를 미디어위키 양식으로 정리해 드립니다.  ---  = Streamlit 기반 문단 단위 검색 및 확장 UI 챗봇 =  == 주요 기능 == * 텍스...</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B0%9C%EC%9D%B8_PC_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EA%B2%80%EC%83%89_%EC%B1%97%EB%B4%87_%EC%9B%B9_%ED%99%94%EB%A9%B4&amp;diff=1829&amp;oldid=prev"/>
		<updated>2025-06-23T16:48:28Z</updated>

		<summary type="html">&lt;p&gt;새 문서:  알겠습니다. 요청하신 내용을 반영하여   1. **문단(Paragraph) 단위로 텍스트 파일을 섹션 분할 및 임베딩/검색**   2. **더 강화된 확장 UI (파일 업로드, 검색 결과 하이라이트, 검색 범위 설명, 대화 이력 다운로드 등)**   기능이 적용된 Streamlit 예제 코드를 미디어위키 양식으로 정리해 드립니다.  ---  = Streamlit 기반 문단 단위 검색 및 확장 UI 챗봇 =  == 주요 기능 == * 텍스...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;새 문서&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;
알겠습니다. 요청하신 내용을 반영하여  &lt;br /&gt;
1. **문단(Paragraph) 단위로 텍스트 파일을 섹션 분할 및 임베딩/검색**  &lt;br /&gt;
2. **더 강화된 확장 UI (파일 업로드, 검색 결과 하이라이트, 검색 범위 설명, 대화 이력 다운로드 등)**  &lt;br /&gt;
기능이 적용된 Streamlit 예제 코드를 미디어위키 양식으로 정리해 드립니다.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
= Streamlit 기반 문단 단위 검색 및 확장 UI 챗봇 =&lt;br /&gt;
&lt;br /&gt;
== 주요 기능 ==&lt;br /&gt;
* 텍스트 문서를 문단(빈 줄 기준)으로 분할하여 정교한 검색&lt;br /&gt;
* 파일 업로드 UI 지원 (업로드 또는 폴더 선택 중 선택)&lt;br /&gt;
* 챗봇 대화 이력 다운로드&lt;br /&gt;
* 검색 결과 내 문단/문서 정보, 유사도, 하이라이트 표시&lt;br /&gt;
* 검색 범위 안내/설명&lt;br /&gt;
* 다크모드에 친화적인 UI 요소&lt;br /&gt;
&lt;br /&gt;
== 필수 패키지 설치 ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install streamlit sentence-transformers chromadb&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 전체 코드 ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import streamlit as st&lt;br /&gt;
from sentence_transformers import SentenceTransformer&lt;br /&gt;
import chromadb&lt;br /&gt;
from chromadb.config import Settings&lt;br /&gt;
import tempfile&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
# 문단 분리 (빈 줄 기준)&lt;br /&gt;
def split_paragraphs(text):&lt;br /&gt;
    paragraphs = re.split(r&amp;#039;\n\s*\n&amp;#039;, text.strip())&lt;br /&gt;
    return [para.strip().replace(&amp;#039;\n&amp;#039;, &amp;#039; &amp;#039;) for para in paragraphs if para.strip()]&lt;br /&gt;
&lt;br /&gt;
# 파일 로딩 및 문단 분할&lt;br /&gt;
def load_docs_and_paragraphs(file_paths):&lt;br /&gt;
    doc_data = []&lt;br /&gt;
    for path in file_paths:&lt;br /&gt;
        with open(path, &amp;#039;r&amp;#039;, encoding=&amp;#039;utf-8&amp;#039;) as f:&lt;br /&gt;
            text = f.read()&lt;br /&gt;
            paragraphs = split_paragraphs(text)&lt;br /&gt;
            file_name = os.path.basename(path)&lt;br /&gt;
            for i, p in enumerate(paragraphs):&lt;br /&gt;
                doc_data.append({&lt;br /&gt;
                    &amp;#039;file&amp;#039;: file_name,&lt;br /&gt;
                    &amp;#039;paragraph&amp;#039;: p,&lt;br /&gt;
                    &amp;#039;idx&amp;#039;: i&lt;br /&gt;
                })&lt;br /&gt;
    return doc_data&lt;br /&gt;
&lt;br /&gt;
# 업로드 파일 임시저장&lt;br /&gt;
def save_uploaded_files(uploaded_files):&lt;br /&gt;
    tmp_dir = tempfile.mkdtemp()&lt;br /&gt;
    file_paths = []&lt;br /&gt;
    for file in uploaded_files:&lt;br /&gt;
        path = os.path.join(tmp_dir, file.name)&lt;br /&gt;
        with open(path, &amp;#039;wb&amp;#039;) as out:&lt;br /&gt;
            out.write(file.read())&lt;br /&gt;
        file_paths.append(path)&lt;br /&gt;
    return file_paths&lt;br /&gt;
&lt;br /&gt;
# 벡터 임베딩/DB 구축 (문단 단위)&lt;br /&gt;
@st.cache_resource&lt;br /&gt;
def build_vector_db(doc_data):&lt;br /&gt;
    model = SentenceTransformer(&amp;quot;paraphrase-MiniLM-L6-v2&amp;quot;)&lt;br /&gt;
    chroma_client = chromadb.Client(Settings(chroma_db_impl=&amp;quot;mem&amp;quot;))&lt;br /&gt;
    collection = chroma_client.create_collection(name=&amp;quot;paragraphs&amp;quot;)&lt;br /&gt;
    paragraphs = [d[&amp;quot;paragraph&amp;quot;] for d in doc_data]&lt;br /&gt;
    metadatas = [{&amp;quot;file&amp;quot;: d[&amp;quot;file&amp;quot;], &amp;quot;paragraph_idx&amp;quot;: d[&amp;quot;idx&amp;quot;]} for d in doc_data]&lt;br /&gt;
    ids = [f&amp;quot;{d[&amp;#039;file&amp;#039;]}#{d[&amp;#039;idx&amp;#039;]}&amp;quot; for d in doc_data]&lt;br /&gt;
    if paragraphs:&lt;br /&gt;
        embeddings = model.encode(paragraphs, show_progress_bar=True).tolist()&lt;br /&gt;
        collection.add(&lt;br /&gt;
            documents=paragraphs,&lt;br /&gt;
            metadatas=metadatas,&lt;br /&gt;
            ids=ids,&lt;br /&gt;
            embeddings=embeddings&lt;br /&gt;
        )&lt;br /&gt;
    else:&lt;br /&gt;
        embeddings = []&lt;br /&gt;
    return collection, model&lt;br /&gt;
&lt;br /&gt;
# 검색 및 하이라이트&lt;br /&gt;
def search_paragraphs(user_query, collection, model, top_k):&lt;br /&gt;
    query_emb = model.encode([user_query]).tolist()[0]&lt;br /&gt;
    results = collection.query(query_embeddings=[query_emb], n_results=top_k)&lt;br /&gt;
    response = []&lt;br /&gt;
    for i in range(len(results[&amp;quot;ids&amp;quot;][0])):&lt;br /&gt;
        para = results[&amp;quot;documents&amp;quot;][0][i]&lt;br /&gt;
        meta = results[&amp;quot;metadatas&amp;quot;][0][i]&lt;br /&gt;
        score = results[&amp;quot;distances&amp;quot;][0][i]&lt;br /&gt;
        file = meta[&amp;quot;file&amp;quot;]&lt;br /&gt;
        idx = meta[&amp;quot;paragraph_idx&amp;quot;]&lt;br /&gt;
        response.append((file, idx, para, score))&lt;br /&gt;
    return response&lt;br /&gt;
&lt;br /&gt;
# 하이라이트 (검색어 주요 단어만)&lt;br /&gt;
def highlight_keywords(text, query):&lt;br /&gt;
    # 주요 단어 추출(간단 버전)&lt;br /&gt;
    key_terms = [w for w in re.findall(r&amp;#039;\w+&amp;#039;, query) if len(w) &amp;gt; 1]&lt;br /&gt;
    for t in set(key_terms):&lt;br /&gt;
        text = re.sub(f&amp;quot;({re.escape(t)})&amp;quot;, r&amp;quot;&amp;lt;mark&amp;gt;\1&amp;lt;/mark&amp;gt;&amp;quot;, text, flags=re.IGNORECASE)&lt;br /&gt;
    return text&lt;br /&gt;
&lt;br /&gt;
# 대화 이력 다운로드용 텍스트 변환&lt;br /&gt;
def chat_history_to_text(chat_history):&lt;br /&gt;
    log = &amp;quot;&amp;quot;&lt;br /&gt;
    for q, resparas in chat_history:&lt;br /&gt;
        log += f&amp;quot;[질문]\n{q}\n&amp;quot;&lt;br /&gt;
        for idx, (fname, para_idx, answer, sim) in enumerate(resparas):&lt;br /&gt;
            log += f&amp;quot;- ({fname}) {para_idx+1}번째 문단, 유사도: {round(1-sim, 3)}\n{answer}\n&amp;quot;&lt;br /&gt;
        log += &amp;quot;\n&amp;quot;&lt;br /&gt;
    return log&lt;br /&gt;
&lt;br /&gt;
# UI 시작&lt;br /&gt;
st.set_page_config(page_title=&amp;quot;문단 기반 챗봇&amp;quot;, layout=&amp;quot;wide&amp;quot;)&lt;br /&gt;
st.title(&amp;quot;📚 문단 단위 검색 &amp;amp; 확장UI 챗봇&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# --- 검색 데이터 소스 선택 UI ---&lt;br /&gt;
with st.sidebar:&lt;br /&gt;
    st.header(&amp;quot;데이터 업로드/폴더 선택&amp;quot;)&lt;br /&gt;
    method = st.radio(&amp;quot;데이터 소스&amp;quot;, [&amp;#039;폴더 경로&amp;#039;, &amp;#039;파일 업로드&amp;#039;])&lt;br /&gt;
    file_paths = []&lt;br /&gt;
    if method == &amp;#039;폴더 경로&amp;#039;:&lt;br /&gt;
        folder = st.text_input(&amp;quot;텍스트 파일 폴더 경로&amp;quot;, &amp;quot;./textfiles&amp;quot;)&lt;br /&gt;
        if folder and os.path.exists(folder):&lt;br /&gt;
            file_paths = [os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(&amp;#039;.txt&amp;#039;)]&lt;br /&gt;
        else:&lt;br /&gt;
            st.warning(&amp;quot;유효한 폴더 경로를 입력하세요.&amp;quot;)&lt;br /&gt;
    else:&lt;br /&gt;
        uploaded_files = st.file_uploader(&amp;quot;txt 파일 1개 이상 업로드&amp;quot;, type=[&amp;#039;txt&amp;#039;], accept_multiple_files=True)&lt;br /&gt;
        if uploaded_files:&lt;br /&gt;
            file_paths = save_uploaded_files(uploaded_files)&lt;br /&gt;
&lt;br /&gt;
    st.markdown(&amp;quot;---&amp;quot;)&lt;br /&gt;
    st.info(&amp;quot;문단은 txt 파일 내에서 빈 줄(Enter 2번)로 구분됩니다.\n문단 단위로 검색이 이뤄집니다.\n검색 결과 하이라이트 제공.&amp;quot;)&lt;br /&gt;
    top_k = st.slider(&amp;quot;검색결과 개수(top_k)&amp;quot;, 1, 10, 3)&lt;br /&gt;
    st.caption(&amp;quot;챗봇이 보여주는 답변 개수 조절&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# 데이터 로딩 및 벡터화&lt;br /&gt;
if not file_paths:&lt;br /&gt;
    st.warning(&amp;quot;🛑 사용할 .txt 파일이 없습니다. 좌측에서 파일을 올리거나 경로를 선택해 주세요.&amp;quot;)&lt;br /&gt;
    st.stop()&lt;br /&gt;
&lt;br /&gt;
# 문단 데이터 준비&lt;br /&gt;
with st.spinner(&amp;quot;문단 분할/벡터 임베딩 중...&amp;quot;):&lt;br /&gt;
    doc_data = load_docs_and_paragraphs(file_paths)&lt;br /&gt;
    if not doc_data:&lt;br /&gt;
        st.error(&amp;quot;문단 데이터가 없습니다. 파일 내용을 확인하세요.&amp;quot;)&lt;br /&gt;
        st.stop()&lt;br /&gt;
    collection, model = build_vector_db(doc_data)&lt;br /&gt;
&lt;br /&gt;
# 컬럼 UI&lt;br /&gt;
col1, col2 = st.columns([1,2])&lt;br /&gt;
&lt;br /&gt;
# 왼쪽 : 문서 리스트 및 정보&lt;br /&gt;
with col1:&lt;br /&gt;
    st.subheader(&amp;quot;📄 문서/문단 요약&amp;quot;)&lt;br /&gt;
    doc_set = set([d[&amp;#039;file&amp;#039;] for d in doc_data])&lt;br /&gt;
    st.markdown(f&amp;quot;- **문서 수:** {len(doc_set)}개&amp;quot;)&lt;br /&gt;
    st.markdown(f&amp;quot;- **전체 문단 수:** {len(doc_data)}개&amp;quot;)&lt;br /&gt;
    fselect = st.selectbox(&amp;quot;문서별 보기&amp;quot;, [&amp;quot;전체 보기&amp;quot;] + sorted(doc_set))&lt;br /&gt;
    # 원하는 문서만 필터링&lt;br /&gt;
    filtered_data = doc_data if fselect==&amp;quot;전체 보기&amp;quot; else [d for d in doc_data if d[&amp;#039;file&amp;#039;]==fselect]&lt;br /&gt;
    for d in filtered_data[:20]:  # 최대 20개만 미리보기&lt;br /&gt;
        st.markdown(f&amp;quot;&amp;lt;div style=&amp;#039;margin-bottom:8px;&amp;#039;&amp;gt;&amp;lt;span style=&amp;#039;color:#888&amp;#039;&amp;gt;[{d[&amp;#039;file&amp;#039;]},{d[&amp;#039;idx&amp;#039;]+1}번문단]&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;&amp;lt;span style=&amp;#039;font-size:13.5px;color:#444&amp;#039;&amp;gt;{d[&amp;#039;paragraph&amp;#039;][:110]}{&amp;#039;...&amp;#039; if len(d[&amp;#039;paragraph&amp;#039;])&amp;gt;110 else &amp;#039;&amp;#039;}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;quot;, unsafe_allow_html=True)&lt;br /&gt;
    if len(filtered_data) &amp;gt; 20:&lt;br /&gt;
        st.caption(&amp;quot;※ 문단이 많아 미리보기는 20건까지만 표시합니다.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# 오른쪽 : 챗봇 인터페이스, 답변, 대화 이력&lt;br /&gt;
with col2:&lt;br /&gt;
    st.subheader(&amp;quot;💬 검색질문 &amp;amp; 문단 단위 답변&amp;quot;)&lt;br /&gt;
    if &amp;quot;chat_history&amp;quot; not in st.session_state:&lt;br /&gt;
        st.session_state.chat_history = []&lt;br /&gt;
    with st.form(key=&amp;quot;ask_form&amp;quot;, clear_on_submit=True):&lt;br /&gt;
        user_query = st.text_area(&amp;quot;당신의 질문을 입력하세요&amp;quot;)&lt;br /&gt;
        submitted = st.form_submit_button(&amp;quot;질문 및 검색&amp;quot;)&lt;br /&gt;
    if submitted and user_query.strip():&lt;br /&gt;
        answers = search_paragraphs(user_query, collection, model, top_k=top_k)&lt;br /&gt;
        st.session_state.chat_history.append((user_query, answers))&lt;br /&gt;
&lt;br /&gt;
    if st.session_state.chat_history:&lt;br /&gt;
        st.markdown(&amp;quot;---&amp;quot;)&lt;br /&gt;
        for question, resparas in reversed(st.session_state.chat_history[-6:]):  # 최근 6개만&lt;br /&gt;
            st.markdown(f&amp;quot;&amp;lt;div style=&amp;#039;background:#eaf7ff;border-radius:8px;padding:8px 10px 4px 10px;margin-bottom:8px;&amp;#039;&amp;gt;&amp;lt;b&amp;gt;🙋 질문:&amp;lt;/b&amp;gt; {question}&amp;lt;/div&amp;gt;&amp;quot;, unsafe_allow_html=True)&lt;br /&gt;
            for idx, (fname, para_idx, answer, sim) in enumerate(resparas):&lt;br /&gt;
                # 하이라이트 적용&lt;br /&gt;
                highlighted = highlight_keywords(answer, question)&lt;br /&gt;
                st.markdown(&lt;br /&gt;
                   f&amp;quot;&amp;lt;div style=&amp;#039;background:#f8faff;border-radius:6px;padding:10px;margin-bottom:6px;&amp;#039;&amp;gt;&amp;quot;&lt;br /&gt;
                   f&amp;quot;&amp;lt;b&amp;gt;🔎 관련문단 {idx+1}:&amp;lt;/b&amp;gt; &amp;lt;span style=&amp;#039;color:#2a7af3&amp;#039;&amp;gt;{fname}&amp;lt;/span&amp;gt; &amp;quot;&lt;br /&gt;
                   f&amp;quot;&amp;lt;span style=&amp;#039;color:#888&amp;#039;&amp;gt;{para_idx+1}번문단&amp;lt;/span&amp;gt; (유사도: &amp;lt;b&amp;gt;{round(1-sim,3)}&amp;lt;/b&amp;gt;)&amp;quot;&lt;br /&gt;
                   f&amp;quot;&amp;lt;br&amp;gt;&amp;lt;div style=&amp;#039;max-height:120px;overflow:auto;font-size:15px&amp;#039;&amp;gt;{highlighted} ...&amp;lt;/div&amp;gt;&amp;quot;&lt;br /&gt;
                   f&amp;quot;&amp;lt;/div&amp;gt;&amp;quot;, unsafe_allow_html=True)&lt;br /&gt;
&lt;br /&gt;
        with st.expander(&amp;quot;⬇️ 대화 이력 다운로드&amp;quot;):&lt;br /&gt;
            chat_log = chat_history_to_text(st.session_state.chat_history)&lt;br /&gt;
            st.download_button(&amp;quot;대화로그 TXT 내려받기&amp;quot;, data=chat_log, file_name=&amp;quot;chatbot_log.txt&amp;quot;)&lt;br /&gt;
    else:&lt;br /&gt;
        st.info(&amp;quot;질문을 입력하면 문단별로 가장 유사한 답변을 찾아줍니다.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
st.caption(&amp;quot;ⓒ 예시 코드. 문의/기능 개선 요청 환영합니다.&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 추가 설명 ==&lt;br /&gt;
* 업로드 또는 폴더선택 중 하나만 사용&lt;br /&gt;
* 문단 단위 검색으로 Q&amp;amp;A 품질이 향상됨&lt;br /&gt;
* 결과에 검색 질의어와 매칭되는 부분이 하이라이트됨  &lt;br /&gt;
* 대화 이력 다운로드 버튼으로 전체 세션의 Q&amp;amp;A 저장 가능&lt;br /&gt;
* 검색결과 수(top_k) 슬라이더로 조절&lt;br /&gt;
&lt;br /&gt;
== 실행 방법 ==&lt;br /&gt;
1. 위 코드를 예시로 `paragraphchat_ui.py`로 저장&lt;br /&gt;
2. 터미널에서 실행  &lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
streamlit run paragraphchat_ui.py&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
심층 검색, 답변 요약, 다중 파일 업로드 병행, 기타 UI 커스터마이징 등 추가 확장 검토&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
</feed>