mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
feat(cognition): add ContextWorkspace to manage context blocks
This commit is contained in:
@@ -11,6 +11,8 @@ public interface CognitionCapability {
|
|||||||
|
|
||||||
String initiateTurn(String input, String target);
|
String initiateTurn(String input, String target);
|
||||||
|
|
||||||
|
ContextWorkspace contextWorkspace();
|
||||||
|
|
||||||
List<Message> getChatMessages();
|
List<Message> getChatMessages();
|
||||||
|
|
||||||
List<Message> snapshotChatMessages();
|
List<Message> snapshotChatMessages();
|
||||||
|
|||||||
@@ -35,9 +35,16 @@ public class CognitionCore extends PartnerCore<CognitionCore> {
|
|||||||
*/
|
*/
|
||||||
private List<Message> chatMessages = new ArrayList<>();
|
private List<Message> chatMessages = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ContextWorkspace contextWorkspace = new ContextWorkspace();
|
||||||
|
|
||||||
public CognitionCore() throws IOException, ClassNotFoundException {
|
public CognitionCore() throws IOException, ClassNotFoundException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CapabilityMethod
|
||||||
|
public ContextWorkspace contextWorkspace() {
|
||||||
|
return contextWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
@CapabilityMethod
|
@CapabilityMethod
|
||||||
public String initiateTurn(String input, String target) {
|
public String initiateTurn(String input, String target) {
|
||||||
PartnerRunningFlowContext primaryContext = PartnerRunningFlowContext.Companion.fromSelf(input);
|
PartnerRunningFlowContext primaryContext = PartnerRunningFlowContext.Companion.fromSelf(input);
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
package work.slhaf.partner.core.cognition
|
||||||
|
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import javax.xml.transform.OutputKeys
|
||||||
|
import javax.xml.transform.TransformerFactory
|
||||||
|
import javax.xml.transform.dom.DOMSource
|
||||||
|
import javax.xml.transform.stream.StreamResult
|
||||||
|
import kotlin.concurrent.write
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class ContextWorkspace {
|
||||||
|
|
||||||
|
private val stateSet = mutableSetOf<ContextBlock>()
|
||||||
|
private val lock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据传入的 [ContextBlock.VisibleDomain] 列表,获取上下文块
|
||||||
|
* @param domains 需要获取上下文的域列表,顺序将决定权重优先级,按照列表排序将具备线性权重分层,最终反映到 blockContent 列表的排序上
|
||||||
|
*/
|
||||||
|
fun resolve(domains: List<ContextBlock.VisibleDomain>): List<BlockContent> = lock.write {
|
||||||
|
if (domains.isEmpty()) {
|
||||||
|
return@write emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val domainWeights = domains
|
||||||
|
.distinct()
|
||||||
|
.withIndex()
|
||||||
|
.associate { (index, domain) -> domain to (domains.size - index) }
|
||||||
|
|
||||||
|
val activeBlocks = mutableListOf<ResolvedContextBlock>()
|
||||||
|
val iterator = stateSet.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val block = iterator.next()
|
||||||
|
val activationScore = block.applyTimeFade()
|
||||||
|
if (activationScore <= 0.0) {
|
||||||
|
iterator.remove()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val matchedDomains = block.visibleTo.intersect(domainWeights.keys)
|
||||||
|
if (matchedDomains.isEmpty()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
activeBlocks += ResolvedContextBlock(
|
||||||
|
block = block,
|
||||||
|
domainWeight = matchedDomains.sumOf { domainWeights.getValue(it) },
|
||||||
|
activationScore = activationScore
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeBlocks
|
||||||
|
.sortedWith(
|
||||||
|
compareBy<ResolvedContextBlock> { it.domainWeight }
|
||||||
|
.thenBy { it.block.sourceKey.blockName }
|
||||||
|
.thenBy { it.block.sourceKey.source }
|
||||||
|
.thenBy { it.activationScore }
|
||||||
|
.thenBy { it.block.blockContent.encodeToXmlString() }
|
||||||
|
)
|
||||||
|
.map { it.block.render() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param contextBlock 注册的新上下文块
|
||||||
|
*/
|
||||||
|
fun register(contextBlock: ContextBlock) = lock.write {
|
||||||
|
val iterator = stateSet.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val currentBlock = iterator.next()
|
||||||
|
if (!currentBlock.sameWith(contextBlock)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlock.applyReplaceFade() <= 0.0) {
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stateSet += contextBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expire(blockName: String, source: String) = lock.write {
|
||||||
|
val sourceKey = ContextBlock.SourceKey(blockName, source)
|
||||||
|
val iterator = stateSet.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
if (iterator.next().sourceKey == sourceKey) {
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ResolvedContextBlock(
|
||||||
|
val block: ContextBlock,
|
||||||
|
val domainWeight: Int,
|
||||||
|
val activationScore: Double
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ContextBlock @JvmOverloads constructor(
|
||||||
|
val blockContent: BlockContent,
|
||||||
|
val compactBlock: BlockContent = blockContent,
|
||||||
|
val abstractBlock: BlockContent = blockContent,
|
||||||
|
/**
|
||||||
|
* 对哪些域可见
|
||||||
|
*/
|
||||||
|
val visibleTo: Set<VisibleDomain>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新的 [blockContent] 属性与其相同时,发生的衰退步长
|
||||||
|
*/
|
||||||
|
private val replaceFadeFactor: Double,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随时间发生的衰退步长,按照分钟定义
|
||||||
|
*/
|
||||||
|
private val timeFadeFactor: Double,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发一次激活时,发生的强化步长
|
||||||
|
*/
|
||||||
|
private val activateFactor: Double
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认活跃分数,降低至0时将在 [ContextWorkspace] 中移除该 block
|
||||||
|
* 此外还参与到当同源 block 存在时的排序,按该分数升序排列,只影响同源 block 间的顺序
|
||||||
|
*/
|
||||||
|
private var activationScore = 100.0
|
||||||
|
private var lastTouchedAt = Instant.now()
|
||||||
|
|
||||||
|
enum class VisibleDomain {
|
||||||
|
ACTION,
|
||||||
|
MEMORY,
|
||||||
|
PERCEIVE,
|
||||||
|
COGNITION,
|
||||||
|
COMMUNICATION,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val sourceKey: SourceKey
|
||||||
|
get() = SourceKey(blockContent.blockName, blockContent.source)
|
||||||
|
|
||||||
|
fun applyTimeFade(): Double {
|
||||||
|
val now = Instant.now()
|
||||||
|
val elapsedSeconds = Duration.between(lastTouchedAt, now).toMillis() / 1000.0
|
||||||
|
activationScore = max(0.0, activationScore - elapsedSeconds * (timeFadeFactor / 60.0))
|
||||||
|
lastTouchedAt = now
|
||||||
|
return activationScore
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyReplaceFade(): Double {
|
||||||
|
applyTimeFade()
|
||||||
|
activationScore = max(0.0, activationScore - replaceFadeFactor)
|
||||||
|
return activationScore
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activate(): Double {
|
||||||
|
applyTimeFade()
|
||||||
|
activationScore = min(100.0, activationScore + activateFactor)
|
||||||
|
return activationScore
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sameWith(contextBlock: ContextBlock): Boolean {
|
||||||
|
return this.sourceKey == contextBlock.sourceKey
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(): BlockContent {
|
||||||
|
return when {
|
||||||
|
activationScore < 30 -> abstractBlock
|
||||||
|
activationScore < 70 -> compactBlock
|
||||||
|
else -> blockContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SourceKey(
|
||||||
|
val blockName: String,
|
||||||
|
val source: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BlockContent protected constructor(
|
||||||
|
val blockName: String,
|
||||||
|
val source: String,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun encodeToXml(): Element {
|
||||||
|
val document = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder()
|
||||||
|
.newDocument()
|
||||||
|
|
||||||
|
val root = document.createElement(blockName)
|
||||||
|
root.setAttribute("source", source)
|
||||||
|
document.appendChild(root)
|
||||||
|
|
||||||
|
fillXml(document, root)
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeToXmlString(): String {
|
||||||
|
val transformer = TransformerFactory.newInstance().newTransformer().apply {
|
||||||
|
setOutputProperty(OutputKeys.INDENT, "yes")
|
||||||
|
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
|
||||||
|
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringWriter().use { writer ->
|
||||||
|
transformer.transform(DOMSource(encodeToXml()), StreamResult(writer))
|
||||||
|
writer.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun fillXml(document: Document, root: Element)
|
||||||
|
|
||||||
|
protected fun appendTextElement(
|
||||||
|
document: Document,
|
||||||
|
parent: Element,
|
||||||
|
tagName: String,
|
||||||
|
value: Any?
|
||||||
|
): Element {
|
||||||
|
val element = document.createElement(tagName)
|
||||||
|
element.textContent = value?.toString() ?: ""
|
||||||
|
parent.appendChild(element)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun appendChildElement(
|
||||||
|
document: Document,
|
||||||
|
parent: Element,
|
||||||
|
tagName: String,
|
||||||
|
block: Element.() -> Unit = {}
|
||||||
|
): Element {
|
||||||
|
val element = document.createElement(tagName)
|
||||||
|
parent.appendChild(element)
|
||||||
|
element.block()
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun appendCDataElement(
|
||||||
|
document: Document,
|
||||||
|
parent: Element,
|
||||||
|
tagName: String,
|
||||||
|
value: String?
|
||||||
|
): Element {
|
||||||
|
val element = document.createElement(tagName)
|
||||||
|
element.appendChild(document.createCDATASection(value ?: ""))
|
||||||
|
parent.appendChild(element)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <T> appendListElement(
|
||||||
|
document: Document,
|
||||||
|
parent: Element,
|
||||||
|
wrapperTagName: String,
|
||||||
|
itemTagName: String,
|
||||||
|
values: Iterable<T>,
|
||||||
|
block: Element.(T) -> Unit = { value ->
|
||||||
|
textContent = value?.toString() ?: ""
|
||||||
|
}
|
||||||
|
): Element {
|
||||||
|
val wrapper = document.createElement(wrapperTagName)
|
||||||
|
parent.appendChild(wrapper)
|
||||||
|
|
||||||
|
for (value in values) {
|
||||||
|
val item = document.createElement(itemTagName)
|
||||||
|
wrapper.appendChild(item)
|
||||||
|
item.block(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <T> appendRepeatedElements(
|
||||||
|
document: Document,
|
||||||
|
parent: Element,
|
||||||
|
itemTagName: String,
|
||||||
|
values: Iterable<T>,
|
||||||
|
block: Element.(T) -> Unit = { value ->
|
||||||
|
textContent = value?.toString() ?: ""
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
for (value in values) {
|
||||||
|
val item = document.createElement(itemTagName)
|
||||||
|
parent.appendChild(item)
|
||||||
|
item.block(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CommunicationBlockContent(
|
||||||
|
blockName: String,
|
||||||
|
source: String,
|
||||||
|
) : BlockContent(
|
||||||
|
blockName,
|
||||||
|
source,
|
||||||
|
) {
|
||||||
|
|
||||||
|
enum class Projection {
|
||||||
|
SUPPLY,
|
||||||
|
CONTEXT
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package work.slhaf.partner.core.cognition
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class ContextWorkspaceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sameWith uses blockName and source only`() {
|
||||||
|
val left = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "left",
|
||||||
|
compactContent = "left-compact",
|
||||||
|
abstractContent = "left-abstract"
|
||||||
|
)
|
||||||
|
val sameSource = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "right"
|
||||||
|
)
|
||||||
|
val differentSource = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "backup",
|
||||||
|
content = "right"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(left.sameWith(sameSource))
|
||||||
|
assertFalse(left.sameWith(differentSource))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve sorts by accumulated domain weight across sources`() {
|
||||||
|
val manager = ContextWorkspace()
|
||||||
|
val lowWeight = contextBlock(
|
||||||
|
blockName = "low",
|
||||||
|
source = "source-low",
|
||||||
|
content = "low",
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.COGNITION)
|
||||||
|
)
|
||||||
|
val midWeight = contextBlock(
|
||||||
|
blockName = "mid",
|
||||||
|
source = "source-mid",
|
||||||
|
content = "mid",
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
val highWeight = contextBlock(
|
||||||
|
blockName = "high",
|
||||||
|
source = "source-high",
|
||||||
|
content = "high",
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.ACTION, ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.register(highWeight)
|
||||||
|
manager.register(lowWeight)
|
||||||
|
manager.register(midWeight)
|
||||||
|
|
||||||
|
val resolved = manager.resolve(
|
||||||
|
listOf(
|
||||||
|
ContextBlock.VisibleDomain.ACTION,
|
||||||
|
ContextBlock.VisibleDomain.MEMORY,
|
||||||
|
ContextBlock.VisibleDomain.COGNITION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("low", "mid", "high"),
|
||||||
|
resolved.map { (it as TestBlockContent).content }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve uses activation score only within same source`() {
|
||||||
|
val manager = ContextWorkspace()
|
||||||
|
val older = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "older",
|
||||||
|
replaceFadeFactor = 20.0,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
val newer = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "newer",
|
||||||
|
replaceFadeFactor = 20.0,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
val otherSource = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "secondary",
|
||||||
|
content = "other-source",
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.register(older)
|
||||||
|
manager.register(newer)
|
||||||
|
manager.register(otherSource)
|
||||||
|
|
||||||
|
val resolved = manager.resolve(listOf(ContextBlock.VisibleDomain.MEMORY))
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("older", "newer", "other-source"),
|
||||||
|
resolved.map { (it as TestBlockContent).content }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `register fades matching source blocks and removes zero score ones`() {
|
||||||
|
val manager = ContextWorkspace()
|
||||||
|
val evicted = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "evicted",
|
||||||
|
replaceFadeFactor = 100.0,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
val replacement = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "replacement",
|
||||||
|
replaceFadeFactor = 100.0,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY)
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.register(evicted)
|
||||||
|
manager.register(replacement)
|
||||||
|
|
||||||
|
val resolved = manager.resolve(listOf(ContextBlock.VisibleDomain.MEMORY))
|
||||||
|
|
||||||
|
assertEquals(listOf("replacement"), resolved.map { (it as TestBlockContent).content })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `expire removes all matching source blocks`() {
|
||||||
|
val manager = ContextWorkspace()
|
||||||
|
val first = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "first"
|
||||||
|
)
|
||||||
|
val second = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "second"
|
||||||
|
)
|
||||||
|
val survivor = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "secondary",
|
||||||
|
content = "survivor"
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.register(first)
|
||||||
|
manager.register(second)
|
||||||
|
manager.register(survivor)
|
||||||
|
|
||||||
|
manager.expire("memory", "main")
|
||||||
|
|
||||||
|
val resolved = manager.resolve(listOf(ContextBlock.VisibleDomain.MEMORY))
|
||||||
|
|
||||||
|
assertEquals(listOf("survivor"), resolved.map { (it as TestBlockContent).content })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `expire allows same source to be registered again`() {
|
||||||
|
val manager = ContextWorkspace()
|
||||||
|
val original = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "original"
|
||||||
|
)
|
||||||
|
val restored = contextBlock(
|
||||||
|
blockName = "memory",
|
||||||
|
source = "main",
|
||||||
|
content = "restored"
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.register(original)
|
||||||
|
manager.expire("memory", "main")
|
||||||
|
manager.register(restored)
|
||||||
|
|
||||||
|
val resolved = manager.resolve(listOf(ContextBlock.VisibleDomain.MEMORY))
|
||||||
|
|
||||||
|
assertEquals(listOf("restored"), resolved.map { (it as TestBlockContent).content })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `render switches projections by activation score`() {
|
||||||
|
val original = TestBlockContent("memory", "main", "full")
|
||||||
|
val compact = TestBlockContent("memory", "main", "compact")
|
||||||
|
val summary = TestBlockContent("memory", "main", "summary")
|
||||||
|
|
||||||
|
val compactBlock = ContextBlock(
|
||||||
|
blockContent = original,
|
||||||
|
compactBlock = compact,
|
||||||
|
abstractBlock = summary,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY),
|
||||||
|
replaceFadeFactor = 40.0,
|
||||||
|
timeFadeFactor = 0.0,
|
||||||
|
activateFactor = 0.0
|
||||||
|
)
|
||||||
|
compactBlock.applyReplaceFade()
|
||||||
|
assertSame(compact, compactBlock.render())
|
||||||
|
|
||||||
|
val summaryBlock = ContextBlock(
|
||||||
|
blockContent = original,
|
||||||
|
compactBlock = compact,
|
||||||
|
abstractBlock = summary,
|
||||||
|
visibleTo = setOf(ContextBlock.VisibleDomain.MEMORY),
|
||||||
|
replaceFadeFactor = 80.0,
|
||||||
|
timeFadeFactor = 0.0,
|
||||||
|
activateFactor = 0.0
|
||||||
|
)
|
||||||
|
summaryBlock.applyReplaceFade()
|
||||||
|
assertSame(summary, summaryBlock.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun contextBlock(
|
||||||
|
blockName: String,
|
||||||
|
source: String,
|
||||||
|
content: String,
|
||||||
|
compactContent: String = "${content}-compact",
|
||||||
|
abstractContent: String = "${content}-abstract",
|
||||||
|
visibleTo: Set<ContextBlock.VisibleDomain> = setOf(ContextBlock.VisibleDomain.MEMORY),
|
||||||
|
replaceFadeFactor: Double = 10.0,
|
||||||
|
timeFadeFactor: Double = 0.0,
|
||||||
|
activateFactor: Double = 0.0
|
||||||
|
): ContextBlock {
|
||||||
|
return ContextBlock(
|
||||||
|
blockContent = TestBlockContent(blockName, source, content),
|
||||||
|
compactBlock = TestBlockContent(blockName, source, compactContent),
|
||||||
|
abstractBlock = TestBlockContent(blockName, source, abstractContent),
|
||||||
|
visibleTo = visibleTo,
|
||||||
|
replaceFadeFactor = replaceFadeFactor,
|
||||||
|
timeFadeFactor = timeFadeFactor,
|
||||||
|
activateFactor = activateFactor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBlockContent(
|
||||||
|
blockName: String,
|
||||||
|
source: String,
|
||||||
|
val content: String
|
||||||
|
) : BlockContent(blockName, source) {
|
||||||
|
override fun fillXml(document: org.w3c.dom.Document, root: org.w3c.dom.Element) {
|
||||||
|
appendTextElement(document, root, "content", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user