HN 日本語サマリー

← 一覧へ戻る
プログラミング

書いたものを読み上げるフォント

A font that reads what you wrote (rohanadwankar.github.io)

26 pointsby RohanAdwankar14 コメント

要約

semfontは、テキストの感情価、重要度、意外性、確実性といった要素を分析し、それに応じて自動的に色、太さ、ハイライト、傾斜などのタイポグラフィを調整するライブラリです。このライブラリは、特にAIシステムが生成する大量のテキストを読みやすくすることを目的としており、JavaScriptで実装され、Reactコンポーネントとしても利用可能です。より高度な分析のために、文脈を考慮した第二段階の処理も追加されています。

全文翻訳

semfontは、タイポグラフィを自動設定する小さなライブラリです。以下に見られるように、テキストを自動的にハイライト、色付け、太字、斜体にし、読みやすくすることを目的としています。 仕組み 各単語には4つのスコアが付けられます。それぞれ、辞書検索から始まり、周囲の単語に対するいくつかのルールによって調整されます。 感情価(Valence)は、単語が良いか悪いかを示すスコアで、-1から1の範囲です。3単語後までの否定語は、符号を反転させて減衰させます。なぜなら、「あまり良くない」は賞賛の反対ではなく、軽い不満だからです。2単語後までの強調語は、代わりにスケールを調整します。 let v = VALENCE[word] ?? 0; // great -> 0.75 if (negatorWithin(3)) v = -v * 0.74; // not great -> -0.55 v *= gain; // really great -> 0.98 重要度(Salience)は、単語がどれだけ注目に値するかを示すスコアで、0から1の範囲です。頻度リストは各単語に希少性を与えます。英語で最も一般的な100語の1つなら0、見たことのない単語なら1です。希少性だけでは不十分なので、スコアはこのテキストで単語が繰り返される頻度によっても上昇します。つまり、特定の単語を繰り返し言うことは、そのテキストが何について書かれているかを示します。 const seen = Math.min(1, (repeats - 1) / 2); const repetition = 0.45 + 0.55 * seen; let s = SALIENCE[word] ?? 0; s = Math.max(s, 0.55 * rarity * repetition); // rarity('the') 0.00, rarity('kubelet') 0.93 // kubelet said once -> 0.23 // kubelet said 3 times -> 0.51 意外性(Surprise)は、文が転換する場所を示すスコアで、0から1の範囲です。突然(suddenly)や皮肉にも(ironically)のような単語はそれ自体でそれを知らせます。そうでなければ、それは位置から来ます。対比語の後6単語すべてが距離とともに減衰しながらスコアを得ます。また、その区間の他の部分よりもはるかに希少な単語もスコアを得ます。 let s = SURPRISE[word] ?? 0; // suddenly -> 0.85 if (afterContrast) { s = Math.max(s, 0.45 * 0.82 ** (distance - 1)); } s += 0.3 * Math.max(0, rarity - passageMeanRarity - 0.25); // 'The tests failed' -> failed 0.16 // 'It compiled, but the tests failed' -> failed 0.44 確実性(Certainty)は、書き手がどれだけ確信を持っているかを示すスコアで、-1(控えめ)から1(断定的)の範囲です。probablyやdefinitelyのような単語はテーブルにあります。しかし、「ビルドはおそらく失敗した」と書いた場合、あなたはprobablyという単語について不確かなのではなく、失敗したかどうかについて不確かです。そのため、控えめな言葉はその自身のスコアを維持し、文中の他のすべての単語はそのスコアの55%を得て、文全体が中央の1つの単語ではなく、少し傾きます。 c = CERTAINTY[word] ?? sentenceCertainty * 0.55; // 'The build probably failed.' // probably -0.40, every other word -0.22 次に、テーマが各スコアを1つのタイポグラフィ軸にマッピングします。感情価は色、重要度は太さ、意外性はハイライト、確実性は傾斜にマッピングされます。各軸にはしきい値があるため、ほとんどの単語は変更されません。 アルゴリズムの改善 これらのルールは数単語しか見ないため、そのウィンドウには盲点があります。「新しいエディタを素晴らしいと呼ぶほどではない」と書くと、それを打ち消すnotが9単語後ろにあるため、greatは緑色のままになります。「クラッシュを修正した」と書くと、修正した単語とクラッシュの単語がそれぞれ1つずつ緑色になりますが、これは修正した対象とそれを結びつけるものが何もないためです。 そのため、ウィンドウルール後の第二段階の処理が実行され、各節全体を読み取ります。否定語は節の終わりまで到達し、距離とともに減衰するため、greatは赤色に変わります。fixed、recovered、avoidedのような動詞は、それに続くものを改善されたものとしてマークし、crashを緑色に変えます。 同じパスでless brokenやfewer complaintsを改善として読み取り、too simpleを不満として、bad newsの前に lone Greatを皮肉として読み取ります。変更はすべて単語に記録されるため、単語がなぜその色になったのかを尋ねることができます。 analyze('We fixed the crash.').tokens[6]; // { text: 'crash', valence: 0.44, // notes: ['resolved by "fixed"'] } これは最初のパスと同じくらいのコストで、予算内に収まるため、切り替えるスイッチはありません。これらは、それにつながった10の文で、そのうちの2つを含みます。左が最初のバージョン、右が現在です。 beforenow Great, another outage. Just what I needed today.Great, another outage. Just what I needed today. We fixed the crash and closed the security hole before anyone noticed.We fixed the crash and closed the security hole before anyone noticed. The cluster recovered from the crash in under a minute.The cluster recovered from the crash in under a minute. We avoided a catastrophic outage by catching the bug in staging.We avoided a catastrophic outage by catching the bug in staging. I would not go so far as to call the new editor great.I would not go so far as to call the new editor great. Less broken than last week, and far fewer complaints.Less broken than last week, and far fewer complaints. The memory leak is gone.The memory leak is gone. The reviewer called it "terrible", which is wrong.The reviewer called it "terrible", which is wrong. The API is too simple and the docs are too clever.The API is too simple and the docs are too clever. 使用方法 import { SemanticText } from 'semfont'; <SemanticText as="p"> The migration ran clean on staging. In production it deleted the index, and the rollback failed too. </SemanticText> テーマを選択するか、必要なチャネルのみを選択します。 <SemanticText text={incident} theme="monochrome" /> <SemanticText text={incident} channels={['valence']} /> 独自の語彙を教え込みます。 <SemanticText lexicon={{ valence: { flaky: -0.7, oncall: -0.4 }, salience: { rollback: 0.8 } }} text={incident} /> 私がこれを始めたケースです。AIシステムは大量のテキストをストリームしますが、それをすべて読むのは難しいため、この意図は、ほとんどのストリーミングコンポーネントに簡単に組み込んでテキストを読みやすくできるライブラリであるということです。 import { useChat } from '@ai-sdk/react'; import { SemanticText } from 'semfont'; function Chat() { const { messages } = useChat(); return messages.map((m) => { const text = m.parts.filter((p) => p.type === 'text').map((p) => p.text).join(''); return m.role === 'assistant' ? <SemanticText key={m.id} as="p" text={text} /> : <p key={m.id}>{text}</p>; }); } プレーンテキストを送信 semfont またはReactをスキップしてスコアを取得します。 analyzeはエンジン単体で、単語ごとに4つの数値、CSSなしです。これらのインポートはReactなしで機能します。 import { analyze } from 'semfont/analyze'; import { styleFor, themes } from 'semfont/theme'; const { tokens } = analyze('The rollback failed too.'); tokens[4]; // { text: 'failed', valence: -0.7, salience: 0.23, surprise: 0.06, certainty: 0, ... } styleFor(tokens[4], themes.editorial).style; // { color: 'color-mix(in oklab, currentColor, oklch(0.58 0.19 25) 53%)' } 最後の形式は、このページが機能する方法です。バンドラーはないため、1つのimport mapがブラウザにsemfont/analyzeとsemfont/themeがどこにあるかを伝えます。npmのバージョンにピン留めされており、上記のデモボックスはReactコンポーネントと同じ3行です。analyze、style、render。 <script type="importmap"> { "imports": { "semfont/analyze": "https://cdn.jsdelivr.net/npm/semfont@0.2.0/src/analyze.js", "semfont/theme": "https://cdn.jsdelivr.net/npm/semfont@0.2.0/src/theme.js" } } </script> <script type="module"> import { analyze } from 'semfont/analyze'; import { styleFor, themes } from 'semfont/theme'; </script> 次のステップ 実装から推測できるかもしれませんが、ストリーミングを遅くしない速度で完璧な分類を得ることは本質的に不可能でしょう。しかし、テキストを読みやすくするという目的のためには完璧である必要はなく、いくつかの単純なヒューリスティクスが十分な効果を発揮するかもしれません。とはいえ、皮肉のような扱いに興味深いケースや、埋め込み節を持つ否定のような、解析可能なケースがいくつかあります。さらに、ストリーミングコーディングエージェントのために、語彙に含める価値のある技術用語があります。 コードとデモはgithub.com/RohanAdwankar/semfontにあります。