fix: preserve user chat messages after ai onboarding
This commit is contained in:
@@ -701,8 +701,10 @@ public class BossApiClient {
|
||||
|
||||
void rememberIdentity(JSONObject json) {
|
||||
if (json == null) return;
|
||||
JSONObject session = json.optJSONObject("session");
|
||||
JSONObject source = session != null ? session : json;
|
||||
JSONObject source = resolveSessionIdentitySource(json);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
String restoreToken = source.optString("restoreToken", "");
|
||||
@@ -723,6 +725,24 @@ public class BossApiClient {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JSONObject resolveSessionIdentitySource(JSONObject json) {
|
||||
JSONObject session = json.optJSONObject("session");
|
||||
if (session != null) {
|
||||
return session;
|
||||
}
|
||||
if (
|
||||
json.has("restoreToken")
|
||||
|| json.has("account")
|
||||
|| json.has("role")
|
||||
|| json.has("expiresAt")
|
||||
|| json.has("sessionCookie")
|
||||
) {
|
||||
return json;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void clearSession() {
|
||||
prefs.edit()
|
||||
.remove(KEY_SESSION_COOKIE)
|
||||
|
||||
@@ -718,11 +718,12 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
|
||||
private View buildMessageView(JSONObject message) {
|
||||
String messageId = message.optString("id", "");
|
||||
String sender = message.optString("sender", "");
|
||||
String senderLabel = message.optString("senderLabel", "消息");
|
||||
String body = message.optString("body", "");
|
||||
String meta = formatMessageTime(message.optString("sentAt", ""));
|
||||
String kind = message.optString("kind", "");
|
||||
boolean outgoing = isOutgoingMessage(senderLabel);
|
||||
boolean outgoing = isOutgoingMessage(senderLabel, sender);
|
||||
|
||||
View messageView;
|
||||
View.OnClickListener messagePrimaryClick = null;
|
||||
@@ -1293,7 +1294,10 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
return remainingScroll <= BossUi.dp(this, 96);
|
||||
}
|
||||
|
||||
private boolean isOutgoingMessage(String senderLabel) {
|
||||
private boolean isOutgoingMessage(String senderLabel, @Nullable String sender) {
|
||||
if ("user".equals(sender)) {
|
||||
return true;
|
||||
}
|
||||
if (TextUtils.isEmpty(senderLabel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,6 +119,27 @@ public class BossApiClientDispatchPlansTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rememberIdentityDoesNotOverwriteSessionIdentityFromAiAccountOnboardingResponse() throws Exception {
|
||||
InMemorySharedPreferences prefs = new InMemorySharedPreferences();
|
||||
prefs.edit()
|
||||
.putString("account", "17600003315")
|
||||
.putString("display_name", "Boss 超级管理员")
|
||||
.apply();
|
||||
BossApiClient apiClient = new BossApiClient(prefs, "https://boss.hyzq.net");
|
||||
|
||||
JSONObject onboardingResponse = new JSONObject()
|
||||
.put("ok", true)
|
||||
.put("accountId", "openai-api-primary")
|
||||
.put("displayName", "OpenAI 平台账号")
|
||||
.put("message", "OpenAI 平台账号已登录,并设为当前主控。");
|
||||
|
||||
apiClient.rememberIdentity(onboardingResponse);
|
||||
|
||||
assertEquals("17600003315", apiClient.getAccountLabel());
|
||||
assertEquals("Boss 超级管理员", apiClient.getDisplayName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onboardMasterNodeFallsBackToGenericAccountCreationWhenDedicatedRouteMissing() throws Exception {
|
||||
RecordingConnection dedicated = new RecordingConnection(
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -24,6 +25,11 @@ import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowDialog;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 34)
|
||||
public class ProjectDetailActivityUiTest {
|
||||
@@ -191,6 +197,41 @@ public class ProjectDetailActivityUiTest {
|
||||
assertFalse(viewTreeContainsText(attachmentView, "已分析"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userMessageRemainsOutgoingWhenAiAccountDisplayNameDiffers() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
||||
TestProjectDetailActivity activity = Robolectric
|
||||
.buildActivity(TestProjectDetailActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
InMemorySharedPreferences prefs = new InMemorySharedPreferences();
|
||||
prefs.edit()
|
||||
.putString("account", "17600003315")
|
||||
.putString("display_name", "OpenAI 平台账号")
|
||||
.apply();
|
||||
ReflectionHelpers.setField(activity, "apiClient", new BossApiClient(prefs, "https://boss.hyzq.net"));
|
||||
|
||||
JSONObject message = new JSONObject()
|
||||
.put("id", "msg-user-1")
|
||||
.put("sender", "user")
|
||||
.put("senderLabel", "Boss 超级管理员")
|
||||
.put("body", "请只回复一句:聊天链路自检正常。")
|
||||
.put("kind", "text")
|
||||
.put("sentAt", "2026-03-31T10:26:00.000Z");
|
||||
|
||||
View messageView = ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"buildMessageView",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
|
||||
);
|
||||
|
||||
assertTrue(viewTreeContainsText(messageView, "10:26"));
|
||||
assertFalse(viewTreeContainsText(messageView, "Boss 超级管理员 · 10:26"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outgoingAttachmentMetaPrefersTimeOnly() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
@@ -384,4 +425,137 @@ public class ProjectDetailActivityUiTest {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InMemorySharedPreferences implements SharedPreferences {
|
||||
private final Map<String, Object> values = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getAll() {
|
||||
return Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key, String defValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof String ? (String) value : defValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getStringSet(String key, Set<String> defValues) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Set ? (Set<String>) value : defValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Integer ? (Integer) value : defValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Long ? (Long) value : defValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String key, float defValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Float ? (Float) value : defValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
Object value = values.get(key);
|
||||
return value instanceof Boolean ? (Boolean) value : defValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String key) {
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor edit() {
|
||||
return new Editor() {
|
||||
private final Map<String, Object> staged = new HashMap<>();
|
||||
private boolean clearRequested = false;
|
||||
|
||||
@Override
|
||||
public Editor putString(String key, String value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putStringSet(String key, Set<String> value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putInt(String key, int value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putLong(String key, long value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putFloat(String key, float value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putBoolean(String key, boolean value) {
|
||||
staged.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor remove(String key) {
|
||||
staged.put(key, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor clear() {
|
||||
clearRequested = true;
|
||||
staged.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if (clearRequested) {
|
||||
values.clear();
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : staged.entrySet()) {
|
||||
if (entry.getValue() == null) {
|
||||
values.remove(entry.getKey());
|
||||
} else {
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
|
||||
|
||||
@Override
|
||||
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user