The stored `colors` key names a palette — rhema (the default, Jadeite), aquamarine, black-and-white, or dracula — and the Appearance page of /settings cycles it live, like the theme and icon toggles beside it. Each palette carries a dark and a light appearance under the existing `theme` key; dracula's light side is derived, the official palette ships only dark. The appearance decision moves off token comparison: Theme now answers is_dark() from the ground/text luminance, so palettes beyond the original two get the right per-profile fallback tones.
37 lines
1.2 KiB
Vue
37 lines
1.2 KiB
Vue
<script setup lang="ts">
|
|
// Agent replies are markdown; this mounts the rendered HTML. The parser
|
|
// keeps raw HTML in the source escaped (html stays false) and refuses
|
|
// javascript: link targets, so v-html only ever receives generated
|
|
// markup.
|
|
import MarkdownIt from 'markdown-it'
|
|
import type { RendererRule } from 'markdown-it'
|
|
|
|
const props = defineProps<{
|
|
text: string
|
|
/** While true, a blinking cursor trails the last rendered block. */
|
|
streaming?: boolean
|
|
}>()
|
|
|
|
const md = new MarkdownIt({ html: false, linkify: true })
|
|
|
|
// Rendered links leave the dashboard; open them in a new tab so the
|
|
// transcript and its event stream survive the click.
|
|
const defaultLinkOpen: RendererRule =
|
|
md.renderer.rules.link_open ??
|
|
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options))
|
|
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
|
const token = tokens[idx]
|
|
if (token) {
|
|
token.attrSet('target', '_blank')
|
|
token.attrSet('rel', 'noopener noreferrer')
|
|
}
|
|
return defaultLinkOpen(tokens, idx, options, env, self)
|
|
}
|
|
|
|
const html = computed(() => md.render(props.text))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="md" :class="{ streaming }" v-html="html" />
|
|
</template>
|