pythonでのコピーについても追記しました!(2024.08.04)
やりたいこと
streamlit上で、AI生成したテキストをクリップボードにコピーできるボタンを設置したい!
クリップボードにコピーする処理だけ調べました。
Pythonを使う方法と、Javascrptを使う方法があります。
Pythonを使ったシンプルなテキストコピー
pyperclip
というモジュールを使うと良い。
pip install pyperclip
import pyperclip
text = "クリップボードにコピーしたいテキスト"
if st.button("コピー"):
pyperclip.copy(text)
簡単
しかし、問題が。
streamlitではボタンがクリックされるとアプリケーション全体を再実行されてしまうので、画面が再読み込みされてしまう。
「表示されている生成した文章をコピー」という要件的に、コピーボタンを押すと生成した文章が表示されなくなってしまうのは不便。
Javascriptを使ったシンプルなテキストコピー
ということで、今回はJavascriptを使ったテキストコピーを採用します。
前回の記事でcomponents.htmlを使えばJavascriptを動かすことができることは学習済み!
import streamlit as st
import streamlit.components.v1 as stc
# コピーするテキスト
copy_text = "これ、クリップボードにコピーされるよ!"
# HTMLテンプレート
html_content = f"""
<button onclick="navigator.clipboard.writeText('{copy_text}')">
コピる!
</button>
"""
# 表示
stc.html(html_content, height=50)
st.write(copy_text)
録画ではtailwind効かせてるけど、画面はこんな感じ。やったー!
Langchainで生成したテキストをコピー
次は、Langchainで生成したテキストをコピーできるように。
今回のコピーボタンはtailwind使ってちょっとリッチにしてみた
onclick
内の処理を実は少し変えてある。どこかな〜
# 細かな初期化作業や生成部分は省略
generate_txt = chat([
HumanMessage(content=prompt.format(article=generate_article.content))
])
# コピーするテキストを定義
copy_text = generate_txt.content
# HTMLテンプレートを作成
html_content = f"""
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="inline-flex border border-gray-200 rounded-full p-0.5 dark:border-neutral-700">
<button onclick='navigator.clipboard.writeText({json.dumps(copy_text)})' type="button" class="inline-flex shrink-0 justify-center items-center size-8 rounded-full text-gray-500 hover:bg-blue-100 hover:text-blue-800 focus:outline-none focus:bg-blue-100 focus:text-blue-800 dark:text-neutral-500 dark:hover:bg-blue-900 dark:hover:text-blue-200 dark:focus:bg-blue-900 dark:focus:text-blue-200">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-copy"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z" /><path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" /></svg>
</button>
</div>
</body>
</html>
"""
# HTMLコンテンツを表示
stc.html(html_content,height=40)
st.write(generated_text.content)
変えたところ
json.dumps()
の処理を追加しました。なぜこの変更が必要だったのか、説明していきます。
つまづきポイント
Langchainで生成したテキストを単純にJavaScriptに渡そうとしたら、うまくいきませんでした。
エラーメッセージ:
SyntaxError: Invalid or unexpected token (at VM616 about:srcdoc:1:31)
注目すべきは、pyperclip
を使用した場合は問題なくコピーできたという点です。これは、Pythonの世界内でのコピーだからです。
解決策:json.dumps()
の使用
JavaScriptのnavigator.clipboard.writeText()
にPythonの変数を渡す際、json.dumps()
を使用する必要があります。
onclick='navigator.clipboard.writeText({json.dumps(copy_text)})'
なぜjson.dumps()
が必要なのか?
-
文字列のエスケープ: Pythonの文字列には、JavaScriptが特別な意味を持つ文字(例:
'
,"
、\n
)が含まれている可能性があります。json.dumps()
はこれらの文字を適切にエスケープします。 -
Unicode処理:
json.dumps()
は非ASCII文字を適切にエンコードします。
例えば:
text = "Hello\nWorld"
print(json.dumps(text)) # 出力: "Hello\\nWorld"
この処理により、Pythonの文字列(改行や特殊文字を含む)がJavaScriptで安全に使用できる形に変換される!
この方法を使えば、Langchainで生成した複雑なテキストでも、問題なくクリップボードにコピーできるようになりました!
まとめ
1. Pythonでコピーするなら
-
pyperclip
モジュール使えば簡単だよ! - でも、Streamlitだとボタン押すたびにページ再読み込みしちゃう
- 動的に生成したテキストをコピーしたいときは使いにくいかも
2. JavaScriptでコピーするなら
- Streamlitの
components.html
使ってJavaScript埋め込める - ページ再読み込みなしでコピーできるから便利!
- 動的なコンテンツのコピーによき
テキストの種類で違う
- 普通のテキスト: そのままJavaScriptに文字列渡せばOK
- Langchainとかで生成した複雑なテキスト:
json.dumps()
使おう
3. かっこよくしたいなら
- TailwindCSSあると、簡単にいい感じのボタン作れる!
4. どっち使う?
- 静的なテキストで再読み込みしても大丈夫なら: Python(
pyperclip
) - 動的なテキストやUI反応良くしたいなら: JavaScript
これで、streamlit上にいい感じのコピーボタンが設置できたよ!AIが生成したテキストも簡単にコピーできるし結構使いやすくなったはず。